Files
low-code-react-app-b/src/components/ComprehensiveDemoPage.tsx
johndoe6345789 4dfded3533 Generated by Spark: @johndoe6345789 ➜ /workspaces/low-code-react-app-b (main) $ npm run build
> spark-template@0.0.0 prebuild
> mkdir -p /tmp/dist || true

> spark-template@0.0.0 build
> tsc -b --noCheck && vite build

vite v7.3.1 building client environment for production...
<script src="/runtime-config.js"> in "/index.html" can't be bundled without type="module" attribute
✓ 37 modules transformed.
✗ Build failed in 1.07s
error during build:
[vite]: Rollup failed to resolve import "@github/spark/hooks" from "/workspaces/low-code-react-app-b/src/hooks/use-project-state.ts".
This is most likely unintended because it can break your application at runtime.
If you do want to externalize this module explicitly add it to
`build.rollupOptions.external`
    at viteLog (file:///workspaces/low-code-react-app-b/node_modules/vite/dist/node/chunks/config.js:33635:57)
    at file:///workspaces/low-code-react-app-b/node_modules/vite/dist/node/chunks/config.js:33669:73
    at onwarn (file:///workspaces/low-code-react-app-b/node_modules/@vitejs/plugin-react-swc/index.js:76:7)
    at file:///workspaces/low-code-react-app-b/node_modules/vite/dist/node/chunks/config.js:33669:28
    at onRollupLog (file:///workspaces/low-code-react-app-b/node_modules/vite/dist/node/chunks/config.js:33664:63)
    at onLog (file:///workspaces/low-code-react-app-b/node_modules/vite/dist/node/chunks/config.js:33467:4)
    at file:///workspaces/low-code-react-app-b/node_modules/rollup/dist/es/shared/node-entry.js:20961:32
    at Object.logger [as onLog] (file:///workspaces/low-code-react-app-b/node_modules/rollup/dist/es/shared/node-entry.js:22848:9)
    at ModuleLoader.handleInvalidResolvedId (file:///workspaces/low-code-react-app-b/node_modules/rollup/dist/es/shared/node-entry.js:21592:26)
    at file:///workspaces/low-code-react-app-b/node_modules/rollup/dist/es/shared/node-entry.js:21550:26
@johndoe6345789 ➜ /workspaces/low-code-react-app-b (main) $
2026-01-17 19:39:46 +00:00

314 lines
12 KiB
TypeScript

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 { 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
}
export function ComprehensiveDemoPage() {
const [todos, setTodos] = useKV<Todo[]>('json-demo-todos', [])
const crud = useCRUD<Todo>({
items: todos,
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 handleAddTodo = () => {
if (newTodoText.trim()) {
crud.create({
id: Date.now(),
text: newTodoText,
completed: false,
priority: newTodoPriority,
createdAt: new Date().toISOString(),
})
setNewTodoText('')
setNewTodoPriority('medium')
newTodoDialog.close()
toast.success('Task added successfully!')
}
}
const handleToggleTodo = (id: number) => {
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!')
}
}
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'
}
}
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>
</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>
)}
</div>
)
}
// Missing import
import { useState } from 'react'