mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-25 06:04:54 +00:00
Refactor Redux integration demo layout
This commit is contained in:
@@ -1,39 +1,29 @@
|
||||
import { useEffect } from 'react'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { toast } from 'sonner'
|
||||
import {
|
||||
ArrowsClockwise,
|
||||
Database,
|
||||
CloudArrowUp,
|
||||
CloudArrowDown,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Clock,
|
||||
Trash,
|
||||
FilePlus
|
||||
} from '@phosphor-icons/react'
|
||||
import { useReduxFiles } from '@/hooks/use-redux-files'
|
||||
import { useReduxComponentTrees } from '@/hooks/use-redux-component-trees'
|
||||
import { useReduxSync } from '@/hooks/use-redux-sync'
|
||||
import { useAppSelector } from '@/store'
|
||||
import { ComponentTreesCard } from '@/components/redux-integration/ComponentTreesCard'
|
||||
import { DangerZoneCard } from '@/components/redux-integration/DangerZoneCard'
|
||||
import { FilesCard } from '@/components/redux-integration/FilesCard'
|
||||
import { ReduxIntegrationHeader } from '@/components/redux-integration/ReduxIntegrationHeader'
|
||||
import { StatusCardsSection } from '@/components/redux-integration/StatusCardsSection'
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
|
||||
export function ReduxIntegrationDemo() {
|
||||
const { files, load: loadFiles, save: saveFile, remove: removeFile } = useReduxFiles()
|
||||
const { trees, load: loadTrees } = useReduxComponentTrees()
|
||||
const {
|
||||
status,
|
||||
lastSyncedAt,
|
||||
flaskConnected,
|
||||
const {
|
||||
status,
|
||||
lastSyncedAt,
|
||||
flaskConnected,
|
||||
flaskStats,
|
||||
syncToFlask,
|
||||
syncToFlask,
|
||||
syncFromFlask,
|
||||
checkConnection,
|
||||
clearFlaskData
|
||||
clearFlaskData,
|
||||
} = useReduxSync()
|
||||
|
||||
const settings = useAppSelector((state) => state.settings.settings)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -51,284 +41,49 @@ export function ReduxIntegrationDemo() {
|
||||
updatedAt: Date.now(),
|
||||
}
|
||||
saveFile(newFile)
|
||||
toast.success('Test file created and saved to IndexedDB')
|
||||
toast.success(reduxIntegrationCopy.toast.createTestFile)
|
||||
}
|
||||
|
||||
const handleDeleteFile = (fileId: string) => {
|
||||
removeFile(fileId)
|
||||
toast.success('File deleted from IndexedDB')
|
||||
toast.success(reduxIntegrationCopy.toast.deleteFile)
|
||||
}
|
||||
|
||||
const handleSyncUp = () => {
|
||||
syncToFlask()
|
||||
toast.info('Syncing to Flask API...')
|
||||
toast.info(reduxIntegrationCopy.toast.syncUp)
|
||||
}
|
||||
|
||||
const handleSyncDown = () => {
|
||||
syncFromFlask()
|
||||
toast.info('Syncing from Flask API...')
|
||||
toast.info(reduxIntegrationCopy.toast.syncDown)
|
||||
}
|
||||
|
||||
const handleClearFlask = () => {
|
||||
clearFlaskData()
|
||||
toast.warning('Clearing Flask storage...')
|
||||
}
|
||||
|
||||
const getSyncStatusBadge = () => {
|
||||
switch (status) {
|
||||
case 'idle':
|
||||
return <Badge variant="outline">Idle</Badge>
|
||||
case 'syncing':
|
||||
return <Badge variant="secondary" className="animate-pulse">Syncing...</Badge>
|
||||
case 'success':
|
||||
return <Badge variant="default" className="bg-green-600"><CheckCircle className="w-3 h-3 mr-1" />Success</Badge>
|
||||
case 'error':
|
||||
return <Badge variant="destructive"><XCircle className="w-3 h-3 mr-1" />Error</Badge>
|
||||
}
|
||||
}
|
||||
|
||||
const getConnectionBadge = () => {
|
||||
return flaskConnected ? (
|
||||
<Badge variant="default" className="bg-green-600">
|
||||
<CheckCircle className="w-3 h-3 mr-1" />Connected
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="destructive">
|
||||
<XCircle className="w-3 h-3 mr-1" />Disconnected
|
||||
</Badge>
|
||||
)
|
||||
toast.warning(reduxIntegrationCopy.toast.clearFlask)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-auto p-6 space-y-6">
|
||||
<div className="max-w-7xl mx-auto">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold mb-2">Redux Integration Demo</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Comprehensive Redux Toolkit integration with IndexedDB and Flask API synchronization
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Database className="w-5 h-5" />
|
||||
IndexedDB Status
|
||||
</CardTitle>
|
||||
<CardDescription>Local browser storage</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Files</span>
|
||||
<Badge variant="outline">{files.length}</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Component Trees</span>
|
||||
<Badge variant="outline">{trees.length}</Badge>
|
||||
</div>
|
||||
<Separator />
|
||||
<Button
|
||||
onClick={handleCreateTestFile}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
>
|
||||
<FilePlus className="w-4 h-4 mr-2" />
|
||||
Create Test File
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CloudArrowUp className="w-5 h-5" />
|
||||
Flask API Status
|
||||
</CardTitle>
|
||||
<CardDescription>Remote server connection</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Connection</span>
|
||||
{getConnectionBadge()}
|
||||
</div>
|
||||
{flaskStats && (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Total Keys</span>
|
||||
<Badge variant="outline">{flaskStats.totalKeys}</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Storage Size</span>
|
||||
<Badge variant="outline">
|
||||
{(flaskStats.totalSizeBytes / 1024).toFixed(2)} KB
|
||||
</Badge>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Separator />
|
||||
<Button
|
||||
onClick={checkConnection}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
>
|
||||
<ArrowsClockwise className="w-4 h-4 mr-2" />
|
||||
Check Connection
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ArrowsClockwise className="w-5 h-5" />
|
||||
Sync Status
|
||||
</CardTitle>
|
||||
<CardDescription>Data synchronization</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Status</span>
|
||||
{getSyncStatusBadge()}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Auto Sync</span>
|
||||
<Badge variant={settings.autoSync ? "default" : "outline"}>
|
||||
{settings.autoSync ? 'Enabled' : 'Disabled'}
|
||||
</Badge>
|
||||
</div>
|
||||
{lastSyncedAt && (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">Last Sync</span>
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
{new Date(lastSyncedAt).toLocaleTimeString()}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
<Separator />
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleSyncUp}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
disabled={!flaskConnected || status === 'syncing'}
|
||||
>
|
||||
<CloudArrowUp className="w-4 h-4 mr-1" />
|
||||
Push
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSyncDown}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
disabled={!flaskConnected || status === 'syncing'}
|
||||
>
|
||||
<CloudArrowDown className="w-4 h-4 mr-1" />
|
||||
Pull
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Files in Redux Store</CardTitle>
|
||||
<CardDescription>
|
||||
Files managed by Redux and synced with IndexedDB/Flask
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{files.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<Database className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||
<p>No files yet. Create a test file to get started.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{files.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center justify-between p-3 border border-border rounded-md hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{file.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{file.path} • Updated {new Date(file.updatedAt).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteFile(file.id)}
|
||||
>
|
||||
<Trash className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Component Trees in Redux Store</CardTitle>
|
||||
<CardDescription>
|
||||
JSON component trees loaded from components.json
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{trees.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<Database className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||
<p>No component trees loaded yet.</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{trees.map((tree) => (
|
||||
<div
|
||||
key={tree.id}
|
||||
className="flex items-center justify-between p-3 border border-border rounded-md hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{tree.name}</div>
|
||||
{tree.description && (
|
||||
<div className="text-xs text-muted-foreground">{tree.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant="outline">
|
||||
{tree.root.type}
|
||||
</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="mt-6 border-destructive/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-destructive">Danger Zone</CardTitle>
|
||||
<CardDescription>
|
||||
Irreversible operations - use with caution
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleClearFlask}
|
||||
disabled={!flaskConnected}
|
||||
>
|
||||
<Trash className="w-4 h-4 mr-2" />
|
||||
Clear Flask Storage
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<ReduxIntegrationHeader />
|
||||
<StatusCardsSection
|
||||
filesCount={files.length}
|
||||
treesCount={trees.length}
|
||||
flaskConnected={flaskConnected}
|
||||
flaskStats={flaskStats}
|
||||
status={status}
|
||||
lastSyncedAt={lastSyncedAt}
|
||||
autoSyncEnabled={settings.autoSync}
|
||||
onCreateTestFile={handleCreateTestFile}
|
||||
onCheckConnection={checkConnection}
|
||||
onSyncUp={handleSyncUp}
|
||||
onSyncDown={handleSyncDown}
|
||||
/>
|
||||
<FilesCard files={files} onDeleteFile={handleDeleteFile} />
|
||||
<ComponentTreesCard trees={trees} />
|
||||
<DangerZoneCard flaskConnected={flaskConnected} onClearFlask={handleClearFlask} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
45
src/components/redux-integration/ComponentTreesCard.tsx
Normal file
45
src/components/redux-integration/ComponentTreesCard.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Database } from '@phosphor-icons/react'
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
import { ComponentTree } from '@/store/slices/componentTreesSlice'
|
||||
|
||||
type ComponentTreesCardProps = {
|
||||
trees: ComponentTree[]
|
||||
}
|
||||
|
||||
export function ComponentTreesCard({ trees }: ComponentTreesCardProps) {
|
||||
return (
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>{reduxIntegrationCopy.componentTrees.title}</CardTitle>
|
||||
<CardDescription>{reduxIntegrationCopy.componentTrees.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{trees.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<Database className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||
<p>{reduxIntegrationCopy.componentTrees.empty}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{trees.map((tree) => (
|
||||
<div
|
||||
key={tree.id}
|
||||
className="flex items-center justify-between p-3 border border-border rounded-md hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{tree.name}</div>
|
||||
{tree.description && (
|
||||
<div className="text-xs text-muted-foreground">{tree.description}</div>
|
||||
)}
|
||||
</div>
|
||||
<Badge variant="outline">{tree.root.type}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
26
src/components/redux-integration/DangerZoneCard.tsx
Normal file
26
src/components/redux-integration/DangerZoneCard.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Trash } from '@phosphor-icons/react'
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
|
||||
type DangerZoneCardProps = {
|
||||
flaskConnected: boolean
|
||||
onClearFlask: () => void
|
||||
}
|
||||
|
||||
export function DangerZoneCard({ flaskConnected, onClearFlask }: DangerZoneCardProps) {
|
||||
return (
|
||||
<Card className="mt-6 border-destructive/50">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-destructive">{reduxIntegrationCopy.danger.title}</CardTitle>
|
||||
<CardDescription>{reduxIntegrationCopy.danger.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Button variant="destructive" onClick={onClearFlask} disabled={!flaskConnected}>
|
||||
<Trash className="w-4 h-4 mr-2" />
|
||||
{reduxIntegrationCopy.danger.clearButton}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
50
src/components/redux-integration/FilesCard.tsx
Normal file
50
src/components/redux-integration/FilesCard.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Database, Trash } from '@phosphor-icons/react'
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
import { FileItem } from '@/store/slices/filesSlice'
|
||||
|
||||
type FilesCardProps = {
|
||||
files: FileItem[]
|
||||
onDeleteFile: (fileId: string) => void
|
||||
}
|
||||
|
||||
export function FilesCard({ files, onDeleteFile }: FilesCardProps) {
|
||||
return (
|
||||
<Card className="mt-6">
|
||||
<CardHeader>
|
||||
<CardTitle>{reduxIntegrationCopy.files.title}</CardTitle>
|
||||
<CardDescription>{reduxIntegrationCopy.files.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{files.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
<Database className="w-12 h-12 mx-auto mb-3 opacity-50" />
|
||||
<p>{reduxIntegrationCopy.files.empty}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{files.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center justify-between p-3 border border-border rounded-md hover:bg-muted/50 transition-colors"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<div className="font-medium">{file.name}</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{file.path} • {reduxIntegrationCopy.files.updatedLabel}{' '}
|
||||
{new Date(file.updatedAt).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => onDeleteFile(file.id)}>
|
||||
<Trash className="w-4 h-4 text-destructive" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
76
src/components/redux-integration/FlaskStatusCard.tsx
Normal file
76
src/components/redux-integration/FlaskStatusCard.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { ArrowsClockwise, CheckCircle, CloudArrowUp, XCircle } from '@phosphor-icons/react'
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
type FlaskStats = {
|
||||
totalKeys: number
|
||||
totalSizeBytes: number
|
||||
} | null
|
||||
|
||||
type FlaskStatusCardProps = {
|
||||
flaskConnected: boolean
|
||||
flaskStats: FlaskStats
|
||||
onCheckConnection: () => void
|
||||
}
|
||||
|
||||
export function FlaskStatusCard({ flaskConnected, flaskStats, onCheckConnection }: FlaskStatusCardProps) {
|
||||
const connectionLabel = flaskConnected
|
||||
? reduxIntegrationCopy.cards.flask.status.connected
|
||||
: reduxIntegrationCopy.cards.flask.status.disconnected
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<CloudArrowUp className="w-5 h-5" />
|
||||
{reduxIntegrationCopy.cards.flask.title}
|
||||
</CardTitle>
|
||||
<CardDescription>{reduxIntegrationCopy.cards.flask.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.flask.labels.connection}
|
||||
</span>
|
||||
{flaskConnected ? (
|
||||
<Badge variant="default" className="bg-green-600">
|
||||
<CheckCircle className="w-3 h-3 mr-1" />
|
||||
{connectionLabel}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="destructive">
|
||||
<XCircle className="w-3 h-3 mr-1" />
|
||||
{connectionLabel}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
{flaskStats && (
|
||||
<>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.flask.labels.totalKeys}
|
||||
</span>
|
||||
<Badge variant="outline">{flaskStats.totalKeys}</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.flask.labels.storageSize}
|
||||
</span>
|
||||
<Badge variant="outline">
|
||||
{(flaskStats.totalSizeBytes / 1024).toFixed(2)}{' '}
|
||||
{reduxIntegrationCopy.cards.flask.labels.storageUnit}
|
||||
</Badge>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<Separator />
|
||||
<Button onClick={onCheckConnection} variant="outline" size="sm" className="w-full">
|
||||
<ArrowsClockwise className="w-4 h-4 mr-2" />
|
||||
{reduxIntegrationCopy.cards.flask.labels.checkConnection}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
49
src/components/redux-integration/IndexedDbStatusCard.tsx
Normal file
49
src/components/redux-integration/IndexedDbStatusCard.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Database, FilePlus } from '@phosphor-icons/react'
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
|
||||
type IndexedDbStatusCardProps = {
|
||||
filesCount: number
|
||||
treesCount: number
|
||||
onCreateTestFile: () => void
|
||||
}
|
||||
|
||||
export function IndexedDbStatusCard({
|
||||
filesCount,
|
||||
treesCount,
|
||||
onCreateTestFile,
|
||||
}: IndexedDbStatusCardProps) {
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Database className="w-5 h-5" />
|
||||
{reduxIntegrationCopy.cards.indexedDb.title}
|
||||
</CardTitle>
|
||||
<CardDescription>{reduxIntegrationCopy.cards.indexedDb.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.indexedDb.labels.files}
|
||||
</span>
|
||||
<Badge variant="outline">{filesCount}</Badge>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.indexedDb.labels.componentTrees}
|
||||
</span>
|
||||
<Badge variant="outline">{treesCount}</Badge>
|
||||
</div>
|
||||
<Separator />
|
||||
<Button onClick={onCreateTestFile} variant="outline" size="sm" className="w-full">
|
||||
<FilePlus className="w-4 h-4 mr-2" />
|
||||
{reduxIntegrationCopy.cards.indexedDb.labels.createTestFile}
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
10
src/components/redux-integration/ReduxIntegrationHeader.tsx
Normal file
10
src/components/redux-integration/ReduxIntegrationHeader.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
|
||||
export function ReduxIntegrationHeader() {
|
||||
return (
|
||||
<div className="mb-6">
|
||||
<h1 className="text-3xl font-bold mb-2">{reduxIntegrationCopy.page.title}</h1>
|
||||
<p className="text-muted-foreground">{reduxIntegrationCopy.page.description}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
60
src/components/redux-integration/StatusCardsSection.tsx
Normal file
60
src/components/redux-integration/StatusCardsSection.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import { IndexedDbStatusCard } from '@/components/redux-integration/IndexedDbStatusCard'
|
||||
import { FlaskStatusCard } from '@/components/redux-integration/FlaskStatusCard'
|
||||
import { SyncStatusCard } from '@/components/redux-integration/SyncStatusCard'
|
||||
import { SyncStatus } from '@/store/slices/syncSlice'
|
||||
|
||||
type FlaskStats = {
|
||||
totalKeys: number
|
||||
totalSizeBytes: number
|
||||
} | null
|
||||
|
||||
type StatusCardsSectionProps = {
|
||||
filesCount: number
|
||||
treesCount: number
|
||||
flaskConnected: boolean
|
||||
flaskStats: FlaskStats
|
||||
status: SyncStatus
|
||||
lastSyncedAt: number | null
|
||||
autoSyncEnabled: boolean
|
||||
onCreateTestFile: () => void
|
||||
onCheckConnection: () => void
|
||||
onSyncUp: () => void
|
||||
onSyncDown: () => void
|
||||
}
|
||||
|
||||
export function StatusCardsSection({
|
||||
filesCount,
|
||||
treesCount,
|
||||
flaskConnected,
|
||||
flaskStats,
|
||||
status,
|
||||
lastSyncedAt,
|
||||
autoSyncEnabled,
|
||||
onCreateTestFile,
|
||||
onCheckConnection,
|
||||
onSyncUp,
|
||||
onSyncDown,
|
||||
}: StatusCardsSectionProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<IndexedDbStatusCard
|
||||
filesCount={filesCount}
|
||||
treesCount={treesCount}
|
||||
onCreateTestFile={onCreateTestFile}
|
||||
/>
|
||||
<FlaskStatusCard
|
||||
flaskConnected={flaskConnected}
|
||||
flaskStats={flaskStats}
|
||||
onCheckConnection={onCheckConnection}
|
||||
/>
|
||||
<SyncStatusCard
|
||||
status={status}
|
||||
lastSyncedAt={lastSyncedAt}
|
||||
autoSyncEnabled={autoSyncEnabled}
|
||||
flaskConnected={flaskConnected}
|
||||
onSyncUp={onSyncUp}
|
||||
onSyncDown={onSyncDown}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
116
src/components/redux-integration/SyncStatusCard.tsx
Normal file
116
src/components/redux-integration/SyncStatusCard.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { ArrowsClockwise, CheckCircle, Clock, CloudArrowDown, CloudArrowUp, XCircle } from '@phosphor-icons/react'
|
||||
import reduxIntegrationCopy from '@/data/redux-integration-demo.json'
|
||||
import { SyncStatus } from '@/store/slices/syncSlice'
|
||||
|
||||
type SyncStatusCardProps = {
|
||||
status: SyncStatus
|
||||
lastSyncedAt: number | null
|
||||
autoSyncEnabled: boolean
|
||||
flaskConnected: boolean
|
||||
onSyncUp: () => void
|
||||
onSyncDown: () => void
|
||||
}
|
||||
|
||||
export function SyncStatusCard({
|
||||
status,
|
||||
lastSyncedAt,
|
||||
autoSyncEnabled,
|
||||
flaskConnected,
|
||||
onSyncUp,
|
||||
onSyncDown,
|
||||
}: SyncStatusCardProps) {
|
||||
const getSyncStatusBadge = () => {
|
||||
switch (status) {
|
||||
case 'idle':
|
||||
return <Badge variant="outline">{reduxIntegrationCopy.cards.sync.status.idle}</Badge>
|
||||
case 'syncing':
|
||||
return (
|
||||
<Badge variant="secondary" className="animate-pulse">
|
||||
{reduxIntegrationCopy.cards.sync.status.syncing}
|
||||
</Badge>
|
||||
)
|
||||
case 'success':
|
||||
return (
|
||||
<Badge variant="default" className="bg-green-600">
|
||||
<CheckCircle className="w-3 h-3 mr-1" />
|
||||
{reduxIntegrationCopy.cards.sync.status.success}
|
||||
</Badge>
|
||||
)
|
||||
case 'error':
|
||||
return (
|
||||
<Badge variant="destructive">
|
||||
<XCircle className="w-3 h-3 mr-1" />
|
||||
{reduxIntegrationCopy.cards.sync.status.error}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ArrowsClockwise className="w-5 h-5" />
|
||||
{reduxIntegrationCopy.cards.sync.title}
|
||||
</CardTitle>
|
||||
<CardDescription>{reduxIntegrationCopy.cards.sync.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.sync.labels.status}
|
||||
</span>
|
||||
{getSyncStatusBadge()}
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.sync.labels.autoSync}
|
||||
</span>
|
||||
<Badge variant={autoSyncEnabled ? 'default' : 'outline'}>
|
||||
{autoSyncEnabled
|
||||
? reduxIntegrationCopy.cards.sync.autoSync.enabled
|
||||
: reduxIntegrationCopy.cards.sync.autoSync.disabled}
|
||||
</Badge>
|
||||
</div>
|
||||
{lastSyncedAt && (
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{reduxIntegrationCopy.cards.sync.labels.lastSync}
|
||||
</span>
|
||||
<Badge variant="outline" className="flex items-center gap-1">
|
||||
<Clock className="w-3 h-3" />
|
||||
{new Date(lastSyncedAt).toLocaleTimeString()}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
<Separator />
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={onSyncUp}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
disabled={!flaskConnected || status === 'syncing'}
|
||||
>
|
||||
<CloudArrowUp className="w-4 h-4 mr-1" />
|
||||
{reduxIntegrationCopy.cards.sync.labels.push}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onSyncDown}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
disabled={!flaskConnected || status === 'syncing'}
|
||||
>
|
||||
<CloudArrowDown className="w-4 h-4 mr-1" />
|
||||
{reduxIntegrationCopy.cards.sync.labels.pull}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
76
src/data/redux-integration-demo.json
Normal file
76
src/data/redux-integration-demo.json
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"page": {
|
||||
"title": "Redux Integration Demo",
|
||||
"description": "Comprehensive Redux Toolkit integration with IndexedDB and Flask API synchronization"
|
||||
},
|
||||
"cards": {
|
||||
"indexedDb": {
|
||||
"title": "IndexedDB Status",
|
||||
"description": "Local browser storage",
|
||||
"labels": {
|
||||
"files": "Files",
|
||||
"componentTrees": "Component Trees",
|
||||
"createTestFile": "Create Test File"
|
||||
}
|
||||
},
|
||||
"flask": {
|
||||
"title": "Flask API Status",
|
||||
"description": "Remote server connection",
|
||||
"labels": {
|
||||
"connection": "Connection",
|
||||
"totalKeys": "Total Keys",
|
||||
"storageSize": "Storage Size",
|
||||
"storageUnit": "KB",
|
||||
"checkConnection": "Check Connection"
|
||||
},
|
||||
"status": {
|
||||
"connected": "Connected",
|
||||
"disconnected": "Disconnected"
|
||||
}
|
||||
},
|
||||
"sync": {
|
||||
"title": "Sync Status",
|
||||
"description": "Data synchronization",
|
||||
"labels": {
|
||||
"status": "Status",
|
||||
"autoSync": "Auto Sync",
|
||||
"lastSync": "Last Sync",
|
||||
"push": "Push",
|
||||
"pull": "Pull"
|
||||
},
|
||||
"status": {
|
||||
"idle": "Idle",
|
||||
"syncing": "Syncing...",
|
||||
"success": "Success",
|
||||
"error": "Error"
|
||||
},
|
||||
"autoSync": {
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"title": "Files in Redux Store",
|
||||
"description": "Files managed by Redux and synced with IndexedDB/Flask",
|
||||
"empty": "No files yet. Create a test file to get started.",
|
||||
"updatedLabel": "Updated"
|
||||
},
|
||||
"componentTrees": {
|
||||
"title": "Component Trees in Redux Store",
|
||||
"description": "JSON component trees loaded from components.json",
|
||||
"empty": "No component trees loaded yet."
|
||||
},
|
||||
"danger": {
|
||||
"title": "Danger Zone",
|
||||
"description": "Irreversible operations - use with caution",
|
||||
"clearButton": "Clear Flask Storage"
|
||||
},
|
||||
"toast": {
|
||||
"createTestFile": "Test file created and saved to IndexedDB",
|
||||
"deleteFile": "File deleted from IndexedDB",
|
||||
"syncUp": "Syncing to Flask API...",
|
||||
"syncDown": "Syncing from Flask API...",
|
||||
"clearFlask": "Clearing Flask storage..."
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user