@wealthx/shadcn 1.5.12 → 1.5.13
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/.turbo/turbo-build.log +74 -74
- package/CHANGELOG.md +6 -0
- package/dist/{chunk-CPM6P63C.mjs → chunk-BF5FKUF6.mjs} +53 -24
- package/dist/chunk-KICT4VQL.mjs +508 -0
- package/dist/chunk-V23CBULF.mjs +432 -0
- package/dist/components/ui/appointment-calendar-view.js +177 -176
- package/dist/components/ui/appointment-calendar-view.mjs +1 -1
- package/dist/components/ui/bank-statement-generate-dialog.js +163 -76
- package/dist/components/ui/bank-statement-generate-dialog.mjs +2 -1
- package/dist/components/ui/resource-center/index.js +1030 -0
- package/dist/components/ui/resource-center/index.mjs +29 -0
- package/dist/index.js +540 -364
- package/dist/index.mjs +15 -13
- package/dist/styles.css +1 -1
- package/package.json +4 -4
- package/src/components/index.tsx +2 -0
- package/src/components/ui/appointment-calendar-view.tsx +211 -199
- package/src/components/ui/bank-statement-generate-dialog.tsx +125 -97
- package/src/components/ui/resource-center/index.tsx +35 -0
- package/src/components/ui/resource-center/resource-cards.tsx +218 -0
- package/src/components/ui/resource-center/resource-carousel.tsx +122 -0
- package/src/components/ui/resource-center/resource-center-header.tsx +95 -0
- package/src/components/ui/resource-center/resource-email-editor-dialog.tsx +131 -0
- package/src/components/ui/resource-center/resource-modal.tsx +76 -0
- package/src/components/ui/resource-center/types.ts +81 -0
- package/src/styles/styles-css.ts +1 -1
- package/tsup.config.ts +1 -1
- package/dist/chunk-IODGRCQG.mjs +0 -438
- package/dist/chunk-XYWEGBAA.mjs +0 -348
- package/dist/components/ui/resource-center.js +0 -748
- package/dist/components/ui/resource-center.mjs +0 -24
- package/src/components/ui/resource-center.tsx +0 -539
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wealthx/shadcn",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.13",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -949,9 +949,9 @@
|
|
|
949
949
|
"require": "./dist/components/ui/transactions-summary-block.js"
|
|
950
950
|
},
|
|
951
951
|
"./resource-center": {
|
|
952
|
-
"types": "./src/components/ui/resource-center.tsx",
|
|
953
|
-
"import": "./dist/components/ui/resource-center.mjs",
|
|
954
|
-
"require": "./dist/components/ui/resource-center.js"
|
|
952
|
+
"types": "./src/components/ui/resource-center/index.tsx",
|
|
953
|
+
"import": "./dist/components/ui/resource-center/index.mjs",
|
|
954
|
+
"require": "./dist/components/ui/resource-center/index.js"
|
|
955
955
|
}
|
|
956
956
|
}
|
|
957
957
|
}
|
package/src/components/index.tsx
CHANGED
|
@@ -1262,6 +1262,7 @@ export {
|
|
|
1262
1262
|
ResourceCarousel,
|
|
1263
1263
|
ResourceCenterHeader,
|
|
1264
1264
|
ResourceDocumentCard,
|
|
1265
|
+
ResourceEmailEditorDialog,
|
|
1265
1266
|
ResourceEmailTemplateCard,
|
|
1266
1267
|
ResourceModal,
|
|
1267
1268
|
ResourceVideoCard,
|
|
@@ -1271,6 +1272,7 @@ export type {
|
|
|
1271
1272
|
ResourceCenterHeaderProps,
|
|
1272
1273
|
ResourceDocumentCardProps,
|
|
1273
1274
|
ResourceDocumentItem,
|
|
1275
|
+
ResourceEmailEditorDialogProps,
|
|
1274
1276
|
ResourceEmailTemplateCardProps,
|
|
1275
1277
|
ResourceEmailTemplateItem,
|
|
1276
1278
|
ResourceModalAttachment,
|
|
@@ -4,18 +4,10 @@ import { Badge } from "./badge";
|
|
|
4
4
|
import { Button } from "./button";
|
|
5
5
|
import { Separator } from "./separator";
|
|
6
6
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs";
|
|
7
|
-
import {
|
|
8
|
-
Check,
|
|
9
|
-
ChevronLeft,
|
|
10
|
-
ChevronRight,
|
|
11
|
-
RefreshCw,
|
|
12
|
-
X,
|
|
13
|
-
CalendarClock,
|
|
14
|
-
} from "lucide-react";
|
|
7
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
15
8
|
import type { AppointmentStatus } from "./appointment-time-slot-picker";
|
|
16
9
|
import { formatWeekdayShort } from "@/lib/format-date";
|
|
17
10
|
|
|
18
|
-
// Re-export so consumers who import AppointmentStatus from this module still work
|
|
19
11
|
export type { AppointmentStatus } from "./appointment-time-slot-picker";
|
|
20
12
|
|
|
21
13
|
export interface AppointmentCalendarItem {
|
|
@@ -60,48 +52,41 @@ export interface AppointmentCalendarViewProps {
|
|
|
60
52
|
onToday?: () => void;
|
|
61
53
|
onViewChange?: (view: "day" | "week" | "month") => void;
|
|
62
54
|
onSelectAppointment?: (apt: AppointmentCalendarItem) => void;
|
|
63
|
-
/** Slot rendered in the calendar toolbar right side
|
|
55
|
+
/** Slot rendered in the calendar toolbar right side */
|
|
64
56
|
toolbarActions?: ReactNode;
|
|
65
57
|
}
|
|
66
58
|
|
|
67
59
|
// ---------------------------------------------------------------------------
|
|
68
|
-
// Status
|
|
60
|
+
// Status config
|
|
69
61
|
// ---------------------------------------------------------------------------
|
|
70
62
|
|
|
71
|
-
const
|
|
72
|
-
pending: "border-l-warning bg-warning/5",
|
|
73
|
-
confirmed: "border-l-success bg-success/5",
|
|
74
|
-
cancelled: "border-l-destructive bg-destructive/5",
|
|
75
|
-
rescheduled: "border-l-info bg-info/5",
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const STATUS_CONFIG: Record<
|
|
63
|
+
const STATUS: Record<
|
|
79
64
|
AppointmentStatus,
|
|
80
65
|
{
|
|
81
|
-
|
|
66
|
+
card: string;
|
|
67
|
+
badge: "warning" | "success" | "destructive" | "info";
|
|
82
68
|
label: string;
|
|
83
|
-
icon: ReactNode;
|
|
84
69
|
}
|
|
85
70
|
> = {
|
|
86
71
|
pending: {
|
|
87
|
-
|
|
72
|
+
card: "border-l-warning bg-warning/[0.06]",
|
|
73
|
+
badge: "warning",
|
|
88
74
|
label: "Pending",
|
|
89
|
-
icon: <CalendarClock size={12} />,
|
|
90
75
|
},
|
|
91
76
|
confirmed: {
|
|
92
|
-
|
|
77
|
+
card: "border-l-success bg-success/[0.06]",
|
|
78
|
+
badge: "success",
|
|
93
79
|
label: "Confirmed",
|
|
94
|
-
icon: <Check size={12} />,
|
|
95
80
|
},
|
|
96
81
|
cancelled: {
|
|
97
|
-
|
|
82
|
+
card: "border-l-destructive bg-destructive/[0.06]",
|
|
83
|
+
badge: "destructive",
|
|
98
84
|
label: "Cancelled",
|
|
99
|
-
icon: <X size={12} />,
|
|
100
85
|
},
|
|
101
86
|
rescheduled: {
|
|
102
|
-
|
|
87
|
+
card: "border-l-info bg-info/[0.06]",
|
|
88
|
+
badge: "info",
|
|
103
89
|
label: "Rescheduled",
|
|
104
|
-
icon: <RefreshCw size={12} />,
|
|
105
90
|
},
|
|
106
91
|
};
|
|
107
92
|
|
|
@@ -123,6 +108,75 @@ function getHourFromTime(time: string): number {
|
|
|
123
108
|
|
|
124
109
|
const DEFAULT_HOURS = [9, 10, 11, 12, 13, 14, 15, 16, 17];
|
|
125
110
|
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Shared sub-components
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
function TimeGutter({ hour }: { hour: number }) {
|
|
116
|
+
return (
|
|
117
|
+
<div className="flex w-16 shrink-0 items-start justify-end border-r border-border pr-3 pt-2">
|
|
118
|
+
<span className="text-caption text-muted-foreground">
|
|
119
|
+
{formatHour(hour)}
|
|
120
|
+
</span>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function DayCard({
|
|
126
|
+
apt,
|
|
127
|
+
onSelect,
|
|
128
|
+
}: {
|
|
129
|
+
apt: AppointmentCalendarItem;
|
|
130
|
+
onSelect?: (apt: AppointmentCalendarItem) => void;
|
|
131
|
+
}) {
|
|
132
|
+
return (
|
|
133
|
+
<Button
|
|
134
|
+
type="button"
|
|
135
|
+
variant="ghost"
|
|
136
|
+
onClick={() => onSelect?.(apt)}
|
|
137
|
+
className={`h-auto w-full justify-start gap-3 border-l-2 px-3 py-3 text-left hover:opacity-80 ${STATUS[apt.status].card}`}
|
|
138
|
+
>
|
|
139
|
+
<Avatar className="h-8 w-8 shrink-0">
|
|
140
|
+
<AvatarFallback className="text-caption">
|
|
141
|
+
{apt.clientAvatarInitials}
|
|
142
|
+
</AvatarFallback>
|
|
143
|
+
</Avatar>
|
|
144
|
+
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
145
|
+
<span className="text-body-small font-semibold">{apt.clientName}</span>
|
|
146
|
+
<span className="text-caption text-muted-foreground">
|
|
147
|
+
{apt.timeStart} – {apt.timeEnd}
|
|
148
|
+
</span>
|
|
149
|
+
</div>
|
|
150
|
+
<Badge variant={STATUS[apt.status].badge}>
|
|
151
|
+
{STATUS[apt.status].label}
|
|
152
|
+
</Badge>
|
|
153
|
+
</Button>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function AptChip({
|
|
158
|
+
apt,
|
|
159
|
+
onSelect,
|
|
160
|
+
className,
|
|
161
|
+
}: {
|
|
162
|
+
apt: AppointmentCalendarItem;
|
|
163
|
+
onSelect?: (apt: AppointmentCalendarItem) => void;
|
|
164
|
+
className?: string;
|
|
165
|
+
}) {
|
|
166
|
+
return (
|
|
167
|
+
<Button
|
|
168
|
+
type="button"
|
|
169
|
+
variant="ghost"
|
|
170
|
+
onClick={() => onSelect?.(apt)}
|
|
171
|
+
className={`h-auto w-full justify-start truncate border-l-2 text-left hover:opacity-80 ${STATUS[apt.status].card} ${className ?? ""}`}
|
|
172
|
+
>
|
|
173
|
+
<span className="truncate text-caption font-medium">
|
|
174
|
+
{apt.timeStart} {apt.clientName}
|
|
175
|
+
</span>
|
|
176
|
+
</Button>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
126
180
|
// ---------------------------------------------------------------------------
|
|
127
181
|
// Day view
|
|
128
182
|
// ---------------------------------------------------------------------------
|
|
@@ -141,82 +195,51 @@ function DayView({
|
|
|
141
195
|
onSelectAppointment?: (apt: AppointmentCalendarItem) => void;
|
|
142
196
|
}) {
|
|
143
197
|
const d = new Date(date + "T00:00:00");
|
|
144
|
-
const dayLabel = formatWeekdayShort(d);
|
|
145
|
-
const dayShort = d.getDate();
|
|
146
198
|
const isToday = date === today;
|
|
147
199
|
const dayApts = appointments.filter((a) => a.date === date);
|
|
148
200
|
|
|
149
201
|
return (
|
|
150
|
-
<div className="flex flex-col">
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
<div className="
|
|
154
|
-
<div
|
|
155
|
-
className={`flex flex-col items-center gap-1 py-3 ${isToday ? "bg-primary/5" : ""}`}
|
|
156
|
-
>
|
|
202
|
+
<div className="flex flex-1 flex-col">
|
|
203
|
+
<div className="flex shrink-0 border-b border-border">
|
|
204
|
+
<div className="w-16 shrink-0 border-r border-border" />
|
|
205
|
+
<div className="flex flex-1 flex-col items-center gap-1 py-4">
|
|
157
206
|
<span
|
|
158
|
-
className={`text-
|
|
207
|
+
className={`text-caption tracking-wide ${isToday ? "font-semibold text-primary" : "text-muted-foreground"}`}
|
|
159
208
|
>
|
|
160
|
-
{
|
|
209
|
+
{formatWeekdayShort(d)}
|
|
161
210
|
</span>
|
|
162
211
|
<span
|
|
163
|
-
className={`flex h-10 w-10 items-center justify-center text-xl font-semibold ${isToday ? "
|
|
212
|
+
className={`flex h-10 w-10 items-center justify-center text-xl font-semibold ${isToday ? "text-primary" : ""}`}
|
|
164
213
|
>
|
|
165
|
-
{
|
|
214
|
+
{d.getDate()}
|
|
166
215
|
</span>
|
|
167
216
|
</div>
|
|
168
217
|
</div>
|
|
169
218
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
<div
|
|
177
|
-
key={hour}
|
|
178
|
-
className="grid min-h-[60px] grid-cols-[48px_1fr] border-b border-border/40 last:border-b-0"
|
|
179
|
-
>
|
|
180
|
-
<div
|
|
181
|
-
className={`flex items-start justify-end border-r border-border pr-2 pt-1 ${isToday ? "bg-primary/5" : ""}`}
|
|
182
|
-
>
|
|
183
|
-
<span className="text-[10px] text-muted-foreground">
|
|
184
|
-
{formatHour(hour)}
|
|
185
|
-
</span>
|
|
186
|
-
</div>
|
|
219
|
+
<div className="flex flex-1 flex-col">
|
|
220
|
+
{hours.map((hour) => {
|
|
221
|
+
const aptsAtHour = dayApts.filter(
|
|
222
|
+
(a) => getHourFromTime(a.timeStart) === hour,
|
|
223
|
+
);
|
|
224
|
+
return (
|
|
187
225
|
<div
|
|
188
|
-
|
|
226
|
+
key={hour}
|
|
227
|
+
className="flex flex-1 border-b border-border/40 last:border-b-0"
|
|
189
228
|
>
|
|
190
|
-
{
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
{apt.clientAvatarInitials}
|
|
201
|
-
</AvatarFallback>
|
|
202
|
-
</Avatar>
|
|
203
|
-
<div className="flex min-w-0 flex-1 flex-col">
|
|
204
|
-
<span className="text-sm font-semibold">
|
|
205
|
-
{apt.clientName}
|
|
206
|
-
</span>
|
|
207
|
-
<span className="text-xs text-muted-foreground">
|
|
208
|
-
{apt.timeStart} – {apt.timeEnd}
|
|
209
|
-
</span>
|
|
210
|
-
</div>
|
|
211
|
-
<Badge variant={STATUS_CONFIG[apt.status].variant}>
|
|
212
|
-
{STATUS_CONFIG[apt.status].label}
|
|
213
|
-
</Badge>
|
|
214
|
-
</Button>
|
|
215
|
-
))}
|
|
229
|
+
<TimeGutter hour={hour} />
|
|
230
|
+
<div className="flex flex-1 flex-col gap-2 p-2">
|
|
231
|
+
{aptsAtHour.map((apt) => (
|
|
232
|
+
<DayCard
|
|
233
|
+
key={apt.id}
|
|
234
|
+
apt={apt}
|
|
235
|
+
onSelect={onSelectAppointment}
|
|
236
|
+
/>
|
|
237
|
+
))}
|
|
238
|
+
</div>
|
|
216
239
|
</div>
|
|
217
|
-
|
|
218
|
-
)
|
|
219
|
-
|
|
240
|
+
);
|
|
241
|
+
})}
|
|
242
|
+
</div>
|
|
220
243
|
</div>
|
|
221
244
|
);
|
|
222
245
|
}
|
|
@@ -239,25 +262,21 @@ function WeekView({
|
|
|
239
262
|
onSelectAppointment?: (apt: AppointmentCalendarItem) => void;
|
|
240
263
|
}) {
|
|
241
264
|
return (
|
|
242
|
-
<div className="flex flex-col overflow-x-auto">
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
className="grid border-b border-border"
|
|
246
|
-
style={{ gridTemplateColumns: `48px repeat(${weekDays.length}, 1fr)` }}
|
|
247
|
-
>
|
|
248
|
-
<div className="border-r border-border" />
|
|
265
|
+
<div className="flex flex-1 flex-col overflow-x-auto">
|
|
266
|
+
<div className="flex shrink-0 border-b border-border">
|
|
267
|
+
<div className="w-16 shrink-0 border-r border-border" />
|
|
249
268
|
{weekDays.map((day) => (
|
|
250
269
|
<div
|
|
251
270
|
key={day.date}
|
|
252
|
-
className=
|
|
271
|
+
className="flex flex-1 flex-col items-center border-r border-border py-3 last:border-r-0"
|
|
253
272
|
>
|
|
254
273
|
<span
|
|
255
|
-
className={`text-
|
|
274
|
+
className={`text-caption ${day.date === today ? "font-semibold text-primary" : "text-muted-foreground"}`}
|
|
256
275
|
>
|
|
257
276
|
{day.label}
|
|
258
277
|
</span>
|
|
259
278
|
<span
|
|
260
|
-
className={`text-
|
|
279
|
+
className={`text-body-small font-semibold ${day.date === today ? "text-primary" : ""}`}
|
|
261
280
|
>
|
|
262
281
|
{day.short}
|
|
263
282
|
</span>
|
|
@@ -265,46 +284,37 @@ function WeekView({
|
|
|
265
284
|
))}
|
|
266
285
|
</div>
|
|
267
286
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
287
|
+
<div className="flex flex-1 flex-col">
|
|
288
|
+
{hours.map((hour) => (
|
|
289
|
+
<div
|
|
290
|
+
key={hour}
|
|
291
|
+
className="flex flex-1 border-b border-border/40 last:border-b-0"
|
|
292
|
+
>
|
|
293
|
+
<TimeGutter hour={hour} />
|
|
294
|
+
{weekDays.map((day) => {
|
|
295
|
+
const aptsAtCell = appointments.filter(
|
|
296
|
+
(a) =>
|
|
297
|
+
a.date === day.date && getHourFromTime(a.timeStart) === hour,
|
|
298
|
+
);
|
|
299
|
+
return (
|
|
300
|
+
<div
|
|
301
|
+
key={day.date}
|
|
302
|
+
className="flex flex-1 flex-col overflow-hidden border-r border-border/40 p-1 last:border-r-0"
|
|
303
|
+
>
|
|
304
|
+
{aptsAtCell.map((apt) => (
|
|
305
|
+
<AptChip
|
|
306
|
+
key={apt.id}
|
|
307
|
+
apt={apt}
|
|
308
|
+
onSelect={onSelectAppointment}
|
|
309
|
+
className="px-2 py-1.5"
|
|
310
|
+
/>
|
|
311
|
+
))}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
})}
|
|
281
315
|
</div>
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
(a) =>
|
|
285
|
-
a.date === day.date && getHourFromTime(a.timeStart) === hour,
|
|
286
|
-
);
|
|
287
|
-
return (
|
|
288
|
-
<div
|
|
289
|
-
key={day.date}
|
|
290
|
-
className={`min-h-[44px] overflow-hidden border-r border-border/40 p-0.5 last:border-r-0 ${day.date === today ? "bg-primary/5" : ""}`}
|
|
291
|
-
>
|
|
292
|
-
{aptsAtCell.map((apt) => (
|
|
293
|
-
<Button
|
|
294
|
-
key={apt.id}
|
|
295
|
-
type="button"
|
|
296
|
-
variant="ghost"
|
|
297
|
-
className={`h-auto w-full justify-start truncate border-l-2 px-1 py-1 text-left text-xs font-medium hover:opacity-80 ${STATUS_COLORS[apt.status]}`}
|
|
298
|
-
onClick={() => onSelectAppointment?.(apt)}
|
|
299
|
-
>
|
|
300
|
-
{apt.timeStart} {apt.clientName}
|
|
301
|
-
</Button>
|
|
302
|
-
))}
|
|
303
|
-
</div>
|
|
304
|
-
);
|
|
305
|
-
})}
|
|
306
|
-
</div>
|
|
307
|
-
))}
|
|
316
|
+
))}
|
|
317
|
+
</div>
|
|
308
318
|
</div>
|
|
309
319
|
);
|
|
310
320
|
}
|
|
@@ -355,71 +365,68 @@ function MonthView({
|
|
|
355
365
|
`${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
356
366
|
|
|
357
367
|
return (
|
|
358
|
-
<div className="flex flex-col">
|
|
359
|
-
|
|
360
|
-
<div className="grid grid-cols-7 border-b border-border">
|
|
368
|
+
<div className="flex flex-1 flex-col">
|
|
369
|
+
<div className="grid shrink-0 grid-cols-7 border-b border-border">
|
|
361
370
|
{MONTH_DAY_HEADERS.map((d) => (
|
|
362
371
|
<div
|
|
363
372
|
key={d}
|
|
364
373
|
className="flex items-center justify-center border-r border-border/40 py-2 last:border-r-0"
|
|
365
374
|
>
|
|
366
|
-
<span className="text-
|
|
375
|
+
<span className="text-caption font-medium text-muted-foreground">
|
|
367
376
|
{d}
|
|
368
377
|
</span>
|
|
369
378
|
</div>
|
|
370
379
|
))}
|
|
371
380
|
</div>
|
|
372
381
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
</div>
|
|
422
|
-
))}
|
|
382
|
+
<div className="flex flex-1 flex-col">
|
|
383
|
+
{grid.map((week, wi) => (
|
|
384
|
+
<div
|
|
385
|
+
key={wi}
|
|
386
|
+
className="flex flex-1 border-b border-border last:border-b-0"
|
|
387
|
+
>
|
|
388
|
+
{week.map((day, di) => {
|
|
389
|
+
const iso = day ? toIso(day) : null;
|
|
390
|
+
const dayApts = iso
|
|
391
|
+
? appointments.filter((a) => a.date === iso)
|
|
392
|
+
: [];
|
|
393
|
+
const isToday = iso === today;
|
|
394
|
+
|
|
395
|
+
return (
|
|
396
|
+
<div
|
|
397
|
+
key={di}
|
|
398
|
+
className={`min-w-0 flex-1 border-r border-border/40 p-1.5 last:border-r-0 ${!day ? "bg-muted/20" : ""}`}
|
|
399
|
+
>
|
|
400
|
+
{day && (
|
|
401
|
+
<>
|
|
402
|
+
<div
|
|
403
|
+
className={`mb-1 flex h-6 w-6 items-center justify-center text-xs font-semibold ${isToday ? "text-primary" : "text-foreground"}`}
|
|
404
|
+
>
|
|
405
|
+
{day.getDate()}
|
|
406
|
+
</div>
|
|
407
|
+
<div className="flex flex-col gap-0.5">
|
|
408
|
+
{dayApts.slice(0, 2).map((apt) => (
|
|
409
|
+
<AptChip
|
|
410
|
+
key={apt.id}
|
|
411
|
+
apt={apt}
|
|
412
|
+
onSelect={onSelectAppointment}
|
|
413
|
+
className="px-1.5 py-1"
|
|
414
|
+
/>
|
|
415
|
+
))}
|
|
416
|
+
{dayApts.length > 2 && (
|
|
417
|
+
<p className="px-1 text-caption text-muted-foreground">
|
|
418
|
+
+{dayApts.length - 2} more
|
|
419
|
+
</p>
|
|
420
|
+
)}
|
|
421
|
+
</div>
|
|
422
|
+
</>
|
|
423
|
+
)}
|
|
424
|
+
</div>
|
|
425
|
+
);
|
|
426
|
+
})}
|
|
427
|
+
</div>
|
|
428
|
+
))}
|
|
429
|
+
</div>
|
|
423
430
|
</div>
|
|
424
431
|
);
|
|
425
432
|
}
|
|
@@ -447,8 +454,7 @@ export function AppointmentCalendarView({
|
|
|
447
454
|
const effectiveViewDate = viewDate ?? dayViewDate ?? today;
|
|
448
455
|
|
|
449
456
|
return (
|
|
450
|
-
<div className="flex flex-col border border-border bg-card">
|
|
451
|
-
{/* Toolbar */}
|
|
457
|
+
<div className="flex flex-1 flex-col min-h-0 border border-border bg-card">
|
|
452
458
|
<div className="flex items-center justify-between gap-4 px-4 py-3">
|
|
453
459
|
<div className="flex items-center gap-2">
|
|
454
460
|
<Button
|
|
@@ -459,7 +465,7 @@ export function AppointmentCalendarView({
|
|
|
459
465
|
>
|
|
460
466
|
<ChevronLeft className="h-3.5 w-3.5" />
|
|
461
467
|
</Button>
|
|
462
|
-
<p className="text-
|
|
468
|
+
<p className="text-label-medium">{periodLabel}</p>
|
|
463
469
|
<Button
|
|
464
470
|
variant="outline"
|
|
465
471
|
size="sm"
|
|
@@ -482,7 +488,7 @@ export function AppointmentCalendarView({
|
|
|
482
488
|
<Tabs
|
|
483
489
|
defaultValue={defaultView}
|
|
484
490
|
onValueChange={(v) => onViewChange?.(v as "day" | "week" | "month")}
|
|
485
|
-
className="flex flex-col"
|
|
491
|
+
className="flex flex-col flex-1 min-h-0"
|
|
486
492
|
>
|
|
487
493
|
<div className="px-4 pt-3">
|
|
488
494
|
<TabsList>
|
|
@@ -491,7 +497,10 @@ export function AppointmentCalendarView({
|
|
|
491
497
|
<TabsTrigger value="month">Month</TabsTrigger>
|
|
492
498
|
</TabsList>
|
|
493
499
|
</div>
|
|
494
|
-
<TabsContent
|
|
500
|
+
<TabsContent
|
|
501
|
+
value="day"
|
|
502
|
+
className="mt-0 min-h-0 flex-1 flex flex-col overflow-y-auto"
|
|
503
|
+
>
|
|
495
504
|
<DayView
|
|
496
505
|
appointments={appointments}
|
|
497
506
|
date={effectiveViewDate}
|
|
@@ -500,7 +509,10 @@ export function AppointmentCalendarView({
|
|
|
500
509
|
onSelectAppointment={onSelectAppointment}
|
|
501
510
|
/>
|
|
502
511
|
</TabsContent>
|
|
503
|
-
<TabsContent
|
|
512
|
+
<TabsContent
|
|
513
|
+
value="week"
|
|
514
|
+
className="mt-0 min-h-0 flex-1 flex flex-col overflow-y-auto"
|
|
515
|
+
>
|
|
504
516
|
<WeekView
|
|
505
517
|
appointments={appointments}
|
|
506
518
|
weekDays={weekDays}
|
|
@@ -511,7 +523,7 @@ export function AppointmentCalendarView({
|
|
|
511
523
|
</TabsContent>
|
|
512
524
|
<TabsContent
|
|
513
525
|
value="month"
|
|
514
|
-
className="mt-0
|
|
526
|
+
className="mt-0 min-h-0 flex-1 flex flex-col overflow-y-auto"
|
|
515
527
|
>
|
|
516
528
|
<MonthView
|
|
517
529
|
appointments={appointments}
|