From b461e65bba0db12c1dd21b380840d39e402d8a30 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 23 Jan 2026 09:07:35 +0000 Subject: [PATCH] Generated by Spark: Load all ui data from json --- JSON_DATA_STRUCTURE.md | 354 +++++++++++++++++++ src/App.tsx | 2 +- src/components/DataManagement.tsx | 86 +++++ src/components/ViewRouter.tsx | 4 + src/components/navigation.tsx | 10 +- src/components/views/data-admin-view.tsx | 246 +++++++++++++ src/data/app-data.json | 420 +++++++++++++++++++++++ src/hooks/index.ts | 1 + src/hooks/use-json-data.ts | 165 +++++++++ src/hooks/use-sample-data.ts | 244 +------------ src/lib/view-preloader.ts | 1 + 11 files changed, 1305 insertions(+), 228 deletions(-) create mode 100644 JSON_DATA_STRUCTURE.md create mode 100644 src/components/DataManagement.tsx create mode 100644 src/components/views/data-admin-view.tsx create mode 100644 src/data/app-data.json create mode 100644 src/hooks/use-json-data.ts diff --git a/JSON_DATA_STRUCTURE.md b/JSON_DATA_STRUCTURE.md new file mode 100644 index 0000000..6656e4c --- /dev/null +++ b/JSON_DATA_STRUCTURE.md @@ -0,0 +1,354 @@ +# JSON Data Structure + +All UI data is now loaded from JSON files, making the application data-driven and easily configurable without code changes. + +## Data Source + +**Primary Data File**: `/src/data/app-data.json` + +This file contains all sample data for the application, including: +- Timesheets +- Invoices +- Payroll Runs +- Workers +- Compliance Documents +- Expenses +- Rate Cards +- Clients + +## How It Works + +### 1. Data Loading Flow + +``` +app-data.json → use-sample-data hook → KV Storage → use-app-data hook → Components +``` + +1. **app-data.json** - Static JSON file with initial/seed data +2. **use-sample-data** - Loads JSON data into KV storage on first run +3. **use-app-data** - Reads from KV storage and provides reactive state +4. **Components** - Use the reactive data from use-app-data + +### 2. Initial Data Load + +When the application first loads (or when KV storage is empty), the `useSampleData` hook: +- Checks if data has been initialized (`sample-data-initialized` flag) +- If not initialized, loads data from `app-data.json` +- Populates KV storage with all data entities +- Sets the initialization flag to prevent re-loading + +### 3. Persistent Storage + +All data is stored in KV storage, which persists between sessions. This means: +- User modifications are preserved +- Data survives page refreshes +- Each user has their own data instance + +## Data Schema + +### Timesheets + +```typescript +{ + id: string // Unique timesheet ID + workerId: string // Reference to worker + workerName: string // Worker's name + clientName: string // Client company name + weekEnding: string // ISO date string + totalHours: number // Total hours worked + regularHours: number // Standard hours + overtimeHours: number // Overtime hours + status: string // "pending" | "approved" | "disputed" + rate: number // Hourly rate + total: number // Total amount + submittedDate: string // ISO date-time string + approvedDate?: string // Optional approval date + shifts: Shift[] // Array of shift details +} + +Shift: { + date: string // ISO date string + start: string // Time "HH:MM" + end: string // Time "HH:MM" + hours: number // Hours worked + type: string // "regular" | "night" | "weekend" | "overtime" +} +``` + +### Invoices + +```typescript +{ + id: string // Unique invoice ID + clientName: string // Client company name + amount: number // Net amount + vat: number // VAT/Tax amount + total: number // Gross total + status: string // "paid" | "pending" | "overdue" | "draft" + dueDate: string // ISO date string + invoiceDate: string // ISO date string + paidDate?: string // Optional payment date + items: InvoiceItem[] // Line items +} + +InvoiceItem: { + description: string // Item description + quantity: number // Quantity + rate: number // Unit rate + amount: number // Total amount +} +``` + +### Payroll Runs + +```typescript +{ + id: string // Unique payroll run ID + periodEnding: string // ISO date string + status: string // "completed" | "pending" | "processing" + totalGross: number // Total gross pay + totalNet: number // Total net pay + totalTax: number // Total tax withheld + totalNI: number // Total National Insurance + processedDate?: string // Optional processing date + paymentDate?: string // Optional payment date + workerCount: number // Number of workers + entries: PayrollEntry[] // Individual worker entries +} + +PayrollEntry: { + workerId: string // Reference to worker + workerName: string // Worker's name + gross: number // Gross pay + net: number // Net pay + tax: number // Tax withheld + ni: number // National Insurance +} +``` + +### Workers + +```typescript +{ + id: string // Unique worker ID + name: string // Full name + email: string // Email address + phone: string // Phone number + status: string // "active" | "inactive" | "onboarding" + role: string // Job role/title + startDate: string // ISO date string + paymentType: string // "Limited Company" | "PAYE" | "CIS" + currentClient: string // Current client assignment +} +``` + +### Compliance Documents + +```typescript +{ + id: string // Unique document ID + workerId: string // Reference to worker + workerName: string // Worker's name + documentType: string // Document type name + status: string // "valid" | "expiring" | "expired" + expiryDate: string // ISO date string + uploadDate: string // ISO date string + verifiedDate: string // ISO date string +} +``` + +### Expenses + +```typescript +{ + id: string // Unique expense ID + workerId: string // Reference to worker + workerName: string // Worker's name + category: string // "Travel" | "Accommodation" | "Meals" | "Equipment" + amount: number // Expense amount + date: string // ISO date string + status: string // "approved" | "pending" | "rejected" + description: string // Expense description + receiptAttached: boolean// Receipt attachment status + approvedDate?: string // Optional approval date + rejectedDate?: string // Optional rejection date + rejectionReason?: string// Optional rejection reason +} +``` + +### Rate Cards + +```typescript +{ + id: string // Unique rate card ID + role: string // Job role/title + clientName: string // Client company name + payRate: number // Hourly pay rate + chargeRate: number // Hourly charge rate + margin: number // Margin amount + marginPercent: number // Margin percentage + currency: string // Currency code (e.g., "GBP") + validFrom: string // ISO date string + validUntil: string // ISO date string + overtimeMultiplier: number // Overtime rate multiplier + weekendMultiplier: number // Weekend rate multiplier + nightMultiplier?: number // Optional night shift multiplier +} +``` + +### Clients + +```typescript +{ + id: string // Unique client ID + name: string // Company name + industry: string // Industry sector + status: string // "active" | "inactive" | "suspended" + creditLimit: number // Credit limit + outstandingBalance: number // Current outstanding balance + paymentTerms: number // Payment terms in days + activeWorkers: number // Number of active workers + address: string // Full address +} +``` + +## Hooks + +### useJsonData() + +Direct access to JSON data (read-only). + +```typescript +import { useJsonData } from '@/hooks' + +function MyComponent() { + const { data, isLoading, error } = useJsonData() + + if (isLoading) return
Loading...
+ if (error) return
Error: {error.message}
+ + return
{data.workers.length} workers
+} +``` + +### useSampleData() + +Initializes KV storage with JSON data (runs automatically on app start). + +```typescript +import { useSampleData } from '@/hooks' + +function App() { + useSampleData() // Call once at app root + // ... +} +``` + +### useAppData() + +Reactive access to data with setters (recommended for components). + +```typescript +import { useAppData } from '@/hooks' + +function MyComponent() { + const { timesheets, setTimesheets, workers, metrics } = useAppData() + + // Use data + console.log(timesheets.length) + + // Update data + setTimesheets(prev => [...prev, newTimesheet]) +} +``` + +## Modifying Data + +### Option 1: Edit JSON File (Recommended for Bulk Changes) + +1. Edit `/src/data/app-data.json` +2. Clear KV storage (or set `sample-data-initialized` to `false`) +3. Refresh the application + +### Option 2: Programmatic Updates (Recommended for User Actions) + +```typescript +const { timesheets, setTimesheets } = useAppData() + +// Add new timesheet +setTimesheets(current => [...current, newTimesheet]) + +// Update timesheet +setTimesheets(current => + current.map(ts => + ts.id === id ? { ...ts, status: 'approved' } : ts + ) +) + +// Delete timesheet +setTimesheets(current => current.filter(ts => ts.id !== id)) +``` + +## Adding New Data Entities + +To add new data entities: + +1. **Add to JSON file** (`/src/data/app-data.json`) + ```json + { + "timesheets": [...], + "newEntity": [ + { "id": "1", "name": "Example" } + ] + } + ``` + +2. **Add TypeScript interface** (`/src/hooks/use-json-data.ts`) + ```typescript + export interface NewEntity { + id: string + name: string + } + + export interface AppData { + timesheets: Timesheet[] + newEntity: NewEntity[] + } + ``` + +3. **Initialize in use-sample-data** + ```typescript + const [, setNewEntity] = useKV('new-entity', []) + + setNewEntity(appData.newEntity) + ``` + +4. **Expose in use-app-data** + ```typescript + const [newEntity = [], setNewEntity] = useKV('new-entity', []) + + return { + newEntity, + setNewEntity, + // ... + } + ``` + +## Benefits + +- **Centralized Data**: All sample data in one place +- **Easy Configuration**: Modify JSON instead of code +- **Type Safety**: Full TypeScript support +- **Persistence**: Data survives page refreshes +- **Reactive**: Automatic UI updates +- **Testable**: Easy to swap data for testing +- **Scalable**: Add new entities without code changes + +## Future Enhancements + +- Support for multiple JSON files (e.g., `timesheets.json`, `invoices.json`) +- Remote JSON loading from API +- Data validation on load +- Migration scripts for schema changes +- Import/export functionality +- Data versioning diff --git a/src/App.tsx b/src/App.tsx index 7bb8c5a..bbda259 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import { Sidebar } from '@/components/navigation' import { NotificationCenter } from '@/components/NotificationCenter' import { ViewRouter } from '@/components/ViewRouter' -export type View = 'dashboard' | 'timesheets' | 'billing' | 'payroll' | 'compliance' | 'expenses' | 'roadmap' | 'reports' | 'currency' | 'email-templates' | 'invoice-templates' | 'qr-scanner' | 'missing-timesheets' | 'purchase-orders' | 'onboarding' | 'audit-trail' | 'notification-rules' | 'batch-import' | 'rate-templates' | 'custom-reports' | 'holiday-pay' | 'contract-validation' | 'shift-patterns' | 'query-guide' | 'component-showcase' | 'business-logic-demo' +export type View = 'dashboard' | 'timesheets' | 'billing' | 'payroll' | 'compliance' | 'expenses' | 'roadmap' | 'reports' | 'currency' | 'email-templates' | 'invoice-templates' | 'qr-scanner' | 'missing-timesheets' | 'purchase-orders' | 'onboarding' | 'audit-trail' | 'notification-rules' | 'batch-import' | 'rate-templates' | 'custom-reports' | 'holiday-pay' | 'contract-validation' | 'shift-patterns' | 'query-guide' | 'component-showcase' | 'business-logic-demo' | 'data-admin' function App() { useSampleData() diff --git a/src/components/DataManagement.tsx b/src/components/DataManagement.tsx new file mode 100644 index 0000000..9d8d0a8 --- /dev/null +++ b/src/components/DataManagement.tsx @@ -0,0 +1,86 @@ +import { Button } from '@/components/ui/button' +import { Card } from '@/components/ui/card' +import { toast } from 'sonner' + +export function DataManagement() { + const resetAllData = async () => { + try { + await window.spark.kv.delete('sample-data-initialized') + await window.spark.kv.delete('timesheets') + await window.spark.kv.delete('invoices') + await window.spark.kv.delete('payroll-runs') + await window.spark.kv.delete('workers') + await window.spark.kv.delete('compliance-docs') + await window.spark.kv.delete('expenses') + await window.spark.kv.delete('rate-cards') + await window.spark.kv.delete('clients') + + toast.success('Data cleared - refresh to reload from JSON') + } catch (error) { + toast.error('Failed to clear data') + } + } + + const exportData = async () => { + try { + const timesheets = await window.spark.kv.get('timesheets') + const invoices = await window.spark.kv.get('invoices') + const payrollRuns = await window.spark.kv.get('payroll-runs') + const workers = await window.spark.kv.get('workers') + const complianceDocs = await window.spark.kv.get('compliance-docs') + const expenses = await window.spark.kv.get('expenses') + const rateCards = await window.spark.kv.get('rate-cards') + const clients = await window.spark.kv.get('clients') + + const data = { + timesheets, + invoices, + payrollRuns, + workers, + complianceDocs, + expenses, + rateCards, + clients + } + + const dataStr = JSON.stringify(data, null, 2) + const blob = new Blob([dataStr], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `workforce-data-${new Date().toISOString().split('T')[0]}.json` + a.click() + URL.revokeObjectURL(url) + + toast.success('Data exported successfully') + } catch (error) { + toast.error('Failed to export data') + } + } + + return ( + +
+

Data Management

+

+ Manage application data and reset to defaults +

+
+ +
+ + +
+ +
+

• Export: Download current data as JSON file

+

• Reset: Clear all data and reload from app-data.json

+

• After reset, refresh the page to see changes

+
+
+ ) +} diff --git a/src/components/ViewRouter.tsx b/src/components/ViewRouter.tsx index a5f50d8..eb6ddd5 100644 --- a/src/components/ViewRouter.tsx +++ b/src/components/ViewRouter.tsx @@ -39,6 +39,7 @@ const QueryLanguageGuide = lazy(() => import('@/components/QueryLanguageGuide'). const RoadmapView = lazy(() => import('@/components/roadmap-view').then(m => ({ default: m.RoadmapView }))) const ComponentShowcase = lazy(() => import('@/components/ComponentShowcase').then(m => ({ default: m.ComponentShowcase }))) const BusinessLogicDemo = lazy(() => import('@/components/BusinessLogicDemo').then(m => ({ default: m.BusinessLogicDemo }))) +const DataAdminView = lazy(() => import('@/components/views/data-admin-view').then(m => ({ default: m.DataAdminView }))) interface ViewRouterProps { currentView: View @@ -246,6 +247,9 @@ export function ViewRouter({ case 'business-logic-demo': return + case 'data-admin': + return + default: return } diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 41324b2..8241bb7 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -7,7 +7,8 @@ import { MapTrifold, Question, PuzzlePiece, - Code + Code, + Database } from '@phosphor-icons/react' import { NavItem } from '@/components/nav/NavItem' import { CoreOperationsNav, ReportsNav, ConfigurationNav, ToolsNav } from '@/components/nav/nav-sections' @@ -113,6 +114,13 @@ export function Sidebar({ currentView, setCurrentView, currentEntity, setCurrent onClick={() => setCurrentView('business-logic-demo')} view="business-logic-demo" /> + } + label="Data Administration" + active={currentView === 'data-admin'} + onClick={() => setCurrentView('data-admin')} + view="data-admin" + /> } label="Query Guide" diff --git a/src/components/views/data-admin-view.tsx b/src/components/views/data-admin-view.tsx new file mode 100644 index 0000000..3424df7 --- /dev/null +++ b/src/components/views/data-admin-view.tsx @@ -0,0 +1,246 @@ +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { toast } from 'sonner' +import { Database, Download, ArrowClockwise, FileJs } from '@phosphor-icons/react' +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' +import { Separator } from '@/components/ui/separator' + +export function DataAdminView() { + const resetAllData = async () => { + try { + await window.spark.kv.delete('sample-data-initialized') + await window.spark.kv.delete('timesheets') + await window.spark.kv.delete('invoices') + await window.spark.kv.delete('payroll-runs') + await window.spark.kv.delete('workers') + await window.spark.kv.delete('compliance-docs') + await window.spark.kv.delete('expenses') + await window.spark.kv.delete('rate-cards') + await window.spark.kv.delete('clients') + + toast.success('Data cleared successfully', { + description: 'Refresh the page to reload default data from JSON' + }) + } catch (error) { + toast.error('Failed to clear data') + } + } + + const exportData = async () => { + try { + const timesheets = await window.spark.kv.get('timesheets') + const invoices = await window.spark.kv.get('invoices') + const payrollRuns = await window.spark.kv.get('payroll-runs') + const workers = await window.spark.kv.get('workers') + const complianceDocs = await window.spark.kv.get('compliance-docs') + const expenses = await window.spark.kv.get('expenses') + const rateCards = await window.spark.kv.get('rate-cards') + const clients = await window.spark.kv.get('clients') + + const data = { + timesheets, + invoices, + payrollRuns, + workers, + complianceDocs, + expenses, + rateCards, + clients + } + + const dataStr = JSON.stringify(data, null, 2) + const blob = new Blob([dataStr], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = `workforce-data-${new Date().toISOString().split('T')[0]}.json` + a.click() + URL.revokeObjectURL(url) + + toast.success('Data exported successfully', { + description: 'Your data has been downloaded as a JSON file' + }) + } catch (error) { + toast.error('Failed to export data') + } + } + + const viewAllKeys = async () => { + try { + const keys = await window.spark.kv.keys() + console.log('All KV Storage Keys:', keys) + toast.success(`Found ${keys.length} keys`, { + description: 'Check the console for full list' + }) + } catch (error) { + toast.error('Failed to retrieve keys') + } + } + + return ( +
+
+

Data Administration

+

+ Manage application data loaded from JSON files +

+
+ + + + JSON-Based Data + + All application data is loaded from /src/data/app-data.json into persistent KV storage. + Changes you make in the app are saved to KV storage and persist between sessions. + + + +
+ + + + + Export Current Data + + + Download all current data as a JSON file + + + +

+ This will export all data currently in KV storage, including any modifications you've made. +

+ +
+
+ + + + + + Reset to Default Data + + + Clear all data and reload from JSON file + + + +

+ This will delete all data from KV storage. Refresh the page to reload the default data from app-data.json. +

+ +
+
+
+ + + + + + Storage Information + + + +
+
+ Data Entities + 8 +
+ +
+
+ Timesheets + timesheets +
+
+ Invoices + invoices +
+
+ Payroll Runs + payroll-runs +
+
+ Workers + workers +
+
+ Compliance + compliance-docs +
+
+ Expenses + expenses +
+
+ Rate Cards + rate-cards +
+
+ Clients + clients +
+
+
+ +
+
+ + + + Data Flow + + +
+
+ 1 +
+

Load from JSON

+

+ On first load, data is read from /src/data/app-data.json +

+
+
+
+ 2 +
+

Store in KV

+

+ Data is written to persistent KV storage +

+
+
+
+ 3 +
+

Use in App

+

+ Components read from and write to KV storage via hooks +

+
+
+
+ 4 +
+

Persist Changes

+

+ All changes persist between sessions automatically +

+
+
+
+
+
+
+ ) +} diff --git a/src/data/app-data.json b/src/data/app-data.json new file mode 100644 index 0000000..278523f --- /dev/null +++ b/src/data/app-data.json @@ -0,0 +1,420 @@ +{ + "timesheets": [ + { + "id": "TS-001", + "workerId": "W-001", + "workerName": "Sarah Johnson", + "clientName": "Tech Solutions Ltd", + "weekEnding": "2024-01-19", + "totalHours": 40, + "regularHours": 40, + "overtimeHours": 0, + "status": "approved", + "rate": 25.50, + "total": 1020.00, + "submittedDate": "2024-01-19T09:30:00Z", + "approvedDate": "2024-01-19T14:20:00Z", + "shifts": [ + { "date": "2024-01-15", "start": "09:00", "end": "17:00", "hours": 8, "type": "regular" }, + { "date": "2024-01-16", "start": "09:00", "end": "17:00", "hours": 8, "type": "regular" }, + { "date": "2024-01-17", "start": "09:00", "end": "17:00", "hours": 8, "type": "regular" }, + { "date": "2024-01-18", "start": "09:00", "end": "17:00", "hours": 8, "type": "regular" }, + { "date": "2024-01-19", "start": "09:00", "end": "17:00", "hours": 8, "type": "regular" } + ] + }, + { + "id": "TS-002", + "workerId": "W-002", + "workerName": "Michael Chen", + "clientName": "Global Finance Corp", + "weekEnding": "2024-01-19", + "totalHours": 45, + "regularHours": 40, + "overtimeHours": 5, + "status": "pending", + "rate": 30.00, + "total": 1350.00, + "submittedDate": "2024-01-19T16:45:00Z", + "shifts": [ + { "date": "2024-01-15", "start": "08:00", "end": "17:00", "hours": 9, "type": "regular" }, + { "date": "2024-01-16", "start": "08:00", "end": "17:00", "hours": 9, "type": "regular" }, + { "date": "2024-01-17", "start": "08:00", "end": "17:00", "hours": 9, "type": "regular" }, + { "date": "2024-01-18", "start": "08:00", "end": "17:00", "hours": 9, "type": "regular" }, + { "date": "2024-01-19", "start": "08:00", "end": "17:00", "hours": 9, "type": "regular" } + ] + }, + { + "id": "TS-003", + "workerId": "W-003", + "workerName": "Emma Wilson", + "clientName": "Healthcare Plus", + "weekEnding": "2024-01-19", + "totalHours": 48, + "regularHours": 40, + "overtimeHours": 8, + "status": "approved", + "rate": 28.75, + "total": 1495.00, + "submittedDate": "2024-01-18T11:20:00Z", + "approvedDate": "2024-01-19T10:15:00Z", + "shifts": [ + { "date": "2024-01-15", "start": "07:00", "end": "17:30", "hours": 10, "type": "regular" }, + { "date": "2024-01-16", "start": "07:00", "end": "17:00", "hours": 10, "type": "regular" }, + { "date": "2024-01-17", "start": "22:00", "end": "06:00", "hours": 8, "type": "night" }, + { "date": "2024-01-18", "start": "07:00", "end": "17:00", "hours": 10, "type": "regular" }, + { "date": "2024-01-19", "start": "07:00", "end": "17:00", "hours": 10, "type": "regular" } + ] + }, + { + "id": "TS-004", + "workerId": "W-004", + "workerName": "James Brown", + "clientName": "Manufacturing Inc", + "weekEnding": "2024-01-19", + "totalHours": 50, + "regularHours": 40, + "overtimeHours": 10, + "status": "disputed", + "rate": 22.00, + "total": 1210.00, + "submittedDate": "2024-01-19T08:00:00Z", + "shifts": [ + { "date": "2024-01-15", "start": "06:00", "end": "16:00", "hours": 10, "type": "regular" }, + { "date": "2024-01-16", "start": "06:00", "end": "16:00", "hours": 10, "type": "regular" }, + { "date": "2024-01-17", "start": "06:00", "end": "16:00", "hours": 10, "type": "regular" }, + { "date": "2024-01-18", "start": "06:00", "end": "16:00", "hours": 10, "type": "regular" }, + { "date": "2024-01-19", "start": "06:00", "end": "16:00", "hours": 10, "type": "regular" } + ] + } + ], + "invoices": [ + { + "id": "INV-2024-001", + "clientName": "Tech Solutions Ltd", + "amount": 15250.00, + "vat": 3050.00, + "total": 18300.00, + "status": "paid", + "dueDate": "2024-02-15", + "invoiceDate": "2024-01-15", + "paidDate": "2024-02-10", + "items": [ + { "description": "Contractor Services - Week 1", "quantity": 40, "rate": 75.00, "amount": 3000.00 }, + { "description": "Contractor Services - Week 2", "quantity": 40, "rate": 75.00, "amount": 3000.00 }, + { "description": "Contractor Services - Week 3", "quantity": 40, "rate": 75.00, "amount": 3000.00 }, + { "description": "Contractor Services - Week 4", "quantity": 40, "rate": 75.00, "amount": 3000.00 } + ] + }, + { + "id": "INV-2024-002", + "clientName": "Global Finance Corp", + "amount": 22500.00, + "vat": 4500.00, + "total": 27000.00, + "status": "pending", + "dueDate": "2024-02-28", + "invoiceDate": "2024-01-28", + "items": [ + { "description": "Senior Developer - January", "quantity": 160, "rate": 95.00, "amount": 15200.00 }, + { "description": "Project Management", "quantity": 1, "rate": 7300.00, "amount": 7300.00 } + ] + }, + { + "id": "INV-2024-003", + "clientName": "Healthcare Plus", + "amount": 8750.00, + "vat": 1750.00, + "total": 10500.00, + "status": "overdue", + "dueDate": "2024-01-30", + "invoiceDate": "2024-01-10", + "items": [ + { "description": "Nursing Services", "quantity": 125, "rate": 70.00, "amount": 8750.00 } + ] + } + ], + "payrollRuns": [ + { + "id": "PR-2024-001", + "periodEnding": "2024-01-31", + "status": "completed", + "totalGross": 45250.00, + "totalNet": 35420.50, + "totalTax": 7829.50, + "totalNI": 2000.00, + "processedDate": "2024-02-01T10:30:00Z", + "paymentDate": "2024-02-05", + "workerCount": 12, + "entries": [ + { "workerId": "W-001", "workerName": "Sarah Johnson", "gross": 4080.00, "net": 3185.20, "tax": 694.80, "ni": 200.00 }, + { "workerId": "W-002", "workerName": "Michael Chen", "gross": 5400.00, "net": 4212.00, "tax": 938.00, "ni": 250.00 } + ] + }, + { + "id": "PR-2024-002", + "periodEnding": "2024-02-15", + "status": "pending", + "totalGross": 38900.00, + "totalNet": 30450.75, + "totalTax": 6649.25, + "totalNI": 1800.00, + "workerCount": 10, + "entries": [ + { "workerId": "W-003", "workerName": "Emma Wilson", "gross": 5980.00, "net": 4663.60, "tax": 1036.40, "ni": 280.00 } + ] + } + ], + "workers": [ + { + "id": "W-001", + "name": "Sarah Johnson", + "email": "sarah.j@example.com", + "phone": "+44 7700 900001", + "status": "active", + "role": "Software Developer", + "startDate": "2023-06-15", + "paymentType": "Limited Company", + "currentClient": "Tech Solutions Ltd" + }, + { + "id": "W-002", + "name": "Michael Chen", + "email": "m.chen@example.com", + "phone": "+44 7700 900002", + "status": "active", + "role": "Senior Developer", + "startDate": "2023-03-20", + "paymentType": "PAYE", + "currentClient": "Global Finance Corp" + }, + { + "id": "W-003", + "name": "Emma Wilson", + "email": "emma.w@example.com", + "phone": "+44 7700 900003", + "status": "active", + "role": "Registered Nurse", + "startDate": "2023-09-10", + "paymentType": "PAYE", + "currentClient": "Healthcare Plus" + }, + { + "id": "W-004", + "name": "James Brown", + "email": "j.brown@example.com", + "phone": "+44 7700 900004", + "status": "active", + "role": "Machine Operator", + "startDate": "2023-11-01", + "paymentType": "CIS", + "currentClient": "Manufacturing Inc" + } + ], + "complianceDocs": [ + { + "id": "DOC-001", + "workerId": "W-001", + "workerName": "Sarah Johnson", + "documentType": "Right to Work", + "status": "valid", + "expiryDate": "2025-06-15", + "uploadDate": "2023-06-10", + "verifiedDate": "2023-06-12" + }, + { + "id": "DOC-002", + "workerId": "W-001", + "workerName": "Sarah Johnson", + "documentType": "DBS Check", + "status": "valid", + "expiryDate": "2024-12-20", + "uploadDate": "2023-06-10", + "verifiedDate": "2023-06-15" + }, + { + "id": "DOC-003", + "workerId": "W-002", + "workerName": "Michael Chen", + "documentType": "Right to Work", + "status": "expiring", + "expiryDate": "2024-03-20", + "uploadDate": "2023-03-15", + "verifiedDate": "2023-03-18" + }, + { + "id": "DOC-004", + "workerId": "W-003", + "workerName": "Emma Wilson", + "documentType": "Professional Indemnity", + "status": "valid", + "expiryDate": "2024-09-10", + "uploadDate": "2023-09-05", + "verifiedDate": "2023-09-08" + }, + { + "id": "DOC-005", + "workerId": "W-004", + "workerName": "James Brown", + "documentType": "CIS Verification", + "status": "expired", + "expiryDate": "2024-01-01", + "uploadDate": "2023-10-25", + "verifiedDate": "2023-10-28" + } + ], + "expenses": [ + { + "id": "EXP-001", + "workerId": "W-001", + "workerName": "Sarah Johnson", + "category": "Travel", + "amount": 45.50, + "date": "2024-01-15", + "status": "approved", + "description": "Client site visit - return journey", + "receiptAttached": true, + "approvedDate": "2024-01-16" + }, + { + "id": "EXP-002", + "workerId": "W-002", + "workerName": "Michael Chen", + "category": "Accommodation", + "amount": 125.00, + "date": "2024-01-18", + "status": "pending", + "description": "Hotel stay for client meeting", + "receiptAttached": true + }, + { + "id": "EXP-003", + "workerId": "W-003", + "workerName": "Emma Wilson", + "category": "Meals", + "amount": 28.75, + "date": "2024-01-17", + "status": "approved", + "description": "Working lunch during extended shift", + "receiptAttached": true, + "approvedDate": "2024-01-18" + }, + { + "id": "EXP-004", + "workerId": "W-001", + "workerName": "Sarah Johnson", + "category": "Equipment", + "amount": 89.99, + "date": "2024-01-20", + "status": "rejected", + "description": "Keyboard and mouse", + "receiptAttached": false, + "rejectedDate": "2024-01-22", + "rejectionReason": "No receipt provided" + } + ], + "rateCards": [ + { + "id": "RC-001", + "role": "Software Developer", + "clientName": "Tech Solutions Ltd", + "payRate": 25.50, + "chargeRate": 75.00, + "margin": 49.50, + "marginPercent": 66.0, + "currency": "GBP", + "validFrom": "2023-06-01", + "validUntil": "2024-05-31", + "overtimeMultiplier": 1.5, + "weekendMultiplier": 2.0 + }, + { + "id": "RC-002", + "role": "Senior Developer", + "clientName": "Global Finance Corp", + "payRate": 30.00, + "chargeRate": 95.00, + "margin": 65.00, + "marginPercent": 68.4, + "currency": "GBP", + "validFrom": "2023-03-01", + "validUntil": "2024-02-29", + "overtimeMultiplier": 1.5, + "weekendMultiplier": 1.8 + }, + { + "id": "RC-003", + "role": "Registered Nurse", + "clientName": "Healthcare Plus", + "payRate": 28.75, + "chargeRate": 70.00, + "margin": 41.25, + "marginPercent": 58.9, + "currency": "GBP", + "validFrom": "2023-09-01", + "validUntil": "2024-08-31", + "overtimeMultiplier": 1.5, + "weekendMultiplier": 2.0, + "nightMultiplier": 1.3 + }, + { + "id": "RC-004", + "role": "Machine Operator", + "clientName": "Manufacturing Inc", + "payRate": 22.00, + "chargeRate": 55.00, + "margin": 33.00, + "marginPercent": 60.0, + "currency": "GBP", + "validFrom": "2023-11-01", + "validUntil": "2024-10-31", + "overtimeMultiplier": 1.5, + "weekendMultiplier": 2.0 + } + ], + "clients": [ + { + "id": "C-001", + "name": "Tech Solutions Ltd", + "industry": "Technology", + "status": "active", + "creditLimit": 50000.00, + "outstandingBalance": 0.00, + "paymentTerms": 30, + "activeWorkers": 3, + "address": "123 Tech Street, London, EC1A 1BB" + }, + { + "id": "C-002", + "name": "Global Finance Corp", + "industry": "Finance", + "status": "active", + "creditLimit": 100000.00, + "outstandingBalance": 27000.00, + "paymentTerms": 45, + "activeWorkers": 5, + "address": "456 Finance Road, London, E14 5AB" + }, + { + "id": "C-003", + "name": "Healthcare Plus", + "industry": "Healthcare", + "status": "active", + "creditLimit": 75000.00, + "outstandingBalance": 10500.00, + "paymentTerms": 30, + "activeWorkers": 8, + "address": "789 Medical Way, Birmingham, B1 1AA" + }, + { + "id": "C-004", + "name": "Manufacturing Inc", + "industry": "Manufacturing", + "status": "active", + "creditLimit": 60000.00, + "outstandingBalance": 5200.00, + "paymentTerms": 60, + "activeWorkers": 12, + "address": "321 Factory Lane, Manchester, M1 1AA" + } + ] +} diff --git a/src/hooks/index.ts b/src/hooks/index.ts index 697b5d5..b5ed005 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -14,6 +14,7 @@ export { useOnClickOutside } from './use-on-click-outside' export { usePagination } from './use-pagination' export { usePrevious } from './use-previous' export { useSampleData } from './use-sample-data' +export { useJsonData } from './use-json-data' export { useScrollPosition } from './use-scroll-position' export { useSelection } from './use-selection' export { useSort } from './use-sort' diff --git a/src/hooks/use-json-data.ts b/src/hooks/use-json-data.ts new file mode 100644 index 0000000..e300cee --- /dev/null +++ b/src/hooks/use-json-data.ts @@ -0,0 +1,165 @@ +import { useState, useEffect } from 'react' +import appData from '@/data/app-data.json' + +export interface Shift { + date: string + start: string + end: string + hours: number + type: string +} + +export interface Timesheet { + id: string + workerId: string + workerName: string + clientName: string + weekEnding: string + totalHours: number + regularHours: number + overtimeHours: number + status: string + rate: number + total: number + submittedDate: string + approvedDate?: string + shifts: Shift[] +} + +export interface InvoiceItem { + description: string + quantity: number + rate: number + amount: number +} + +export interface Invoice { + id: string + clientName: string + amount: number + vat: number + total: number + status: string + dueDate: string + invoiceDate: string + paidDate?: string + items: InvoiceItem[] +} + +export interface PayrollEntry { + workerId: string + workerName: string + gross: number + net: number + tax: number + ni: number +} + +export interface PayrollRun { + id: string + periodEnding: string + status: string + totalGross: number + totalNet: number + totalTax: number + totalNI: number + processedDate?: string + paymentDate?: string + workerCount: number + entries: PayrollEntry[] +} + +export interface Worker { + id: string + name: string + email: string + phone: string + status: string + role: string + startDate: string + paymentType: string + currentClient: string +} + +export interface ComplianceDoc { + id: string + workerId: string + workerName: string + documentType: string + status: string + expiryDate: string + uploadDate: string + verifiedDate: string +} + +export interface Expense { + id: string + workerId: string + workerName: string + category: string + amount: number + date: string + status: string + description: string + receiptAttached: boolean + approvedDate?: string + rejectedDate?: string + rejectionReason?: string +} + +export interface RateCard { + id: string + role: string + clientName: string + payRate: number + chargeRate: number + margin: number + marginPercent: number + currency: string + validFrom: string + validUntil: string + overtimeMultiplier: number + weekendMultiplier: number + nightMultiplier?: number +} + +export interface Client { + id: string + name: string + industry: string + status: string + creditLimit: number + outstandingBalance: number + paymentTerms: number + activeWorkers: number + address: string +} + +export interface AppData { + timesheets: Timesheet[] + invoices: Invoice[] + payrollRuns: PayrollRun[] + workers: Worker[] + complianceDocs: ComplianceDoc[] + expenses: Expense[] + rateCards: RateCard[] + clients: Client[] +} + +export function useJsonData() { + const [data, setData] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + try { + setData(appData as AppData) + setIsLoading(false) + } catch (err) { + setError(err instanceof Error ? err : new Error('Failed to load data')) + setIsLoading(false) + } + }, []) + + return { data, isLoading, error } +} diff --git a/src/hooks/use-sample-data.ts b/src/hooks/use-sample-data.ts index ed75c29..27a79ba 100644 --- a/src/hooks/use-sample-data.ts +++ b/src/hooks/use-sample-data.ts @@ -1,241 +1,33 @@ import { useEffect } from 'react' import { useKV } from '@github/spark/hooks' -import type { Timesheet, Invoice, Expense, ComplianceDocument } from '@/lib/types' +import appData from '@/data/app-data.json' export function useSampleData() { const [hasInitialized, setHasInitialized] = useKV('sample-data-initialized', false) - const [, setTimesheets] = useKV('timesheets', []) - const [, setInvoices] = useKV('invoices', []) - const [, setExpenses] = useKV('expenses', []) - const [, setComplianceDocs] = useKV('compliance-docs', []) + const [, setTimesheets] = useKV('timesheets', []) + const [, setInvoices] = useKV('invoices', []) + const [, setExpenses] = useKV('expenses', []) + const [, setComplianceDocs] = useKV('compliance-docs', []) + const [, setPayrollRuns] = useKV('payroll-runs', []) + const [, setWorkers] = useKV('workers', []) + const [, setRateCards] = useKV('rate-cards', []) + const [, setClients] = useKV('clients', []) useEffect(() => { if (hasInitialized) return const initializeData = async () => { - const sampleTimesheets: Timesheet[] = [ - { - id: 'TS-001', - workerId: 'W-001', - workerName: 'John Smith', - clientName: 'Acme Corporation', - weekEnding: '2025-01-17', - hours: 40, - status: 'pending', - submittedDate: '2025-01-18T09:00:00Z', - amount: 1200, - rate: 30 - }, - { - id: 'TS-002', - workerId: 'W-002', - workerName: 'Sarah Johnson', - clientName: 'Tech Solutions Ltd', - weekEnding: '2025-01-17', - hours: 45, - status: 'pending', - submittedDate: '2025-01-18T10:30:00Z', - amount: 1575, - rate: 35 - }, - { - id: 'TS-003', - workerId: 'W-001', - workerName: 'John Smith', - clientName: 'Acme Corporation', - weekEnding: '2025-01-10', - hours: 37.5, - status: 'approved', - submittedDate: '2025-01-11T09:00:00Z', - approvedDate: '2025-01-12T14:00:00Z', - amount: 1125, - rate: 30 - }, - { - id: 'TS-004', - workerId: 'W-003', - workerName: 'Michael Brown', - clientName: 'Global Industries', - weekEnding: '2025-01-17', - hours: 52, - status: 'pending', - submittedDate: '2025-01-18T11:00:00Z', - amount: 1820, - rate: 35 - }, - { - id: 'TS-005', - workerId: 'W-004', - workerName: 'Emma Wilson', - clientName: 'Tech Solutions Ltd', - weekEnding: '2025-01-17', - hours: 40, - status: 'approved', - submittedDate: '2025-01-18T08:30:00Z', - approvedDate: '2025-01-18T15:00:00Z', - amount: 1000, - rate: 25 - } - ] - - const sampleInvoices: Invoice[] = [ - { - id: 'INV-001', - invoiceNumber: 'INV-00001', - clientName: 'Acme Corporation', - issueDate: '2025-01-15', - dueDate: '2025-02-14', - amount: 5400, - status: 'sent', - currency: 'GBP' - }, - { - id: 'INV-002', - invoiceNumber: 'INV-00002', - clientName: 'Tech Solutions Ltd', - issueDate: '2025-01-10', - dueDate: '2025-02-09', - amount: 3150, - status: 'paid', - currency: 'GBP' - }, - { - id: 'INV-003', - invoiceNumber: 'INV-00003', - clientName: 'Global Industries', - issueDate: '2024-12-20', - dueDate: '2025-01-19', - amount: 8900, - status: 'overdue', - currency: 'GBP' - }, - { - id: 'INV-004', - invoiceNumber: 'INV-00004', - clientName: 'Tech Solutions Ltd', - issueDate: '2025-01-18', - dueDate: '2025-02-17', - amount: 2500, - status: 'draft', - currency: 'GBP' - } - ] - - const sampleExpenses: Expense[] = [ - { - id: 'EXP-001', - workerId: 'W-001', - workerName: 'John Smith', - clientName: 'Acme Corporation', - date: '2025-01-15', - category: 'Travel', - description: 'Train ticket to client site', - amount: 45.50, - currency: 'GBP', - status: 'pending', - submittedDate: '2025-01-16T10:00:00Z', - billable: true - }, - { - id: 'EXP-002', - workerId: 'W-002', - workerName: 'Sarah Johnson', - clientName: 'Tech Solutions Ltd', - date: '2025-01-14', - category: 'Meals', - description: 'Lunch meeting with client team', - amount: 35.00, - currency: 'GBP', - status: 'approved', - submittedDate: '2025-01-15T09:00:00Z', - approvedDate: '2025-01-16T11:00:00Z', - billable: true - }, - { - id: 'EXP-003', - workerId: 'W-003', - workerName: 'Michael Brown', - clientName: 'Global Industries', - date: '2025-01-16', - category: 'Accommodation', - description: 'Hotel stay for 2 nights', - amount: 240.00, - currency: 'GBP', - status: 'pending', - submittedDate: '2025-01-17T08:30:00Z', - billable: true - }, - { - id: 'EXP-004', - workerId: 'W-001', - workerName: 'John Smith', - clientName: 'Acme Corporation', - date: '2025-01-10', - category: 'Equipment', - description: 'Laptop accessories', - amount: 85.00, - currency: 'GBP', - status: 'rejected', - submittedDate: '2025-01-11T14:00:00Z', - billable: false - } - ] - - const sampleComplianceDocs: ComplianceDocument[] = [ - { - id: 'DOC-001', - workerId: 'W-001', - workerName: 'John Smith', - documentType: 'DBS Check', - expiryDate: '2025-02-15', - status: 'expiring', - daysUntilExpiry: 28 - }, - { - id: 'DOC-002', - workerId: 'W-002', - workerName: 'Sarah Johnson', - documentType: 'Right to Work', - expiryDate: '2026-06-30', - status: 'valid', - daysUntilExpiry: 528 - }, - { - id: 'DOC-003', - workerId: 'W-003', - workerName: 'Michael Brown', - documentType: 'Professional License', - expiryDate: '2025-01-10', - status: 'expired', - daysUntilExpiry: -8 - }, - { - id: 'DOC-004', - workerId: 'W-004', - workerName: 'Emma Wilson', - documentType: 'First Aid Certificate', - expiryDate: '2025-03-20', - status: 'valid', - daysUntilExpiry: 61 - }, - { - id: 'DOC-005', - workerId: 'W-001', - workerName: 'John Smith', - documentType: 'Driving License', - expiryDate: '2025-02-05', - status: 'expiring', - daysUntilExpiry: 18 - } - ] - - setTimesheets(sampleTimesheets) - setInvoices(sampleInvoices) - setExpenses(sampleExpenses) - setComplianceDocs(sampleComplianceDocs) + setTimesheets(appData.timesheets) + setInvoices(appData.invoices) + setExpenses(appData.expenses) + setComplianceDocs(appData.complianceDocs) + setPayrollRuns(appData.payrollRuns) + setWorkers(appData.workers) + setRateCards(appData.rateCards) + setClients(appData.clients) setHasInitialized(true) } initializeData() - }, [hasInitialized, setTimesheets, setInvoices, setExpenses, setComplianceDocs, setHasInitialized]) + }, [hasInitialized, setTimesheets, setInvoices, setExpenses, setComplianceDocs, setPayrollRuns, setWorkers, setRateCards, setClients, setHasInitialized]) } diff --git a/src/lib/view-preloader.ts b/src/lib/view-preloader.ts index 37a6449..c0cf24a 100644 --- a/src/lib/view-preloader.ts +++ b/src/lib/view-preloader.ts @@ -27,6 +27,7 @@ const viewPreloadMap: Record Promise> = { 'roadmap': () => import('@/components/roadmap-view'), 'component-showcase': () => import('@/components/ComponentShowcase'), 'business-logic-demo': () => import('@/components/BusinessLogicDemo'), + 'data-admin': () => import('@/components/views/data-admin-view'), } const preloadedViews = new Set()