@wealthx/shadcn 1.5.12 → 1.5.14

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.
Files changed (40) hide show
  1. package/.turbo/turbo-build.log +79 -79
  2. package/CHANGELOG.md +12 -0
  3. package/dist/{chunk-TC43SMIN.mjs → chunk-7N6O3VPJ.mjs} +6 -6
  4. package/dist/{chunk-CPM6P63C.mjs → chunk-GMF7INNS.mjs} +59 -30
  5. package/dist/chunk-KICT4VQL.mjs +508 -0
  6. package/dist/{chunk-BXL74CM2.mjs → chunk-UXWNUMQA.mjs} +4 -4
  7. package/dist/chunk-V23CBULF.mjs +432 -0
  8. package/dist/components/ui/appointment-calendar-view.js +177 -176
  9. package/dist/components/ui/appointment-calendar-view.mjs +1 -1
  10. package/dist/components/ui/bank-statement-document-table.js +6 -6
  11. package/dist/components/ui/bank-statement-document-table.mjs +1 -1
  12. package/dist/components/ui/bank-statement-generate-dialog.js +164 -77
  13. package/dist/components/ui/bank-statement-generate-dialog.mjs +2 -1
  14. package/dist/components/ui/bank-statement-pdf-viewer.js +4 -4
  15. package/dist/components/ui/bank-statement-pdf-viewer.mjs +1 -1
  16. package/dist/components/ui/resource-center/index.js +1030 -0
  17. package/dist/components/ui/resource-center/index.mjs +29 -0
  18. package/dist/index.js +556 -380
  19. package/dist/index.mjs +17 -15
  20. package/dist/styles.css +1 -1
  21. package/package.json +4 -4
  22. package/src/components/index.tsx +2 -0
  23. package/src/components/ui/appointment-calendar-view.tsx +211 -199
  24. package/src/components/ui/bank-statement-document-table.tsx +12 -6
  25. package/src/components/ui/bank-statement-generate-dialog.tsx +125 -97
  26. package/src/components/ui/bank-statement-pdf-viewer.tsx +4 -4
  27. package/src/components/ui/resource-center/index.tsx +35 -0
  28. package/src/components/ui/resource-center/resource-cards.tsx +218 -0
  29. package/src/components/ui/resource-center/resource-carousel.tsx +122 -0
  30. package/src/components/ui/resource-center/resource-center-header.tsx +95 -0
  31. package/src/components/ui/resource-center/resource-email-editor-dialog.tsx +131 -0
  32. package/src/components/ui/resource-center/resource-modal.tsx +76 -0
  33. package/src/components/ui/resource-center/types.ts +81 -0
  34. package/src/styles/styles-css.ts +1 -1
  35. package/tsup.config.ts +1 -1
  36. package/dist/chunk-IODGRCQG.mjs +0 -438
  37. package/dist/chunk-XYWEGBAA.mjs +0 -348
  38. package/dist/components/ui/resource-center.js +0 -748
  39. package/dist/components/ui/resource-center.mjs +0 -24
  40. 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.12",
3
+ "version": "1.5.14",
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
  }
@@ -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 (e.g. pending request button + new appointment button) */
55
+ /** Slot rendered in the calendar toolbar right side */
64
56
  toolbarActions?: ReactNode;
65
57
  }
66
58
 
67
59
  // ---------------------------------------------------------------------------
68
- // Status styling
60
+ // Status config
69
61
  // ---------------------------------------------------------------------------
70
62
 
71
- const STATUS_COLORS: Record<AppointmentStatus, string> = {
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
- variant: "warning" | "success" | "destructive" | "info";
66
+ card: string;
67
+ badge: "warning" | "success" | "destructive" | "info";
82
68
  label: string;
83
- icon: ReactNode;
84
69
  }
85
70
  > = {
86
71
  pending: {
87
- variant: "warning",
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
- variant: "success",
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
- variant: "destructive",
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
- variant: "info",
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
- {/* Column header */}
152
- <div className="grid grid-cols-[48px_1fr] border-b border-border">
153
- <div className="border-r border-border" />
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-xs font-medium tracking-wide ${isToday ? "font-semibold text-primary" : "text-muted-foreground"}`}
207
+ className={`text-caption tracking-wide ${isToday ? "font-semibold text-primary" : "text-muted-foreground"}`}
159
208
  >
160
- {dayLabel}
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 ? "bg-primary text-primary-foreground" : ""}`}
212
+ className={`flex h-10 w-10 items-center justify-center text-xl font-semibold ${isToday ? "text-primary" : ""}`}
164
213
  >
165
- {dayShort}
214
+ {d.getDate()}
166
215
  </span>
167
216
  </div>
168
217
  </div>
169
218
 
170
- {/* Hourly grid */}
171
- {hours.map((hour) => {
172
- const aptsAtHour = dayApts.filter(
173
- (a) => getHourFromTime(a.timeStart) === hour,
174
- );
175
- return (
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
- className={`flex flex-col gap-1.5 p-1.5 ${isToday ? "bg-primary/5" : ""}`}
226
+ key={hour}
227
+ className="flex flex-1 border-b border-border/40 last:border-b-0"
189
228
  >
190
- {aptsAtHour.map((apt) => (
191
- <Button
192
- key={apt.id}
193
- type="button"
194
- variant="ghost"
195
- onClick={() => onSelectAppointment?.(apt)}
196
- className={`h-auto w-full justify-start gap-3 border-l-2 px-3 py-2 text-left hover:opacity-80 ${STATUS_COLORS[apt.status]}`}
197
- >
198
- <Avatar className="h-7 w-7 shrink-0">
199
- <AvatarFallback className="text-xs">
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
- </div>
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
- {/* Day headers */}
244
- <div
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={`flex flex-col items-center border-r border-border py-2 last:border-r-0 ${day.date === today ? "bg-primary/5" : ""}`}
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-xs font-medium ${day.date === today ? "font-semibold text-primary" : "text-muted-foreground"}`}
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-sm font-semibold ${day.date === today ? "text-primary" : ""}`}
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
- {/* Time rows */}
269
- {hours.map((hour) => (
270
- <div
271
- key={hour}
272
- className="grid border-b border-border/40 last:border-b-0"
273
- style={{
274
- gridTemplateColumns: `48px repeat(${weekDays.length}, 1fr)`,
275
- }}
276
- >
277
- <div className="flex items-start justify-end border-r border-border pr-2 pt-1">
278
- <span className="text-[10px] text-muted-foreground">
279
- {formatHour(hour)}
280
- </span>
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
- {weekDays.map((day) => {
283
- const aptsAtCell = appointments.filter(
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
- {/* Day-of-week header row */}
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-xs font-medium text-muted-foreground">
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
- {/* Week rows */}
374
- {grid.map((week, wi) => (
375
- <div
376
- key={wi}
377
- className="grid grid-cols-7 border-b border-border last:border-b-0"
378
- >
379
- {week.map((day, di) => {
380
- const iso = day ? toIso(day) : null;
381
- const dayApts = iso
382
- ? appointments.filter((a) => a.date === iso)
383
- : [];
384
- const isToday = iso === today;
385
-
386
- return (
387
- <div
388
- key={di}
389
- className={`min-h-[80px] border-r border-border/40 p-1 last:border-r-0 ${!day ? "bg-muted/20" : ""} ${isToday ? "bg-primary/5" : ""}`}
390
- >
391
- {day && (
392
- <>
393
- <div
394
- className={`mb-1 flex h-6 w-6 items-center justify-center text-xs font-semibold ${isToday ? "bg-primary text-primary-foreground" : "text-foreground"}`}
395
- >
396
- {day.getDate()}
397
- </div>
398
- <div className="flex flex-col gap-0.5">
399
- {dayApts.slice(0, 2).map((apt) => (
400
- <Button
401
- key={apt.id}
402
- type="button"
403
- variant="ghost"
404
- onClick={() => onSelectAppointment?.(apt)}
405
- className={`h-auto w-full justify-start truncate border-l-2 px-1 py-0.5 text-left text-[10px] font-medium hover:opacity-80 ${STATUS_COLORS[apt.status]}`}
406
- >
407
- {apt.timeStart} {apt.clientName}
408
- </Button>
409
- ))}
410
- {dayApts.length > 2 && (
411
- <p className="px-1 text-[10px] text-muted-foreground">
412
- +{dayApts.length - 2} more
413
- </p>
414
- )}
415
- </div>
416
- </>
417
- )}
418
- </div>
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-sm font-medium">{periodLabel}</p>
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 value="day" className="mt-0 max-h-[75vh] overflow-y-auto">
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 value="week" className="mt-0 max-h-[75vh] overflow-y-auto">
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 max-h-[75vh] overflow-y-auto"
526
+ className="mt-0 min-h-0 flex-1 flex flex-col overflow-y-auto"
515
527
  >
516
528
  <MonthView
517
529
  appointments={appointments}
@@ -117,7 +117,7 @@ export function BankStatementDocumentTable({
117
117
  </div>
118
118
 
119
119
  {/* ── Table ── */}
120
- <Table>
120
+ <Table className="table-fixed">
121
121
  <TableHeader>
122
122
  <TableRow>
123
123
  <TableHead className="w-10">
@@ -130,8 +130,8 @@ export function BankStatementDocumentTable({
130
130
  />
131
131
  </TableHead>
132
132
  <TableHead>Name</TableHead>
133
- <TableHead>Type</TableHead>
134
- <TableHead>Date</TableHead>
133
+ <TableHead className="w-36">Type</TableHead>
134
+ <TableHead className="w-32">Date</TableHead>
135
135
  <TableHead className="w-20">Action</TableHead>
136
136
  </TableRow>
137
137
  </TableHeader>
@@ -167,7 +167,7 @@ export function BankStatementDocumentTable({
167
167
  <TableCell>
168
168
  <p
169
169
  className={cn(
170
- "text-body-medium max-w-[360px] overflow-hidden text-ellipsis whitespace-nowrap font-semibold align-bottom inline-block",
170
+ "truncate text-body-medium font-semibold",
171
171
  doc.hasDownload && doc.statementReady
172
172
  ? "cursor-pointer text-primary hover:underline"
173
173
  : "cursor-default",
@@ -182,8 +182,14 @@ export function BankStatementDocumentTable({
182
182
  </p>
183
183
  </TableCell>
184
184
 
185
- <TableCell>{doc.type}</TableCell>
186
- <TableCell>{doc.updatedDate}</TableCell>
185
+ <TableCell>
186
+ <span className="truncate text-body-medium">{doc.type}</span>
187
+ </TableCell>
188
+ <TableCell>
189
+ <span className="truncate text-body-medium">
190
+ {doc.updatedDate}
191
+ </span>
192
+ </TableCell>
187
193
 
188
194
  {/* Actions */}
189
195
  <TableCell>