modula-ui 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/bin/run.js +86 -0
- package/package.json +71 -0
- package/public/avatars/avatar1.png +0 -0
- package/public/avatars/avatar2.png +0 -0
- package/public/avatars/avatar3.png +0 -0
- package/public/avatars/avatar4.png +0 -0
- package/public/avatars/sources.md +34 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/registry.json +12 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +126 -0
- package/src/app/layout.js +29 -0
- package/src/app/page.js +50 -0
- package/src/app/patterns/page.js +50 -0
- package/src/components/CodeCard.jsx +16 -0
- package/src/components/CopyButton.jsx +31 -0
- package/src/components/Header.jsx +24 -0
- package/src/components/Logo.jsx +64 -0
- package/src/components/MobileOverlay.jsx +10 -0
- package/src/components/PreviewCard.jsx +98 -0
- package/src/components/Sidebar.jsx +47 -0
- package/src/components/ui/avatar.jsx +47 -0
- package/src/components/ui/badge.jsx +44 -0
- package/src/components/ui/button.jsx +56 -0
- package/src/components/ui/calendar.jsx +178 -0
- package/src/components/ui/card.jsx +101 -0
- package/src/components/ui/chart.jsx +314 -0
- package/src/components/ui/checkbox.jsx +30 -0
- package/src/components/ui/dropdown-menu.jsx +223 -0
- package/src/components/ui/input.jsx +24 -0
- package/src/components/ui/navigation-menu.jsx +152 -0
- package/src/components/ui/popover.jsx +47 -0
- package/src/components/ui/progress.jsx +29 -0
- package/src/components/ui/scroll-area.jsx +51 -0
- package/src/components/ui/select.jsx +168 -0
- package/src/components/ui/separator.jsx +27 -0
- package/src/components/ui/sheet.jsx +140 -0
- package/src/components/ui/sidebar.jsx +682 -0
- package/src/components/ui/skeleton.jsx +15 -0
- package/src/components/ui/slider.jsx +56 -0
- package/src/components/ui/tooltip.jsx +55 -0
- package/src/data/componentData.js +12 -0
- package/src/hooks/use-mobile.js +19 -0
- package/src/lib/utils.js +6 -0
- package/src/library/components/Alert.jsx +27 -0
- package/src/library/components/Badge.jsx +19 -0
- package/src/library/components/Button.jsx +31 -0
- package/src/library/components/Card.jsx +25 -0
- package/src/library/components/Input.jsx +35 -0
- package/src/library/components/Modal.jsx +26 -0
- package/src/library/components/Textarea.jsx +15 -0
- package/src/library/components/Toggle.jsx +16 -0
- package/src/library/pages/FitnessPage/FitnessPage.jsx +519 -0
- package/src/library/pages/FitnessPage/index.jsx +12 -0
- package/src/library/pages/GroupChat/GroupChat.jsx +275 -0
- package/src/library/pages/GroupChat/data.js +203 -0
- package/src/library/pages/GroupChat/index.jsx +12 -0
- package/src/library/pages/ReservationsOverview/ReservationsOverviewPage.jsx +225 -0
- package/src/library/pages/ReservationsOverview/index.jsx +12 -0
- package/src/library/pages/VideoConference/VideoConferencePage.jsx +334 -0
- package/src/library/pages/VideoConference/index.jsx +12 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Search,
|
|
4
|
+
Bell,
|
|
5
|
+
LayoutDashboard,
|
|
6
|
+
MessageSquare,
|
|
7
|
+
Users,
|
|
8
|
+
BarChart2,
|
|
9
|
+
Dumbbell,
|
|
10
|
+
Settings,
|
|
11
|
+
HelpCircle,
|
|
12
|
+
ChevronLeft,
|
|
13
|
+
MoreHorizontal,
|
|
14
|
+
Calendar as CalendarIcon,
|
|
15
|
+
Filter,
|
|
16
|
+
Check,
|
|
17
|
+
Eye,
|
|
18
|
+
Link as LinkIcon,
|
|
19
|
+
} from "lucide-react";
|
|
20
|
+
|
|
21
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
22
|
+
import { Button } from "@/components/ui/button";
|
|
23
|
+
import { Input } from "@/components/ui/input";
|
|
24
|
+
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
25
|
+
import { Badge } from "@/components/ui/badge";
|
|
26
|
+
import { Progress } from "@/components/ui/progress";
|
|
27
|
+
import { Checkbox } from "@/components/ui/checkbox";
|
|
28
|
+
import {
|
|
29
|
+
Select,
|
|
30
|
+
SelectContent,
|
|
31
|
+
SelectItem,
|
|
32
|
+
SelectTrigger,
|
|
33
|
+
SelectValue,
|
|
34
|
+
} from "@/components/ui/select";
|
|
35
|
+
|
|
36
|
+
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
|
37
|
+
import { Pie, PieChart, Label } from "recharts";
|
|
38
|
+
|
|
39
|
+
const chartData = [
|
|
40
|
+
{ browser: "chrome", visitors: 70, fill: "var(--color-chrome)" },
|
|
41
|
+
{ browser: "safari", visitors: 30, fill: "var(--color-safari)" },
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
const chartConfig = {
|
|
45
|
+
visitors: {
|
|
46
|
+
label: "Sessions",
|
|
47
|
+
},
|
|
48
|
+
chrome: {
|
|
49
|
+
label: "Completed",
|
|
50
|
+
color: "hsl(217, 91%, 60%)", // Blue-600
|
|
51
|
+
},
|
|
52
|
+
safari: {
|
|
53
|
+
label: "Remaining",
|
|
54
|
+
color: "hsl(210, 40%, 96%)", // Slate-100 (or similar light color for empty part)
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const FitnessPage = () => {
|
|
59
|
+
return (
|
|
60
|
+
<div className="flex min-h-screen bg-neutral-100 font-sans text-slate-900">
|
|
61
|
+
{/* <Sidebar /> */}
|
|
62
|
+
<main className="flex-1 flex flex-col min-w-0 overflow-hidden">
|
|
63
|
+
<DashboardHeader />
|
|
64
|
+
<div className="flex-1 overflow-auto p-8">
|
|
65
|
+
<div className="mb-8">
|
|
66
|
+
<h1 className="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
|
67
|
+
Good morning, Summer <span className="text-2xl">👋</span>
|
|
68
|
+
</h1>
|
|
69
|
+
<p className="text-sm text-gray-500 mt-1">Saturday, 26 Oct 2024</p>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<div className="flex gap-4 mb-6">
|
|
73
|
+
<TotalClientCard />
|
|
74
|
+
<TrainingSessionsCard />
|
|
75
|
+
<TrainingSessionsCard />
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div className="flex gap-6">
|
|
79
|
+
<AppointmentCard />
|
|
80
|
+
<ClientProgressCard />
|
|
81
|
+
{/* <AISuggestionsCard /> */}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</main>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const Sidebar = () => (
|
|
90
|
+
<aside className="w-64 bg-white border-r border-gray-100 flex flex-col hidden md:flex">
|
|
91
|
+
<div className="p-6 flex items-center gap-2">
|
|
92
|
+
<div className="h-8 w-8 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold">
|
|
93
|
+
<Dumbbell size={16} />
|
|
94
|
+
</div>
|
|
95
|
+
<span className="text-xl font-bold tracking-tight">Fitrack</span>
|
|
96
|
+
<Button variant="ghost" size="icon" className="ml-auto h-8 w-8 bg-gray-100 rounded-md">
|
|
97
|
+
<ChevronLeft size={16} />
|
|
98
|
+
</Button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
<div className="px-4 py-2">
|
|
102
|
+
<p className="text-xs font-semibold text-gray-400 mb-4 px-2">MAIN</p>
|
|
103
|
+
<nav className="space-y-1">
|
|
104
|
+
<NavItem icon={LayoutDashboard} label="Today" active />
|
|
105
|
+
<NavItem icon={MessageSquare} label="Weekly" />
|
|
106
|
+
<NavItem icon={Users} label="Goals" />
|
|
107
|
+
<NavItem icon={BarChart2} label="Activity" />
|
|
108
|
+
<NavItem icon={Dumbbell} label="Trends" />
|
|
109
|
+
<NavItem icon={Dumbbell} label="Settings" />
|
|
110
|
+
</nav>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div className="px-4 py-2 mt-auto mb-8">
|
|
114
|
+
<p className="text-xs font-semibold text-gray-400 mb-4 px-2">SUPPORT</p>
|
|
115
|
+
<nav className="space-y-1">
|
|
116
|
+
<NavItem icon={Settings} label="Setting" />
|
|
117
|
+
<NavItem icon={HelpCircle} label="Help" />
|
|
118
|
+
</nav>
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div className="p-4 border-t border-gray-100">
|
|
122
|
+
<div className="flex items-center gap-3 p-2 rounded-lg hover:bg-gray-50 cursor-pointer">
|
|
123
|
+
<Avatar>
|
|
124
|
+
<AvatarImage src="https://github.com/shadcn.png" />
|
|
125
|
+
<AvatarFallback>SY</AvatarFallback>
|
|
126
|
+
</Avatar>
|
|
127
|
+
<div className="flex-1 min-w-0">
|
|
128
|
+
<p className="text-sm font-medium text-gray-900 truncate">Summer Yuri</p>
|
|
129
|
+
<p className="text-xs text-gray-500 truncate">Trainer</p>
|
|
130
|
+
</div>
|
|
131
|
+
<ChevronLeft className="h-4 w-4 text-gray-400 rotate-270" />
|
|
132
|
+
</div>
|
|
133
|
+
<p className="text-[10px] text-gray-400 mt-4 text-center">
|
|
134
|
+
©2024 Fitrack. All right reserved.
|
|
135
|
+
</p>
|
|
136
|
+
</div>
|
|
137
|
+
</aside>
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const DashboardHeader = () => (
|
|
141
|
+
<header className="bg-white border-b border-gray-100 h-16 flex items-center justify-between px-8">
|
|
142
|
+
<div className="flex items-center text-sm text-gray-500">
|
|
143
|
+
<span>Main</span>
|
|
144
|
+
<span className="mx-2">/</span>
|
|
145
|
+
<span className="font-medium text-gray-900">Dashboard</span>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div className="flex items-center gap-4">
|
|
149
|
+
<div className="relative w-64">
|
|
150
|
+
<Search className="absolute left-2.5 top-2.5 h-4 w-4 text-gray-400" />
|
|
151
|
+
<Input
|
|
152
|
+
type="search"
|
|
153
|
+
placeholder="Search"
|
|
154
|
+
className="pl-9 bg-gray-50 border-none focus-visible:ring-1"
|
|
155
|
+
/>
|
|
156
|
+
<div className="absolute right-2.5 top-2.5 flex items-center gap-1">
|
|
157
|
+
<span className="text-[10px] text-gray-400 border border-gray-200 rounded px-1">⌘/</span>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
<Button variant="ghost" size="icon" className="text-gray-500">
|
|
161
|
+
<Bell size={20} />
|
|
162
|
+
</Button>
|
|
163
|
+
</div>
|
|
164
|
+
</header>
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const TotalClientCard = () => (
|
|
168
|
+
<Card className="w-full border-none shadow-sm">
|
|
169
|
+
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
170
|
+
<div className="flex items-center gap-2">
|
|
171
|
+
<div className="h-8 w-8 rounded-full bg-cyan-50 flex items-center justify-center text-cyan-500">
|
|
172
|
+
<Users size={16} />
|
|
173
|
+
</div>
|
|
174
|
+
<CardTitle className="text-base font-medium">Total Client</CardTitle>
|
|
175
|
+
</div>
|
|
176
|
+
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
177
|
+
<MoreHorizontal size={16} />
|
|
178
|
+
</Button>
|
|
179
|
+
</CardHeader>
|
|
180
|
+
<CardContent>
|
|
181
|
+
<div className="flex items-end gap-4 mb-8">
|
|
182
|
+
<span className="text-4xl font-bold">2,000</span>
|
|
183
|
+
<Badge variant="secondary" className="bg-green-50 text-green-600 hover:bg-green-100 mb-1">
|
|
184
|
+
<div className="h-3 w-3 rounded-full bg-green-500 flex items-center justify-center mr-1">
|
|
185
|
+
<Check size={8} className="text-white" />
|
|
186
|
+
</div>
|
|
187
|
+
25%
|
|
188
|
+
</Badge>
|
|
189
|
+
<span className="text-sm text-gray-500 mb-1">vs last month</span>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div className="grid grid-cols-3 gap-8">
|
|
193
|
+
<StatColumn label="Intermediate" value="750" percentage="37.5%" color="bg-blue-200" />
|
|
194
|
+
<StatColumn label="Advanced" value="550" percentage="27.5%" color="bg-blue-600" />
|
|
195
|
+
</div>
|
|
196
|
+
</CardContent>
|
|
197
|
+
</Card>
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const TrainingSessionsCard = () => (
|
|
201
|
+
<Card className="w-full border-none shadow-sm flex flex-col">
|
|
202
|
+
<CardHeader className="flex flex-row items-center justify-between pb-2">
|
|
203
|
+
<div className="flex items-center gap-2">
|
|
204
|
+
<div className="h-8 w-8 rounded-full bg-cyan-50 flex items-center justify-center text-cyan-500">
|
|
205
|
+
<Dumbbell size={16} />
|
|
206
|
+
</div>
|
|
207
|
+
<CardTitle className="text-base font-medium">
|
|
208
|
+
Number of training sessions
|
|
209
|
+
</CardTitle>
|
|
210
|
+
</div>
|
|
211
|
+
<Select defaultValue="week">
|
|
212
|
+
<SelectTrigger className="w-[100px] h-8 text-xs">
|
|
213
|
+
<SelectValue placeholder="Select" />
|
|
214
|
+
</SelectTrigger>
|
|
215
|
+
<SelectContent>
|
|
216
|
+
<SelectItem value="week">This Week</SelectItem>
|
|
217
|
+
<SelectItem value="month">This Month</SelectItem>
|
|
218
|
+
</SelectContent>
|
|
219
|
+
</Select>
|
|
220
|
+
</CardHeader>
|
|
221
|
+
<CardContent className="flex-1 flex flex-col items-center justify-center pb-6">
|
|
222
|
+
<div className="flex-1 w-full min-h-[200px] flex items-center justify-center relative">
|
|
223
|
+
<ChartContainer
|
|
224
|
+
config={chartConfig}
|
|
225
|
+
className="mx-auto aspect-square max-h-[200px]"
|
|
226
|
+
>
|
|
227
|
+
<PieChart>
|
|
228
|
+
<ChartTooltip
|
|
229
|
+
cursor={false}
|
|
230
|
+
content={<ChartTooltipContent hideLabel />}
|
|
231
|
+
/>
|
|
232
|
+
<Pie
|
|
233
|
+
data={chartData}
|
|
234
|
+
dataKey="visitors"
|
|
235
|
+
nameKey="browser"
|
|
236
|
+
innerRadius={60}
|
|
237
|
+
strokeWidth={5}
|
|
238
|
+
>
|
|
239
|
+
<Label
|
|
240
|
+
content={({ viewBox }) => {
|
|
241
|
+
if (viewBox && "cx" in viewBox && "cy" in viewBox) {
|
|
242
|
+
return (
|
|
243
|
+
<text
|
|
244
|
+
x={viewBox.cx}
|
|
245
|
+
y={viewBox.cy}
|
|
246
|
+
textAnchor="middle"
|
|
247
|
+
dominantBaseline="middle"
|
|
248
|
+
>
|
|
249
|
+
<tspan
|
|
250
|
+
x={viewBox.cx}
|
|
251
|
+
y={viewBox.cy}
|
|
252
|
+
className="fill-gray-400 text-xs"
|
|
253
|
+
>
|
|
254
|
+
Total
|
|
255
|
+
</tspan>
|
|
256
|
+
<tspan
|
|
257
|
+
x={viewBox.cx}
|
|
258
|
+
y={(viewBox.cy || 0) + 24}
|
|
259
|
+
className="fill-foreground text-3xl font-bold"
|
|
260
|
+
>
|
|
261
|
+
16
|
|
262
|
+
</tspan>
|
|
263
|
+
</text>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
268
|
+
</Pie>
|
|
269
|
+
</PieChart>
|
|
270
|
+
</ChartContainer>
|
|
271
|
+
{/* Floating badge for 70% */}
|
|
272
|
+
<div className="absolute top-1/4 right-1/4 transform translate-x-2 -translate-y-2 bg-white px-2 py-1 rounded-full shadow-sm border text-xs font-bold text-gray-700 z-10">
|
|
273
|
+
70%
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
|
|
277
|
+
<div className="mt-4 flex items-center gap-2 text-sm">
|
|
278
|
+
<Badge
|
|
279
|
+
variant="secondary"
|
|
280
|
+
className="bg-green-50 text-green-600 hover:bg-green-100"
|
|
281
|
+
>
|
|
282
|
+
<div className="h-3 w-3 rounded-full bg-green-500 flex items-center justify-center mr-1">
|
|
283
|
+
<Check size={8} className="text-white" />
|
|
284
|
+
</div>
|
|
285
|
+
15%
|
|
286
|
+
</Badge>
|
|
287
|
+
<span className="text-gray-500">vs last week</span>
|
|
288
|
+
</div>
|
|
289
|
+
|
|
290
|
+
<div className="w-full mt-6 space-y-2">
|
|
291
|
+
<div className="flex flex-col justify-between text-xs">
|
|
292
|
+
<span className="flex gap-2">
|
|
293
|
+
<div className="w-2 h-2 rounded-full bg-[var(--color-chrome)]"></div>{" "}
|
|
294
|
+
Weekly session goals
|
|
295
|
+
</span>
|
|
296
|
+
<span className="font-medium text-green-600">
|
|
297
|
+
20 Sessions
|
|
298
|
+
</span>
|
|
299
|
+
</div>
|
|
300
|
+
<div className="flex justify-between text-xs">
|
|
301
|
+
<span className="flex items-center gap-2">
|
|
302
|
+
<div className="w-2 h-2 rounded-full bg-[var(--color-safari)]"></div>{" "}
|
|
303
|
+
Average sessions per client
|
|
304
|
+
</span>
|
|
305
|
+
<span className="font-medium text-green-600">
|
|
306
|
+
3 Sessions
|
|
307
|
+
</span>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
</CardContent>
|
|
311
|
+
</Card>
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const AppointmentCard = () => (
|
|
315
|
+
<Card className="w-full border-none shadow-sm">
|
|
316
|
+
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
|
317
|
+
<div className="flex items-center gap-2">
|
|
318
|
+
<div className="h-8 w-8 rounded-full bg-cyan-50 flex items-center justify-center text-cyan-500">
|
|
319
|
+
<CalendarIcon size={16} />
|
|
320
|
+
</div>
|
|
321
|
+
<CardTitle className="text-base font-medium">Appointment</CardTitle>
|
|
322
|
+
</div>
|
|
323
|
+
<Button variant="outline" size="sm" className="h-8 text-xs font-normal text-gray-500">
|
|
324
|
+
<CalendarIcon size={12} className="mr-2" />
|
|
325
|
+
21 Oct 2024 - 27 Oct 2024
|
|
326
|
+
</Button>
|
|
327
|
+
</CardHeader>
|
|
328
|
+
<CardContent>
|
|
329
|
+
{/* Calendar Strip */}
|
|
330
|
+
<div className="flex justify-between mb-6">
|
|
331
|
+
{['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].map((day, i) => (
|
|
332
|
+
<div key={day} className={`flex flex-col items-center p-2 rounded-lg ${i === 5 ? 'bg-cyan-500 text-white' : 'hover:bg-gray-50'}`}>
|
|
333
|
+
<span className={`text-[10px] ${i === 5 ? 'text-white' : 'text-gray-400'}`}>{day}</span>
|
|
334
|
+
<span className="text-sm font-medium mt-1">{21 + i}</span>
|
|
335
|
+
</div>
|
|
336
|
+
))}
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div className="flex items-center gap-2 mb-4">
|
|
340
|
+
<div className="relative flex-1">
|
|
341
|
+
<Search className="absolute left-2.5 top-2.5 h-3 w-3 text-gray-400" />
|
|
342
|
+
<Input placeholder="Search client" className="pl-8 h-9 text-xs bg-white" />
|
|
343
|
+
</div>
|
|
344
|
+
<Button variant="outline" size="sm" className="h-9 px-3">
|
|
345
|
+
<Filter size={14} className="mr-2" /> Filter
|
|
346
|
+
</Button>
|
|
347
|
+
</div>
|
|
348
|
+
|
|
349
|
+
<div className="space-y-3">
|
|
350
|
+
<AppointmentItem name="David Lee" time="08:30 AM" type="Exercise" status="On Schedule" image="https://github.com/shadcn.png" />
|
|
351
|
+
<AppointmentItem name="Sarah Kim" time="03:00 PM" type="Consultation" status="Cancel" image="https://github.com/shadcn.png" />
|
|
352
|
+
<AppointmentItem name="Ihan Dua" time="05:00 PM" type="Exercise" status="On Schedule" image="https://github.com/shadcn.png" />
|
|
353
|
+
</div>
|
|
354
|
+
</CardContent>
|
|
355
|
+
</Card>
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
const ClientProgressCard = () => (
|
|
359
|
+
<Card className="w-full border-none shadow-sm">
|
|
360
|
+
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
|
361
|
+
<div className="flex items-center gap-2">
|
|
362
|
+
<div className="h-8 w-8 rounded-full bg-cyan-50 flex items-center justify-center text-cyan-500">
|
|
363
|
+
<BarChart2 size={16} />
|
|
364
|
+
</div>
|
|
365
|
+
<CardTitle className="text-base font-medium">Client Progress</CardTitle>
|
|
366
|
+
</div>
|
|
367
|
+
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
368
|
+
<MoreHorizontal size={16} />
|
|
369
|
+
</Button>
|
|
370
|
+
</CardHeader>
|
|
371
|
+
<CardContent>
|
|
372
|
+
<div className="flex bg-gray-50 p-1 rounded-lg mb-6">
|
|
373
|
+
<Button variant="ghost" className="flex-1 bg-white shadow-sm h-8 text-xs font-medium">Weekly Target</Button>
|
|
374
|
+
<Button variant="ghost" className="flex-1 h-8 text-xs text-gray-500">Monthly Target</Button>
|
|
375
|
+
</div>
|
|
376
|
+
|
|
377
|
+
<div className="space-y-6">
|
|
378
|
+
<ProgressItem name="Lucas Kim" status="On Track" value={75} color="bg-blue-600" />
|
|
379
|
+
<ProgressItem name="Sophia Chen" status="Overachieving" value={88} color="bg-cyan-500" />
|
|
380
|
+
<ProgressItem name="Ariana Lee" status="Below the target" value={40} color="bg-pink-500" />
|
|
381
|
+
</div>
|
|
382
|
+
</CardContent>
|
|
383
|
+
</Card>
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const AISuggestionsCard = () => (
|
|
387
|
+
<Card className="col-span-1 border-none shadow-sm">
|
|
388
|
+
<CardHeader className="flex flex-row items-center justify-between pb-4">
|
|
389
|
+
<div className="flex items-center gap-2">
|
|
390
|
+
<div className="h-8 w-8 rounded-full bg-cyan-50 flex items-center justify-center text-cyan-500">
|
|
391
|
+
<div className="relative">
|
|
392
|
+
<div className="absolute -top-1 -right-1 h-2 w-2 bg-yellow-400 rounded-full animate-pulse"></div>
|
|
393
|
+
<Users size={16} /> {/* Using Users as placeholder for sparkles/AI icon */}
|
|
394
|
+
</div>
|
|
395
|
+
<CardTitle className="text-base font-medium">AI Suggestions</CardTitle>
|
|
396
|
+
</div>
|
|
397
|
+
<Button variant="ghost" size="icon" className="h-8 w-8">
|
|
398
|
+
<MoreHorizontal size={16} />
|
|
399
|
+
</Button>
|
|
400
|
+
</div>
|
|
401
|
+
</CardHeader>
|
|
402
|
+
<CardContent>
|
|
403
|
+
<div className="space-y-4">
|
|
404
|
+
<SuggestionItem
|
|
405
|
+
text={<span>Suggest <span className="text-cyan-600 font-medium">Sarah Kim</span> to incorporate 10 minutes of stretching after each workout for better flexibility.</span>}
|
|
406
|
+
/>
|
|
407
|
+
<SuggestionItem
|
|
408
|
+
text={<span>Remind <span className="text-cyan-600 font-medium">Amanda Ros</span> to take short breaks during long cardio sessions to maintain energy levels.</span>}
|
|
409
|
+
/>
|
|
410
|
+
<SuggestionItem
|
|
411
|
+
text={<span>Recommend <span className="text-cyan-600 font-medium">Spring Alexander</span> foods like sweet potatoes, oats, and avocado.</span>}
|
|
412
|
+
/>
|
|
413
|
+
<SuggestionItem
|
|
414
|
+
text={<span>Encourage <span className="text-cyan-600 font-medium line-through">David Lee</span> to increase protein intake to support muscle recovery.</span>}
|
|
415
|
+
checked
|
|
416
|
+
/>
|
|
417
|
+
</div>
|
|
418
|
+
</CardContent>
|
|
419
|
+
</Card>
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
// Helper Components
|
|
423
|
+
|
|
424
|
+
const NavItem = ({ icon: Icon, label, active, badge }) => (
|
|
425
|
+
<div
|
|
426
|
+
className={`flex items-center justify-between px-3 py-2 rounded-lg cursor-pointer group ${active ? "bg-white shadow-sm border border-gray-100" : "hover:bg-gray-50"
|
|
427
|
+
}`}
|
|
428
|
+
>
|
|
429
|
+
<div className="flex items-center gap-3">
|
|
430
|
+
<Icon size={18} className={active ? "text-gray-900" : "text-gray-400 group-hover:text-gray-600"} />
|
|
431
|
+
<span className={`text-sm font-medium ${active ? "text-gray-900" : "text-gray-500 group-hover:text-gray-900"}`}>
|
|
432
|
+
{label}
|
|
433
|
+
</span>
|
|
434
|
+
</div>
|
|
435
|
+
{badge && (
|
|
436
|
+
<span className="bg-gray-100 text-gray-600 text-xs font-medium px-2 py-0.5 rounded-full">
|
|
437
|
+
{badge}
|
|
438
|
+
</span>
|
|
439
|
+
)}
|
|
440
|
+
</div>
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
const StatColumn = ({ label, value, percentage, color }) => (
|
|
444
|
+
<div>
|
|
445
|
+
<div className="flex items-center justify-between mb-2">
|
|
446
|
+
<span className="text-2xl font-bold">{value}</span>
|
|
447
|
+
<Button variant="ghost" size="icon" className="h-6 w-6 text-gray-300">
|
|
448
|
+
<Eye size={14} />
|
|
449
|
+
</Button>
|
|
450
|
+
</div>
|
|
451
|
+
<p className="text-sm text-gray-500 mb-3">{label}</p>
|
|
452
|
+
<div className="flex items-center gap-2">
|
|
453
|
+
<span className="text-sm font-medium">{percentage}</span>
|
|
454
|
+
<div className={`h-2 w-full rounded-full bg-gray-100 overflow-hidden`}>
|
|
455
|
+
<div className={`h-full ${color}`} style={{ width: percentage }}></div>
|
|
456
|
+
</div>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const AppointmentItem = ({ name, time, type, status, image }) => (
|
|
462
|
+
<div className="flex items-center justify-between p-3 border border-gray-100 rounded-xl bg-white">
|
|
463
|
+
<div className="flex items-center gap-3">
|
|
464
|
+
<Avatar className="h-10 w-10">
|
|
465
|
+
<AvatarImage src={image} />
|
|
466
|
+
<AvatarFallback>{name[0]}</AvatarFallback>
|
|
467
|
+
</Avatar>
|
|
468
|
+
<div>
|
|
469
|
+
<p className="text-sm font-semibold text-gray-900">{name}</p>
|
|
470
|
+
<div className="flex items-center gap-2 text-xs text-gray-500">
|
|
471
|
+
<span>{time}</span>
|
|
472
|
+
<span className="h-1 w-1 rounded-full bg-gray-300"></span>
|
|
473
|
+
<span>{type}</span>
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
<Badge variant="secondary" className={`font-normal ${status === 'Cancel' ? 'bg-pink-50 text-pink-500' : 'bg-blue-50 text-blue-600'}`}>
|
|
478
|
+
{status}
|
|
479
|
+
</Badge>
|
|
480
|
+
</div>
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
const ProgressItem = ({ name, status, value, color }) => (
|
|
484
|
+
<div>
|
|
485
|
+
<div className="flex items-center justify-between mb-2">
|
|
486
|
+
<div className="flex items-center gap-2">
|
|
487
|
+
<span className="text-sm font-semibold text-gray-900">{name}</span>
|
|
488
|
+
<span className="h-1 w-1 rounded-full bg-gray-300"></span>
|
|
489
|
+
<span className="text-xs text-gray-500">{status}</span>
|
|
490
|
+
</div>
|
|
491
|
+
<div className="flex items-center gap-2">
|
|
492
|
+
<Button variant="ghost" size="icon" className="h-6 w-6 text-gray-400">
|
|
493
|
+
<Eye size={14} />
|
|
494
|
+
</Button>
|
|
495
|
+
<Button variant="ghost" size="icon" className="h-6 w-6 text-gray-400">
|
|
496
|
+
<LinkIcon size={14} />
|
|
497
|
+
</Button>
|
|
498
|
+
</div>
|
|
499
|
+
</div>
|
|
500
|
+
<div className="flex items-center gap-3">
|
|
501
|
+
<Progress value={value} className="h-2" indicatorColor={color} />
|
|
502
|
+
<span className="text-xs font-medium text-gray-500 w-8 text-right">{value}%</span>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
const SuggestionItem = ({ text, checked }) => (
|
|
508
|
+
<div className="flex items-start gap-3">
|
|
509
|
+
<Checkbox id="terms" checked={checked} className="mt-1 data-[state=checked]:bg-cyan-500 data-[state=checked]:border-cyan-500" />
|
|
510
|
+
<label
|
|
511
|
+
htmlFor="terms"
|
|
512
|
+
className={`text-sm leading-relaxed ${checked ? 'text-gray-400 line-through' : 'text-gray-600'}`}
|
|
513
|
+
>
|
|
514
|
+
{text}
|
|
515
|
+
</label>
|
|
516
|
+
</div>
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
export default FitnessPage;
|