mirror of
https://github.com/johndoe6345789/workforce-pay-bill-p.git
synced 2026-04-24 05:14:55 +00:00
Generated by Spark: Load all ui data from json
This commit is contained in:
354
JSON_DATA_STRUCTURE.md
Normal file
354
JSON_DATA_STRUCTURE.md
Normal file
@@ -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 <div>Loading...</div>
|
||||
if (error) return <div>Error: {error.message}</div>
|
||||
|
||||
return <div>{data.workers.length} workers</div>
|
||||
}
|
||||
```
|
||||
|
||||
### 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<NewEntity[]>('new-entity', [])
|
||||
|
||||
setNewEntity(appData.newEntity)
|
||||
```
|
||||
|
||||
4. **Expose in use-app-data**
|
||||
```typescript
|
||||
const [newEntity = [], setNewEntity] = useKV<NewEntity[]>('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
|
||||
@@ -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()
|
||||
|
||||
86
src/components/DataManagement.tsx
Normal file
86
src/components/DataManagement.tsx
Normal file
@@ -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 (
|
||||
<Card className="p-6 space-y-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-2">Data Management</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Manage application data and reset to defaults
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button onClick={exportData} variant="outline">
|
||||
Export Current Data
|
||||
</Button>
|
||||
<Button onClick={resetAllData} variant="destructive">
|
||||
Reset to Default Data
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground space-y-1">
|
||||
<p>• Export: Download current data as JSON file</p>
|
||||
<p>• Reset: Clear all data and reload from app-data.json</p>
|
||||
<p>• After reset, refresh the page to see changes</p>
|
||||
</div>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
@@ -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 <BusinessLogicDemo />
|
||||
|
||||
case 'data-admin':
|
||||
return <DataAdminView />
|
||||
|
||||
default:
|
||||
return <DashboardView metrics={metrics} />
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<NavItem
|
||||
icon={<Database size={20} />}
|
||||
label="Data Administration"
|
||||
active={currentView === 'data-admin'}
|
||||
onClick={() => setCurrentView('data-admin')}
|
||||
view="data-admin"
|
||||
/>
|
||||
<NavItem
|
||||
icon={<Question size={20} />}
|
||||
label="Query Guide"
|
||||
|
||||
246
src/components/views/data-admin-view.tsx
Normal file
246
src/components/views/data-admin-view.tsx
Normal file
@@ -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 (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-3xl font-semibold mb-2">Data Administration</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Manage application data loaded from JSON files
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Alert>
|
||||
<FileJs className="h-4 w-4" />
|
||||
<AlertTitle>JSON-Based Data</AlertTitle>
|
||||
<AlertDescription>
|
||||
All application data is loaded from <code className="px-1 py-0.5 bg-muted rounded text-xs">/src/data/app-data.json</code> into persistent KV storage.
|
||||
Changes you make in the app are saved to KV storage and persist between sessions.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5" />
|
||||
Export Current Data
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Download all current data as a JSON file
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
This will export all data currently in KV storage, including any modifications you've made.
|
||||
</p>
|
||||
<Button onClick={exportData} className="w-full">
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
Export Data
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<ArrowClockwise className="h-5 w-5" />
|
||||
Reset to Default Data
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Clear all data and reload from JSON file
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
This will delete all data from KV storage. Refresh the page to reload the default data from app-data.json.
|
||||
</p>
|
||||
<Button onClick={resetAllData} variant="destructive" className="w-full">
|
||||
<ArrowClockwise className="mr-2 h-4 w-4" />
|
||||
Reset Data
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Database className="h-5 w-5" />
|
||||
Storage Information
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-sm font-medium">Data Entities</span>
|
||||
<Badge variant="secondary">8</Badge>
|
||||
</div>
|
||||
<Separator />
|
||||
<div className="grid grid-cols-2 gap-2 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Timesheets</span>
|
||||
<Badge variant="outline">timesheets</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Invoices</span>
|
||||
<Badge variant="outline">invoices</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Payroll Runs</span>
|
||||
<Badge variant="outline">payroll-runs</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Workers</span>
|
||||
<Badge variant="outline">workers</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Compliance</span>
|
||||
<Badge variant="outline">compliance-docs</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Expenses</span>
|
||||
<Badge variant="outline">expenses</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Rate Cards</span>
|
||||
<Badge variant="outline">rate-cards</Badge>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-muted-foreground">Clients</span>
|
||||
<Badge variant="outline">clients</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={viewAllKeys} variant="outline" className="w-full">
|
||||
<Database className="mr-2 h-4 w-4" />
|
||||
View All Keys (Console)
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Data Flow</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className="mt-0.5">1</Badge>
|
||||
<div>
|
||||
<p className="font-medium text-sm">Load from JSON</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
On first load, data is read from <code>/src/data/app-data.json</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className="mt-0.5">2</Badge>
|
||||
<div>
|
||||
<p className="font-medium text-sm">Store in KV</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Data is written to persistent KV storage
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className="mt-0.5">3</Badge>
|
||||
<div>
|
||||
<p className="font-medium text-sm">Use in App</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Components read from and write to KV storage via hooks
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<Badge className="mt-0.5">4</Badge>
|
||||
<div>
|
||||
<p className="font-medium text-sm">Persist Changes</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
All changes persist between sessions automatically
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
420
src/data/app-data.json
Normal file
420
src/data/app-data.json
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
165
src/hooks/use-json-data.ts
Normal file
165
src/hooks/use-json-data.ts
Normal file
@@ -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<AppData | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [error, setError] = useState<Error | null>(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 }
|
||||
}
|
||||
@@ -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<boolean>('sample-data-initialized', false)
|
||||
const [, setTimesheets] = useKV<Timesheet[]>('timesheets', [])
|
||||
const [, setInvoices] = useKV<Invoice[]>('invoices', [])
|
||||
const [, setExpenses] = useKV<Expense[]>('expenses', [])
|
||||
const [, setComplianceDocs] = useKV<ComplianceDocument[]>('compliance-docs', [])
|
||||
const [, setTimesheets] = useKV<any[]>('timesheets', [])
|
||||
const [, setInvoices] = useKV<any[]>('invoices', [])
|
||||
const [, setExpenses] = useKV<any[]>('expenses', [])
|
||||
const [, setComplianceDocs] = useKV<any[]>('compliance-docs', [])
|
||||
const [, setPayrollRuns] = useKV<any[]>('payroll-runs', [])
|
||||
const [, setWorkers] = useKV<any[]>('workers', [])
|
||||
const [, setRateCards] = useKV<any[]>('rate-cards', [])
|
||||
const [, setClients] = useKV<any[]>('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])
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ const viewPreloadMap: Record<View, () => Promise<any>> = {
|
||||
'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<View>()
|
||||
|
||||
Reference in New Issue
Block a user