From 5feb78e54905fabefe020a987773d16ee7526e3f Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Thu, 5 Feb 2026 16:56:56 +0000 Subject: [PATCH] Generated by Spark: Create scheduled automatic report generation --- ROADMAP.md | 1 + src/App.tsx | 4 +- src/components/ScheduledReportsManager.tsx | 502 +++++++++++++++++++++ src/components/ViewRouter.tsx | 4 + src/components/nav/nav-sections.tsx | 7 + src/data/roadmap.json | 3 +- src/data/translations/en.json | 61 ++- src/hooks/NEW_HOOKS_LATEST.md | 56 +++ src/hooks/use-scheduled-reports.ts | 387 ++++++++++++++++ src/lib/view-preloader.ts | 1 + src/store/slices/uiSlice.ts | 2 +- 11 files changed, 1023 insertions(+), 5 deletions(-) create mode 100644 src/components/ScheduledReportsManager.tsx create mode 100644 src/hooks/use-scheduled-reports.ts diff --git a/ROADMAP.md b/ROADMAP.md index c8c195f..c7eef85 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -88,6 +88,7 @@ This roadmap outlines the phased development plan for WorkForce Pro, a cloud-bas - ✅ Margin analysis and forecasting - ✅ Advanced reports view with multiple report types - ✅ Data export capabilities +- ✅ Scheduled automatic report generation --- diff --git a/src/App.tsx b/src/App.tsx index 4140de0..ea5c76e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,7 +25,7 @@ import { Badge } from '@/components/ui/badge' import { Code } from '@phosphor-icons/react' import { useRef, useState } from 'react' -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' | 'translation-demo' | 'profile' | 'roles-permissions' | 'workflow-templates' | 'parallel-approval-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' | 'translation-demo' | 'profile' | 'roles-permissions' | 'workflow-templates' | 'parallel-approval-demo' | 'scheduled-reports' function App() { const dispatch = useAppDispatch() @@ -87,7 +87,7 @@ function App() { const handleViewChange = (view: View) => { dispatch(setCurrentView(view)) - announce(`Navigated to ${view}`) + announce(`Navigated to ${view}`, 'polite') } useKeyboardShortcuts([ diff --git a/src/components/ScheduledReportsManager.tsx b/src/components/ScheduledReportsManager.tsx new file mode 100644 index 0000000..0c8cd13 --- /dev/null +++ b/src/components/ScheduledReportsManager.tsx @@ -0,0 +1,502 @@ +import { useState } from 'react' +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +import { Textarea } from '@/components/ui/textarea' +import { + useScheduledReports, + type ReportType, + type ReportFrequency, + type ReportFormat, + type ScheduledReport +} from '@/hooks/use-scheduled-reports' +import { + Calendar, + Clock, + Play, + Pause, + Trash, + Plus, + ChartBar, + Download, + CheckCircle, + XCircle, + Clock as ClockIcon +} from '@phosphor-icons/react' +import { toast } from 'sonner' +import { PageHeader } from '@/components/ui/page-header' +import { Grid } from '@/components/ui/grid' +import { Stack } from '@/components/ui/stack' +import { MetricCard } from '@/components/ui/metric-card' +import { useTranslation } from '@/hooks/use-translation' +import { useAppSelector } from '@/store/hooks' +import { formatDistance } from 'date-fns' + +const reportTypeLabels: Record = { + 'margin-analysis': 'Margin Analysis', + 'revenue-summary': 'Revenue Summary', + 'payroll-summary': 'Payroll Summary', + 'timesheet-summary': 'Timesheet Summary', + 'expense-summary': 'Expense Summary', + 'cash-flow': 'Cash Flow', + 'compliance-status': 'Compliance Status', + 'worker-utilization': 'Worker Utilization' +} + +const frequencyLabels: Record = { + daily: 'Daily', + weekly: 'Weekly', + monthly: 'Monthly', + quarterly: 'Quarterly' +} + +export function ScheduledReportsManager() { + const { t } = useTranslation() + const currentUser = useAppSelector(state => state.auth.user) + + const { + schedules, + executions, + createSchedule, + deleteSchedule, + pauseSchedule, + resumeSchedule, + runScheduleNow, + getExecutionHistory + } = useScheduledReports() + + const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) + const [selectedSchedule, setSelectedSchedule] = useState(null) + const [isHistoryDialogOpen, setIsHistoryDialogOpen] = useState(false) + + const [formData, setFormData] = useState({ + name: '', + description: '', + type: 'margin-analysis' as ReportType, + frequency: 'monthly' as ReportFrequency, + format: 'csv' as ReportFormat, + recipients: '' + }) + + const activeSchedules = schedules.filter(s => s.status === 'active').length + const totalExecutions = executions.length + const successRate = executions.length > 0 + ? (executions.filter(e => e.status === 'success').length / executions.length * 100).toFixed(1) + : 0 + + const handleCreate = () => { + if (!formData.name.trim()) { + toast.error('Report name is required') + return + } + + const recipientList = formData.recipients + .split(',') + .map(r => r.trim()) + .filter(r => r.length > 0) + + createSchedule({ + name: formData.name, + description: formData.description || undefined, + type: formData.type, + frequency: formData.frequency, + format: formData.format, + status: 'active', + recipients: recipientList, + createdBy: currentUser?.email || 'unknown' + }) + + toast.success(`Scheduled report "${formData.name}" created`) + setIsCreateDialogOpen(false) + setFormData({ + name: '', + description: '', + type: 'margin-analysis', + frequency: 'monthly', + format: 'csv', + recipients: '' + }) + } + + const handleRunNow = async (id: string) => { + const schedule = schedules.find(s => s.id === id) + if (!schedule) return + + toast.loading(`Running report "${schedule.name}"...`) + const execution = await runScheduleNow(id) + + if (execution?.status === 'success') { + toast.success(`Report "${schedule.name}" completed successfully`) + } else { + toast.error(`Report "${schedule.name}" failed: ${execution?.error || 'Unknown error'}`) + } + } + + const handlePause = (id: string) => { + const schedule = schedules.find(s => s.id === id) + pauseSchedule(id) + toast.info(`Report "${schedule?.name}" paused`) + } + + const handleResume = (id: string) => { + const schedule = schedules.find(s => s.id === id) + resumeSchedule(id) + toast.success(`Report "${schedule?.name}" resumed`) + } + + const handleDelete = (id: string) => { + const schedule = schedules.find(s => s.id === id) + if (confirm(`Delete scheduled report "${schedule?.name}"?`)) { + deleteSchedule(id) + toast.success(`Report "${schedule?.name}" deleted`) + } + } + + const showHistory = (schedule: ScheduledReport) => { + setSelectedSchedule(schedule) + setIsHistoryDialogOpen(true) + } + + const history = selectedSchedule ? getExecutionHistory(selectedSchedule.id) : [] + + return ( + + setIsCreateDialogOpen(true)}> + + Create Schedule + + } + /> + + + } + /> + } + /> + } + /> + + + + {schedules.map((schedule) => ( + + +
+
+
+ {schedule.name} + + {schedule.status} + +
+ {schedule.description && ( + {schedule.description} + )} +
+
+
+ + +
+
+
Type
+
{reportTypeLabels[schedule.type]}
+
+
+
Frequency
+
{frequencyLabels[schedule.frequency]}
+
+
+
Format
+
{schedule.format}
+
+
+
Run Count
+
{schedule.runCount}
+
+
+ + {schedule.lastRunDate && ( +
+ + + Last run: {formatDistance(new Date(schedule.lastRunDate), new Date(), { addSuffix: true })} + + {schedule.lastRunStatus === 'success' ? ( + + ) : ( + + )} +
+ )} + +
+ + + Next run: {formatDistance(new Date(schedule.nextRunDate), new Date(), { addSuffix: true })} + +
+ +
+ + + {schedule.status === 'active' ? ( + + ) : ( + + )} + + + + +
+
+
+
+ ))} + + {schedules.length === 0 && ( + + + +

+ No scheduled reports yet. Create your first automated report schedule. +

+ +
+
+ )} +
+ + + + + Create Scheduled Report + + Set up an automated report to run on a regular schedule + + + + +
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="e.g., Monthly Revenue Report" + /> +
+ +
+ +