mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Merge pull request #54 from johndoe6345789/codex/refactor-comprehensivedemopage-into-subcomponents
Refactor ComprehensiveDemoPage into subcomponents and externalize UI strings
This commit is contained in:
@@ -1,26 +1,15 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { useCRUD, useSearch } from '@/hooks/data'
|
||||
import { useState } from 'react'
|
||||
import { useCRUD } from '@/hooks/data'
|
||||
import { useDialog } from '@/hooks/ui'
|
||||
import { useKV } from '@/hooks/use-kv'
|
||||
import { SearchBar } from '@/components/molecules/SearchBar'
|
||||
import { DataList, ActionButton, IconButton } from '@/components/atoms'
|
||||
import { Plus, Trash, Check, Clock } from '@phosphor-icons/react'
|
||||
import { toast } from 'sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface Todo {
|
||||
id: number
|
||||
text: string
|
||||
completed: boolean
|
||||
priority: 'low' | 'medium' | 'high'
|
||||
createdAt: string
|
||||
}
|
||||
import strings from '@/data/comprehensive-demo.json'
|
||||
import { ComprehensiveDemoHeader } from '@/components/comprehensive-demo/ComprehensiveDemoHeader'
|
||||
import { ComprehensiveDemoStatsRow } from '@/components/comprehensive-demo/ComprehensiveDemoStatsRow'
|
||||
import { ComprehensiveDemoTaskList } from '@/components/comprehensive-demo/ComprehensiveDemoTaskList'
|
||||
import { ComprehensiveDemoArchitectureHighlights } from '@/components/comprehensive-demo/ComprehensiveDemoArchitectureHighlights'
|
||||
import { ComprehensiveDemoDialogs } from '@/components/comprehensive-demo/ComprehensiveDemoDialogs'
|
||||
import type { Priority, Todo } from '@/components/comprehensive-demo/types'
|
||||
|
||||
export function ComprehensiveDemoPage() {
|
||||
const [todos, setTodos] = useKV<Todo[]>('json-demo-todos', [])
|
||||
@@ -30,21 +19,9 @@ export function ComprehensiveDemoPage() {
|
||||
setItems: (updater) => setTodos(updater),
|
||||
})
|
||||
|
||||
const { query, setQuery, filtered } = useSearch({
|
||||
items: todos,
|
||||
searchFields: ['text' as keyof Todo],
|
||||
})
|
||||
|
||||
const newTodoDialog = useDialog()
|
||||
const [newTodoText, setNewTodoText] = useState('')
|
||||
const [newTodoPriority, setNewTodoPriority] = useState<'low' | 'medium' | 'high'>('medium')
|
||||
|
||||
const stats = {
|
||||
total: todos.length,
|
||||
completed: todos.filter(t => t.completed).length,
|
||||
pending: todos.filter(t => !t.completed).length,
|
||||
completionRate: todos.length > 0 ? (todos.filter(t => t.completed).length / todos.length) * 100 : 0,
|
||||
}
|
||||
const [newTodoPriority, setNewTodoPriority] = useState<Priority>('medium')
|
||||
|
||||
const handleAddTodo = () => {
|
||||
if (newTodoText.trim()) {
|
||||
@@ -58,7 +35,7 @@ export function ComprehensiveDemoPage() {
|
||||
setNewTodoText('')
|
||||
setNewTodoPriority('medium')
|
||||
newTodoDialog.close()
|
||||
toast.success('Task added successfully!')
|
||||
toast.success(strings.toast.added)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,248 +43,38 @@ export function ComprehensiveDemoPage() {
|
||||
const todo = todos.find(t => t.id === id)
|
||||
if (todo) {
|
||||
crud.update(id, { completed: !todo.completed })
|
||||
toast.success(todo.completed ? 'Task marked as pending' : 'Task completed!')
|
||||
toast.success(todo.completed ? strings.toast.pending : strings.toast.completed)
|
||||
}
|
||||
}
|
||||
|
||||
const handleDeleteTodo = (id: number) => {
|
||||
crud.delete(id)
|
||||
toast.success('Task deleted')
|
||||
}
|
||||
|
||||
const getPriorityColor = (priority: string) => {
|
||||
switch (priority) {
|
||||
case 'high': return 'bg-red-500/10 text-red-600 border-red-500/20'
|
||||
case 'medium': return 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20'
|
||||
case 'low': return 'bg-blue-500/10 text-blue-600 border-blue-500/20'
|
||||
default: return 'bg-gray-500/10 text-gray-600 border-gray-500/20'
|
||||
}
|
||||
toast.success(strings.toast.deleted)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-auto p-6 bg-gradient-to-br from-background via-background to-primary/5">
|
||||
<div className="max-w-5xl mx-auto space-y-6">
|
||||
{/* Header */}
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-primary via-primary to-accent bg-clip-text text-transparent">
|
||||
Advanced Task Manager
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Demonstrating atomic components, custom hooks, and reactive state management
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stats Row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Card className="bg-card/50 backdrop-blur">
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">Total Tasks</p>
|
||||
<p className="text-3xl font-bold">{stats.total}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-green-500/5 backdrop-blur border-green-500/20">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">Completed</p>
|
||||
<p className="text-3xl font-bold text-green-600">{stats.completed}</p>
|
||||
</div>
|
||||
<Check className="text-green-600" size={24} weight="duotone" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-blue-500/5 backdrop-blur border-blue-500/20">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">Pending</p>
|
||||
<p className="text-3xl font-bold text-blue-600">{stats.pending}</p>
|
||||
</div>
|
||||
<Clock className="text-blue-600" size={24} weight="duotone" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-primary/5 backdrop-blur border-primary/20">
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">Completion</p>
|
||||
<p className="text-3xl font-bold text-primary">{Math.round(stats.completionRate)}%</p>
|
||||
<Progress value={stats.completionRate} className="h-2" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle>Your Tasks</CardTitle>
|
||||
<CardDescription>Manage your tasks with advanced features</CardDescription>
|
||||
</div>
|
||||
<ActionButton
|
||||
icon={<Plus size={16} weight="bold" />}
|
||||
label="Add Task"
|
||||
onClick={newTodoDialog.open}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{/* Search */}
|
||||
<SearchBar
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
placeholder="Search tasks..."
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Task List */}
|
||||
<DataList
|
||||
items={filtered}
|
||||
emptyMessage={query ? 'No tasks match your search' : 'No tasks yet. Click "Add Task" to get started!'}
|
||||
renderItem={(todo) => (
|
||||
<Card className="bg-card/50 backdrop-blur hover:bg-card transition-colors">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox
|
||||
checked={todo.completed}
|
||||
onCheckedChange={() => handleToggleTodo(todo.id)}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className={cn(
|
||||
'font-medium',
|
||||
todo.completed && 'line-through text-muted-foreground'
|
||||
)}>
|
||||
{todo.text}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge variant="outline" className={getPriorityColor(todo.priority)}>
|
||||
{todo.priority}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(todo.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<IconButton
|
||||
icon={<Trash size={16} />}
|
||||
onClick={() => handleDeleteTodo(todo.id)}
|
||||
variant="ghost"
|
||||
title="Delete task"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Architecture Info */}
|
||||
<Card className="bg-accent/5 border-accent/20">
|
||||
<CardHeader>
|
||||
<CardTitle>Architecture Highlights</CardTitle>
|
||||
<CardDescription>What makes this demo special</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="text-accent mt-1" size={16} weight="bold" />
|
||||
<div>
|
||||
<p className="font-medium">Custom Hooks</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
useCRUD for data management, useSearch for filtering, useDialog for modals
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="text-accent mt-1" size={16} weight="bold" />
|
||||
<div>
|
||||
<p className="font-medium">Atomic Components</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
ActionButton, IconButton, DataList, SearchBar - all under 150 LOC
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="text-accent mt-1" size={16} weight="bold" />
|
||||
<div>
|
||||
<p className="font-medium">KV Persistence</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
All data persists between sessions using the Spark KV store
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Check className="text-accent mt-1" size={16} weight="bold" />
|
||||
<div>
|
||||
<p className="font-medium">Reactive State</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Computed stats update automatically when todos change
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<ComprehensiveDemoHeader />
|
||||
<ComprehensiveDemoStatsRow todos={todos} />
|
||||
<ComprehensiveDemoTaskList
|
||||
todos={todos}
|
||||
onAdd={newTodoDialog.open}
|
||||
onToggle={handleToggleTodo}
|
||||
onDelete={handleDeleteTodo}
|
||||
/>
|
||||
<ComprehensiveDemoArchitectureHighlights />
|
||||
</div>
|
||||
|
||||
{/* Add Todo Dialog */}
|
||||
{newTodoDialog.isOpen && (
|
||||
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle>Add New Task</CardTitle>
|
||||
<CardDescription>Create a new task with priority</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Task Description</label>
|
||||
<Input
|
||||
value={newTodoText}
|
||||
onChange={(e) => setNewTodoText(e.target.value)}
|
||||
placeholder="What needs to be done?"
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleAddTodo()}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Priority</label>
|
||||
<div className="flex gap-2">
|
||||
{(['low', 'medium', 'high'] as const).map((priority) => (
|
||||
<Button
|
||||
key={priority}
|
||||
variant={newTodoPriority === priority ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => setNewTodoPriority(priority)}
|
||||
className="flex-1"
|
||||
>
|
||||
{priority}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-4">
|
||||
<Button onClick={handleAddTodo} className="flex-1" disabled={!newTodoText.trim()}>
|
||||
Add Task
|
||||
</Button>
|
||||
<Button onClick={newTodoDialog.close} variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
<ComprehensiveDemoDialogs
|
||||
isOpen={newTodoDialog.isOpen}
|
||||
newTodoText={newTodoText}
|
||||
newTodoPriority={newTodoPriority}
|
||||
onNewTodoTextChange={setNewTodoText}
|
||||
onNewTodoPriorityChange={setNewTodoPriority}
|
||||
onAdd={handleAddTodo}
|
||||
onClose={newTodoDialog.close}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Missing import
|
||||
import { useState } from 'react'
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Check } from '@phosphor-icons/react'
|
||||
import strings from '@/data/comprehensive-demo.json'
|
||||
|
||||
export function ComprehensiveDemoArchitectureHighlights() {
|
||||
return (
|
||||
<Card className="bg-accent/5 border-accent/20">
|
||||
<CardHeader>
|
||||
<CardTitle>{strings.architecture.title}</CardTitle>
|
||||
<CardDescription>{strings.architecture.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{strings.architecture.items.map((item) => (
|
||||
<div key={item.title} className="flex items-start gap-3">
|
||||
<Check className="text-accent mt-1" size={16} weight="bold" />
|
||||
<div>
|
||||
<p className="font-medium">{item.title}</p>
|
||||
<p className="text-sm text-muted-foreground">{item.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import strings from '@/data/comprehensive-demo.json'
|
||||
import type { Priority } from './types'
|
||||
|
||||
interface ComprehensiveDemoDialogsProps {
|
||||
isOpen: boolean
|
||||
newTodoText: string
|
||||
newTodoPriority: Priority
|
||||
onNewTodoTextChange: (value: string) => void
|
||||
onNewTodoPriorityChange: (value: Priority) => void
|
||||
onAdd: () => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const priorities: Priority[] = ['low', 'medium', 'high']
|
||||
|
||||
export function ComprehensiveDemoDialogs({
|
||||
isOpen,
|
||||
newTodoText,
|
||||
newTodoPriority,
|
||||
onNewTodoTextChange,
|
||||
onNewTodoPriorityChange,
|
||||
onAdd,
|
||||
onClose,
|
||||
}: ComprehensiveDemoDialogsProps) {
|
||||
if (!isOpen) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-background/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle>{strings.dialog.title}</CardTitle>
|
||||
<CardDescription>{strings.dialog.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">{strings.dialog.taskDescriptionLabel}</label>
|
||||
<Input
|
||||
value={newTodoText}
|
||||
onChange={(event) => onNewTodoTextChange(event.target.value)}
|
||||
placeholder={strings.dialog.taskDescriptionPlaceholder}
|
||||
onKeyDown={(event) => event.key === 'Enter' && onAdd()}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">{strings.dialog.priorityLabel}</label>
|
||||
<div className="flex gap-2">
|
||||
{priorities.map((priority) => (
|
||||
<Button
|
||||
key={priority}
|
||||
variant={newTodoPriority === priority ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => onNewTodoPriorityChange(priority)}
|
||||
className="flex-1"
|
||||
>
|
||||
{strings.priorityLabels[priority]}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 pt-4">
|
||||
<Button onClick={onAdd} className="flex-1" disabled={!newTodoText.trim()}>
|
||||
{strings.dialog.addButton}
|
||||
</Button>
|
||||
<Button onClick={onClose} variant="outline">
|
||||
{strings.dialog.cancelButton}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import strings from '@/data/comprehensive-demo.json'
|
||||
|
||||
export function ComprehensiveDemoHeader() {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<h1 className="text-4xl font-bold bg-gradient-to-r from-primary via-primary to-accent bg-clip-text text-transparent">
|
||||
{strings.header.title}
|
||||
</h1>
|
||||
<p className="text-muted-foreground">{strings.header.subtitle}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useMemo } from 'react'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Check, Clock } from '@phosphor-icons/react'
|
||||
import strings from '@/data/comprehensive-demo.json'
|
||||
import type { Todo } from './types'
|
||||
|
||||
interface ComprehensiveDemoStatsRowProps {
|
||||
todos: Todo[]
|
||||
}
|
||||
|
||||
export function ComprehensiveDemoStatsRow({ todos }: ComprehensiveDemoStatsRowProps) {
|
||||
const stats = useMemo(() => {
|
||||
const total = todos.length
|
||||
const completed = todos.filter((todo) => todo.completed).length
|
||||
const pending = total - completed
|
||||
const completionRate = total > 0 ? (completed / total) * 100 : 0
|
||||
|
||||
return { total, completed, pending, completionRate }
|
||||
}, [todos])
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<Card className="bg-card/50 backdrop-blur">
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">{strings.stats.total}</p>
|
||||
<p className="text-3xl font-bold">{stats.total}</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-green-500/5 backdrop-blur border-green-500/20">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">{strings.stats.completed}</p>
|
||||
<p className="text-3xl font-bold text-green-600">{stats.completed}</p>
|
||||
</div>
|
||||
<Check className="text-green-600" size={24} weight="duotone" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-blue-500/5 backdrop-blur border-blue-500/20">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm text-muted-foreground">{strings.stats.pending}</p>
|
||||
<p className="text-3xl font-bold text-blue-600">{stats.pending}</p>
|
||||
</div>
|
||||
<Clock className="text-blue-600" size={24} weight="duotone" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-primary/5 backdrop-blur border-primary/20">
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-muted-foreground">{strings.stats.completion}</p>
|
||||
<p className="text-3xl font-bold text-primary">{Math.round(stats.completionRate)}%</p>
|
||||
<Progress value={stats.completionRate} className="h-2" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
116
src/components/comprehensive-demo/ComprehensiveDemoTaskList.tsx
Normal file
116
src/components/comprehensive-demo/ComprehensiveDemoTaskList.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { SearchBar } from '@/components/molecules/SearchBar'
|
||||
import { DataList, ActionButton, IconButton } from '@/components/atoms'
|
||||
import { Trash, Plus } from '@phosphor-icons/react'
|
||||
import { useSearch } from '@/hooks/data'
|
||||
import { cn } from '@/lib/utils'
|
||||
import strings from '@/data/comprehensive-demo.json'
|
||||
import type { Todo } from './types'
|
||||
|
||||
interface ComprehensiveDemoTaskListProps {
|
||||
todos: Todo[]
|
||||
onAdd: () => void
|
||||
onToggle: (id: number) => void
|
||||
onDelete: (id: number) => void
|
||||
}
|
||||
|
||||
const priorityLabels = strings.priorityLabels as Record<Todo['priority'], string>
|
||||
|
||||
const getPriorityColor = (priority: Todo['priority']) => {
|
||||
switch (priority) {
|
||||
case 'high':
|
||||
return 'bg-red-500/10 text-red-600 border-red-500/20'
|
||||
case 'medium':
|
||||
return 'bg-yellow-500/10 text-yellow-600 border-yellow-500/20'
|
||||
case 'low':
|
||||
return 'bg-blue-500/10 text-blue-600 border-blue-500/20'
|
||||
default:
|
||||
return 'bg-gray-500/10 text-gray-600 border-gray-500/20'
|
||||
}
|
||||
}
|
||||
|
||||
export function ComprehensiveDemoTaskList({
|
||||
todos,
|
||||
onAdd,
|
||||
onToggle,
|
||||
onDelete,
|
||||
}: ComprehensiveDemoTaskListProps) {
|
||||
const { query, setQuery, filtered } = useSearch({
|
||||
items: todos,
|
||||
searchFields: ['text' as keyof Todo],
|
||||
})
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
<CardTitle>{strings.taskCard.title}</CardTitle>
|
||||
<CardDescription>{strings.taskCard.description}</CardDescription>
|
||||
</div>
|
||||
<ActionButton
|
||||
icon={<Plus size={16} weight="bold" />}
|
||||
label={strings.taskCard.addTask}
|
||||
onClick={onAdd}
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<SearchBar
|
||||
value={query}
|
||||
onChange={setQuery}
|
||||
placeholder={strings.taskCard.searchPlaceholder}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
<DataList
|
||||
items={filtered}
|
||||
emptyMessage={
|
||||
query ? strings.taskCard.empty.noMatch : strings.taskCard.empty.noTasks
|
||||
}
|
||||
renderItem={(todo) => (
|
||||
<Card className="bg-card/50 backdrop-blur hover:bg-card transition-colors">
|
||||
<CardContent className="pt-6">
|
||||
<div className="flex items-start gap-3">
|
||||
<Checkbox
|
||||
checked={todo.completed}
|
||||
onCheckedChange={() => onToggle(todo.id)}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p
|
||||
className={cn(
|
||||
'font-medium',
|
||||
todo.completed && 'line-through text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
{todo.text}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge variant="outline" className={getPriorityColor(todo.priority)}>
|
||||
{priorityLabels[todo.priority]}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(todo.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<IconButton
|
||||
icon={<Trash size={16} />}
|
||||
onClick={() => onDelete(todo.id)}
|
||||
variant="ghost"
|
||||
title={strings.taskCard.deleteTitle}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
9
src/components/comprehensive-demo/types.ts
Normal file
9
src/components/comprehensive-demo/types.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export type Priority = 'low' | 'medium' | 'high'
|
||||
|
||||
export interface Todo {
|
||||
id: number
|
||||
text: string
|
||||
completed: boolean
|
||||
priority: Priority
|
||||
createdAt: string
|
||||
}
|
||||
65
src/data/comprehensive-demo.json
Normal file
65
src/data/comprehensive-demo.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"header": {
|
||||
"title": "Advanced Task Manager",
|
||||
"subtitle": "Demonstrating atomic components, custom hooks, and reactive state management"
|
||||
},
|
||||
"stats": {
|
||||
"total": "Total Tasks",
|
||||
"completed": "Completed",
|
||||
"pending": "Pending",
|
||||
"completion": "Completion"
|
||||
},
|
||||
"taskCard": {
|
||||
"title": "Your Tasks",
|
||||
"description": "Manage your tasks with advanced features",
|
||||
"addTask": "Add Task",
|
||||
"searchPlaceholder": "Search tasks...",
|
||||
"empty": {
|
||||
"noMatch": "No tasks match your search",
|
||||
"noTasks": "No tasks yet. Click \"Add Task\" to get started!"
|
||||
},
|
||||
"deleteTitle": "Delete task"
|
||||
},
|
||||
"architecture": {
|
||||
"title": "Architecture Highlights",
|
||||
"description": "What makes this demo special",
|
||||
"items": [
|
||||
{
|
||||
"title": "Custom Hooks",
|
||||
"description": "useCRUD for data management, useSearch for filtering, useDialog for modals"
|
||||
},
|
||||
{
|
||||
"title": "Atomic Components",
|
||||
"description": "ActionButton, IconButton, DataList, SearchBar - all under 150 LOC"
|
||||
},
|
||||
{
|
||||
"title": "KV Persistence",
|
||||
"description": "All data persists between sessions using the Spark KV store"
|
||||
},
|
||||
{
|
||||
"title": "Reactive State",
|
||||
"description": "Computed stats update automatically when todos change"
|
||||
}
|
||||
]
|
||||
},
|
||||
"dialog": {
|
||||
"title": "Add New Task",
|
||||
"description": "Create a new task with priority",
|
||||
"taskDescriptionLabel": "Task Description",
|
||||
"taskDescriptionPlaceholder": "What needs to be done?",
|
||||
"priorityLabel": "Priority",
|
||||
"addButton": "Add Task",
|
||||
"cancelButton": "Cancel"
|
||||
},
|
||||
"priorityLabels": {
|
||||
"low": "low",
|
||||
"medium": "medium",
|
||||
"high": "high"
|
||||
},
|
||||
"toast": {
|
||||
"added": "Task added successfully!",
|
||||
"pending": "Task marked as pending",
|
||||
"completed": "Task completed!",
|
||||
"deleted": "Task deleted"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user