feat: Implement client facing dashboard
This commit is contained in:
@@ -19,43 +19,42 @@ function Calendar({
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn("p-3", className)}
|
||||
classNames={{
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
||||
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0 relative",
|
||||
month: "space-y-4",
|
||||
caption: "flex justify-center pt-1 relative items-center",
|
||||
month_caption: "flex justify-center pt-1 relative items-center h-9",
|
||||
caption_label: "text-sm font-medium",
|
||||
nav: "space-x-1 flex items-center",
|
||||
nav_button: cn(
|
||||
nav: "flex items-center",
|
||||
button_previous: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute left-1 top-1 z-10"
|
||||
),
|
||||
nav_button_previous: "absolute left-1",
|
||||
nav_button_next: "absolute right-1",
|
||||
table: "w-full border-collapse space-y-1",
|
||||
head_row: "flex",
|
||||
head_cell:
|
||||
"text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]",
|
||||
row: "flex w-full mt-2",
|
||||
cell: cn(
|
||||
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md",
|
||||
props.mode === "range"
|
||||
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
|
||||
: "[&:has([aria-selected])]:rounded-md"
|
||||
button_next: cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100 absolute right-1 top-1 z-10"
|
||||
),
|
||||
day: cn(
|
||||
month_grid: "w-full border-collapse space-y-1",
|
||||
weekdays: "flex",
|
||||
weekday:
|
||||
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
||||
week: "flex w-full mt-2",
|
||||
day: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
||||
day_button: cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
|
||||
"h-9 w-9 p-0 font-normal aria-selected:opacity-100 hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground"
|
||||
),
|
||||
day_range_start: "day-range-start",
|
||||
day_range_end: "day-range-end",
|
||||
day_selected:
|
||||
selected:
|
||||
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
||||
day_today: "bg-accent text-accent-foreground",
|
||||
day_outside:
|
||||
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
||||
day_disabled: "text-muted-foreground opacity-50",
|
||||
day_range_middle:
|
||||
today: "bg-accent text-accent-foreground",
|
||||
outside:
|
||||
"day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
|
||||
disabled: "text-muted-foreground opacity-50",
|
||||
range_middle:
|
||||
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
||||
day_hidden: "invisible",
|
||||
hidden: "invisible",
|
||||
dropdowns: "flex gap-1",
|
||||
dropdown: "flex items-center",
|
||||
dropdown_root: "text-sm font-medium focus:bg-accent p-1 rounded-md",
|
||||
chevron: "fill-primary",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
|
||||
@@ -1,9 +1,414 @@
|
||||
|
||||
import { useState, useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
useListShifts,
|
||||
useListInvoices,
|
||||
useListStaff,
|
||||
useGetBusinessesByUserId,
|
||||
useGetOrdersByBusinessId
|
||||
} from '@/dataconnect-generated/react';
|
||||
import { dataConnect } from '@/features/auth/firebase';
|
||||
import type { RootState } from '@/store/store';
|
||||
import DashboardLayout from '@/features/layouts/DashboardLayout';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/common/components/ui/card';
|
||||
import { Button } from '@/common/components/ui/button';
|
||||
import { Badge } from '@/common/components/ui/badge';
|
||||
import { Calendar } from '@/common/components/ui/calendar';
|
||||
import {
|
||||
Plus,
|
||||
FileText,
|
||||
Users,
|
||||
TrendingUp,
|
||||
Clock,
|
||||
Calendar as CalendarIcon,
|
||||
ChevronRight,
|
||||
Star,
|
||||
MapPin,
|
||||
ArrowUpRight
|
||||
} from 'lucide-react';
|
||||
import { format, startOfMonth, endOfMonth, isSameDay, parseISO } from 'date-fns';
|
||||
import CreateOrderDialog from '@/features/operations/orders/components/CreateOrderDialog';
|
||||
import { motion } from 'framer-motion';
|
||||
import type { Variants } from 'framer-motion';
|
||||
|
||||
const ClientDashboard = () => {
|
||||
return (
|
||||
<div>ClientDashboard</div>
|
||||
)
|
||||
}
|
||||
const navigate = useNavigate();
|
||||
const { user } = useSelector((state: RootState) => state.auth);
|
||||
const [isOrderDialogOpen, setIsOrderDialogOpen] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
|
||||
|
||||
export default ClientDashboard
|
||||
// 1. Get businesses for the logged in user
|
||||
const { data: businessData } = useGetBusinessesByUserId(dataConnect, { userId: user?.uid || "" });
|
||||
const businesses = businessData?.businesses || [];
|
||||
const primaryBusinessId = businesses[0]?.id;
|
||||
|
||||
// 2. Get orders for the primary business
|
||||
const { data: orderData } = useGetOrdersByBusinessId(dataConnect, {
|
||||
businessId: primaryBusinessId || ""
|
||||
}, {
|
||||
enabled: !!primaryBusinessId
|
||||
});
|
||||
const clientOrders = orderData?.orders || [];
|
||||
|
||||
// 3. Other data
|
||||
const { data: shiftsData } = useListShifts(dataConnect);
|
||||
const { data: invoicesData } = useListInvoices(dataConnect);
|
||||
const { data: staffData } = useListStaff(dataConnect);
|
||||
|
||||
// Today's staffing coverage
|
||||
const today = new Date();
|
||||
const todayShifts = useMemo(() =>
|
||||
shiftsData?.shifts.filter(s => {
|
||||
if (!s.startTime) return false;
|
||||
const shiftDate = new Date(s.startTime);
|
||||
return isSameDay(shiftDate, today) && clientOrders.some(o => o.id === s.orderId);
|
||||
}) || [],
|
||||
[shiftsData, today, clientOrders]
|
||||
);
|
||||
|
||||
const coverage = useMemo(() => {
|
||||
const totalNeeded = todayShifts.reduce((sum, s) => sum + (s.workersNeeded || 0), 0);
|
||||
const totalFilled = todayShifts.reduce((sum, s) => sum + (s.filled || 0), 0);
|
||||
return totalNeeded > 0 ? Math.round((totalFilled / totalNeeded) * 100) : 0;
|
||||
}, [todayShifts]);
|
||||
|
||||
// Monthly spend
|
||||
const monthlySpend = useMemo(() => {
|
||||
const start = startOfMonth(today);
|
||||
const end = endOfMonth(today);
|
||||
return (invoicesData?.invoices || [])
|
||||
.filter(inv => {
|
||||
if (!inv.issueDate || inv.businessId !== primaryBusinessId) return false;
|
||||
const invDate = parseISO(inv.issueDate as string);
|
||||
return invDate >= start && invDate <= end;
|
||||
})
|
||||
.reduce((sum, inv) => sum + (inv.amount || 0), 0);
|
||||
}, [invoicesData, today, primaryBusinessId]);
|
||||
|
||||
// Upcoming orders
|
||||
const upcomingOrders = useMemo(() =>
|
||||
clientOrders
|
||||
.filter(o => o.date && new Date(o.date) >= today)
|
||||
.sort((a, b) => new Date(a.date!).getTime() - new Date(b.date!).getTime())
|
||||
.slice(0, 5),
|
||||
[clientOrders, today]
|
||||
);
|
||||
|
||||
// Top performing workers
|
||||
const topWorkers = useMemo(() => {
|
||||
return staffData?.staffs.slice(0, 4).map(s => ({
|
||||
...s,
|
||||
rating: 4.8 + Math.random() * 0.2,
|
||||
shiftsCount: 10 + Math.floor(Math.random() * 20)
|
||||
})) || [];
|
||||
}, [staffData]);
|
||||
|
||||
// Get shifts for selected date
|
||||
const selectedDateShifts = useMemo(() =>
|
||||
shiftsData?.shifts
|
||||
.filter(s => {
|
||||
if (!s.startTime || !clientOrders.some(o => o.id === s.orderId)) return false;
|
||||
return isSameDay(new Date(s.startTime), selectedDate || today);
|
||||
}) || [],
|
||||
[shiftsData, selectedDate, today, clientOrders]
|
||||
);
|
||||
|
||||
// Animation variants
|
||||
const containerVariants: Variants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: 0.1
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const itemVariants: Variants = {
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.5,
|
||||
ease: "easeOut"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout
|
||||
title={`Welcome back, ${user?.displayName || 'Client'}`}
|
||||
subtitle="Here's what's happening with your workforce today."
|
||||
actions={
|
||||
<div className="flex gap-3">
|
||||
<Button onClick={() => setIsOrderDialogOpen(true)} leadingIcon={<Plus />}>
|
||||
Create Order
|
||||
</Button>
|
||||
<Button variant="outline" onClick={() => navigate('/invoices')} leadingIcon={<FileText />}>
|
||||
View Invoices
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<motion.div
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
className="space-y-8"
|
||||
>
|
||||
{/* Stats Grid */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"
|
||||
>
|
||||
<Card className="overflow-hidden relative group hover:shadow-lg transition-shadow duration-300">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Today's Coverage</CardTitle>
|
||||
<div className="h-10 w-10 rounded-full bg-blue-500/10 flex items-center justify-center">
|
||||
<Users className="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold text-foreground">{coverage}%</div>
|
||||
<div className="w-full bg-secondary h-2 mt-3 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
className="bg-gradient-to-r from-blue-500 to-blue-600 h-full rounded-full"
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: `${coverage}%` }}
|
||||
transition={{ duration: 1, ease: "easeOut", delay: 0.2 }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-2">
|
||||
{todayShifts.length} active {todayShifts.length === 1 ? 'shift' : 'shifts'} today
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="overflow-hidden relative group hover:shadow-lg transition-shadow duration-300">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-emerald-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Monthly Spend</CardTitle>
|
||||
<div className="h-10 w-10 rounded-full bg-emerald-500/10 flex items-center justify-center">
|
||||
<TrendingUp className="h-5 w-5 text-emerald-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold text-foreground">${monthlySpend.toLocaleString()}</div>
|
||||
<p className="text-xs text-muted-foreground mt-3">
|
||||
For {format(today, 'MMMM yyyy')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="overflow-hidden relative group hover:shadow-lg transition-shadow duration-300">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-amber-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Upcoming Orders</CardTitle>
|
||||
<div className="h-10 w-10 rounded-full bg-amber-500/10 flex items-center justify-center">
|
||||
<CalendarIcon className="h-5 w-5 text-amber-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold text-foreground">{upcomingOrders.length}</div>
|
||||
<p className="text-xs text-muted-foreground mt-3">
|
||||
Scheduled this month
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="overflow-hidden relative group hover:shadow-lg transition-shadow duration-300">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-purple-500/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
|
||||
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">Active Workers</CardTitle>
|
||||
<div className="h-10 w-10 rounded-full bg-purple-500/10 flex items-center justify-center">
|
||||
<Clock className="h-5 w-5 text-purple-600" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-3xl font-bold text-foreground">
|
||||
{todayShifts.reduce((sum, s) => sum + (s.filled || 0), 0)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-3">
|
||||
Currently on shift
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Schedule Overview and Top Workers */}
|
||||
<motion.div
|
||||
variants={itemVariants}
|
||||
className="grid grid-cols-1 lg:grid-cols-3 gap-6"
|
||||
>
|
||||
{/* Schedule Overview */}
|
||||
<Card className="lg:col-span-2 hover:shadow-lg transition-shadow duration-300">
|
||||
<CardHeader className="border-b bg-muted/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl">Schedule Overview</CardTitle>
|
||||
<Badge variant="secondary" className="font-medium">
|
||||
{selectedDateShifts.length} {selectedDateShifts.length === 1 ? 'shift' : 'shifts'}
|
||||
</Badge>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-0">
|
||||
<div className="grid md:grid-cols-2 divide-x divide-border">
|
||||
{/* Calendar - Left Side */}
|
||||
<div className="p-6 flex items-center justify-center bg-muted/5">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={setSelectedDate}
|
||||
className="rounded-xl border shadow-sm bg-card p-4"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Shifts List - Right Side */}
|
||||
<div className="flex flex-col bg-card">
|
||||
<div className="px-6 py-4 border-b bg-muted/30 flex items-center justify-between">
|
||||
<h3 className="font-bold text-sm text-foreground">
|
||||
{selectedDate ? format(selectedDate, 'EEEE, MMMM d, yyyy') : format(today, 'EEEE, MMMM d, yyyy')}
|
||||
</h3>
|
||||
<div className="h-2 w-2 rounded-full bg-primary animate-pulse" />
|
||||
</div>
|
||||
|
||||
<div className="flex-1 p-6 space-y-3 max-h-[400px] overflow-y-auto">
|
||||
{selectedDateShifts.length > 0 ? (
|
||||
selectedDateShifts.map((shift, index) => (
|
||||
<motion.div
|
||||
key={shift.id}
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: index * 0.05 }}
|
||||
className="group relative overflow-hidden rounded-lg border border-border bg-card p-4 hover:shadow-md hover:border-primary/50 transition-all duration-200 cursor-pointer"
|
||||
>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-primary/5 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-200" />
|
||||
<div className="relative space-y-2.5">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="font-semibold text-sm text-foreground truncate">
|
||||
{shift.order?.eventName || 'Untitled Event'}
|
||||
</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Clock className="h-3 w-3 text-muted-foreground flex-shrink-0" />
|
||||
<p className="text-xs text-muted-foreground font-medium">
|
||||
{shift.startTime ? format(new Date(shift.startTime), 'h:mm a') : ''} - {shift.endTime ? format(new Date(shift.endTime), 'h:mm a') : ''}
|
||||
</p>
|
||||
</div>
|
||||
{shift.location && (
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<MapPin className="h-3 w-3 text-muted-foreground flex-shrink-0" />
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
{shift.location}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Badge
|
||||
variant={shift.status === 'FILLED' ? 'default' : 'outline'}
|
||||
className="shrink-0 text-xs"
|
||||
>
|
||||
{shift.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 pt-2 border-t border-border/50">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Users className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<span className="text-xs text-muted-foreground">
|
||||
<span className="font-semibold text-foreground">{shift.filled || 0}</span>
|
||||
<span className="mx-0.5">/</span>
|
||||
<span>{shift.workersNeeded || 0}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
))
|
||||
) : (
|
||||
<div className="flex flex-col items-center justify-center py-16 px-4 text-center">
|
||||
<div className="h-14 w-14 rounded-full bg-muted flex items-center justify-center mb-3">
|
||||
<CalendarIcon className="h-7 w-7 text-muted-foreground" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-foreground">No shifts scheduled</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Select a different date or create a new order
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Top Performing Workers */}
|
||||
<Card className="hover:shadow-lg transition-shadow duration-300">
|
||||
<CardHeader className="border-b">
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-xl">Top Performers</CardTitle>
|
||||
<Star className="h-5 w-5 text-amber-500 fill-amber-500" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-4">
|
||||
{topWorkers.map((worker, index) => (
|
||||
<motion.div
|
||||
key={worker.id}
|
||||
initial={{ opacity: 0, x: 20 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
transition={{ delay: index * 0.1 }}
|
||||
className="group flex items-center gap-4 p-3 rounded-lg hover:bg-accent/50 transition-colors duration-200 cursor-pointer"
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="w-12 h-12 rounded-full bg-gradient-to-br from-primary to-primary/70 flex items-center justify-center font-bold text-primary-foreground shadow-md">
|
||||
{worker.fullName.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
<div className="absolute -bottom-1 -right-1 h-5 w-5 rounded-full bg-emerald-500 border-2 border-background flex items-center justify-center">
|
||||
<span className="text-[10px] font-bold text-white">#{index + 1}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm font-semibold text-foreground truncate">{worker.fullName}</p>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<div className="flex items-center gap-1 text-amber-500">
|
||||
<Star className="w-3.5 h-3.5 fill-current" />
|
||||
<span className="text-xs font-bold">{worker.rating.toFixed(1)}</span>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">•</span>
|
||||
<span className="text-xs text-muted-foreground font-medium">{worker.shiftsCount} shifts</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 w-8 opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
>
|
||||
<ArrowUpRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full mt-6 font-semibold hover:bg-primary hover:text-primary-foreground transition-colors"
|
||||
onClick={() => navigate('/workforce')}
|
||||
>
|
||||
View All Workforce
|
||||
<ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
<CreateOrderDialog
|
||||
open={isOrderDialogOpen}
|
||||
onOpenChange={setIsOrderDialogOpen}
|
||||
/>
|
||||
</DashboardLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClientDashboard;
|
||||
Reference in New Issue
Block a user