diff --git a/frontend/src/app/(dashboard)/appointments/page.tsx b/frontend/src/app/(dashboard)/appointments/page.tsx index f66647d..45513c8 100644 --- a/frontend/src/app/(dashboard)/appointments/page.tsx +++ b/frontend/src/app/(dashboard)/appointments/page.tsx @@ -51,6 +51,7 @@ export default function AppointmentsPage() { const [bookingProviderName, setBookingProviderName] = useState(''); const [editingAppointmentId, setEditingAppointmentId] = useState(null); const [savingAppointment, setSavingAppointment] = useState(false); + const [deletingAppointment, setDeletingAppointment] = useState(false); const canManageAppointments = canEditAppointments(currentOrganization); @@ -221,13 +222,19 @@ export default function AppointmentsPage() { } } - async function handleDeleteAppointment(id: string) { + async function handleDeleteEditingAppointment() { + if (!activeEditingAppointment) { + return; + } if (!window.confirm('Remove this appointment?')) { return; } + setDeletingAppointment(true); toast.setError(''); try { - await appointmentsApi.remove(id); + await appointmentsApi.remove(activeEditingAppointment.id); + setBookingOpen(false); + setEditingAppointmentId(null); toast.showSuccess('Appointment removed.'); await loadSchedule(); } catch (err: unknown) { @@ -236,6 +243,8 @@ export default function AppointmentsPage() { ? String((err as { message: unknown }).message) : 'Could not delete appointment.'; toast.showError(message); + } finally { + setDeletingAppointment(false); } } @@ -289,8 +298,6 @@ export default function AppointmentsPage() { providers={providers} appointments={appointments} canBook={canManageAppointments && !isViewingPastDay} - canDelete={canManageAppointments} - onDeleteAppointment={(id) => void handleDeleteAppointment(id)} onSlotClick={(hour, uid, name) => handleSlotClick(hour, uid, name)} onAppointmentClick={(apt) => handleAppointmentClick(apt)} /> @@ -311,6 +318,9 @@ export default function AppointmentsPage() { }} onSubmit={handleSaveAppointment} loading={savingAppointment} + canDelete={canManageAppointments && !isViewingPastDay && !!activeEditingAppointment} + onDelete={() => void handleDeleteEditingAppointment()} + deleting={deletingAppointment} /> {isCreateOpen && ( diff --git a/frontend/src/app/(dashboard)/today/page.tsx b/frontend/src/app/(dashboard)/today/page.tsx index 7ac754e..b12f05b 100644 --- a/frontend/src/app/(dashboard)/today/page.tsx +++ b/frontend/src/app/(dashboard)/today/page.tsx @@ -12,7 +12,7 @@ export default function TodayPage() { return (

- Welcome back Babak !! + Welcome back!!

{showNoSubscriptionNotice && ( diff --git a/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx b/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx index acfb047..08d01a5 100644 --- a/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx +++ b/frontend/src/components/ui/appointments/AppointmentBookingModal.tsx @@ -31,6 +31,9 @@ interface AppointmentBookingModalProps { }) => Promise; editingAppointment?: AppointmentRecord | null; loading?: boolean; + canDelete?: boolean; + onDelete?: () => void | Promise; + deleting?: boolean; } export function AppointmentBookingModal({ @@ -44,6 +47,9 @@ export function AppointmentBookingModal({ onSubmit, editingAppointment = null, loading = false, + canDelete = false, + onDelete, + deleting = false, }: AppointmentBookingModalProps) { const [startTime, setStartTime] = useState('09:00'); const [endTime, setEndTime] = useState('10:00'); @@ -227,13 +233,34 @@ export function AppointmentBookingModal({ {error &&

{error}

} -
- - +
+ {editingAppointment && canDelete && onDelete ? ( + + ) : ( + + )} +
+ + +
diff --git a/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx b/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx index acb47a4..e64a14c 100644 --- a/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx +++ b/frontend/src/components/ui/appointments/AppointmentScheduleGrid.tsx @@ -1,12 +1,8 @@ 'use client'; -import { Trash2 } from 'lucide-react'; import type { AppointmentColumnProvider, AppointmentRecord } from '@/types/appointment'; import { formatHourLabel } from '@/lib/appointmentTime'; -import { - purposeDeleteIconClass, - purposeStyle, -} from '@/components/ui/appointments/appointmentPurposeStyles'; +import { purposeStyle } from '@/components/ui/appointments/appointmentPurposeStyles'; const HOUR_PX = 40; const HOURS = Array.from({ length: 24 }, (_, i) => i); @@ -27,13 +23,17 @@ function layoutBlock(apt: AppointmentRecord, day: Date): { top: string; height: return { top: `${top}%`, height: `${height}%` }; } +function appointmentDurationMinutes(apt: AppointmentRecord): number { + const start = new Date(apt.startAt).getTime(); + const end = new Date(apt.endAt).getTime(); + return Math.max(0, Math.round((end - start) / 60_000)); +} + interface AppointmentScheduleGridProps { day: Date; providers: AppointmentColumnProvider[]; appointments: AppointmentRecord[]; canBook: boolean; - canDelete?: boolean; - onDeleteAppointment?: (id: string) => void; onSlotClick: (hour: number, providerUserId: string, providerName: string) => void; onAppointmentClick?: (appointment: AppointmentRecord) => void; } @@ -43,8 +43,6 @@ export function AppointmentScheduleGrid({ providers, appointments, canBook, - canDelete = false, - onDeleteAppointment, onSlotClick, onAppointmentClick, }: AppointmentScheduleGridProps) { @@ -121,35 +119,22 @@ export function AppointmentScheduleGrid({ if (!pos) { return null; } + const durationMin = appointmentDurationMinutes(apt); return ( )} ); diff --git a/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts b/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts index 6230ad5..c0803ba 100644 --- a/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts +++ b/frontend/src/components/ui/appointments/appointmentPurposeStyles.ts @@ -23,19 +23,6 @@ export function purposeStyle(purpose: string): string { return APPOINTMENT_PURPOSE_STYLES[p] ?? 'bg-surface-elevated border-border text-text-secondary'; } -/** Trash icon — legend hues; `!` overrides global `.lucide { color: var(--color-icon) }`. */ -export function purposeDeleteIconClass(purpose: string): string { - const p = purpose as AppointmentPurpose; - const map: Record = { - consultation: '!text-purpose-consultation-fg', - filling: '!text-purpose-filling-fg', - endo: '!text-purpose-endo-fg', - visit: '!text-purpose-visit-fg', - hygiene: '!text-purpose-hygiene-fg', - }; - return map[p] ?? '!text-text-muted'; -} - /** Small swatch for legend (background + border only). */ export const APPOINTMENT_PURPOSE_LEGEND_SWATCH: Record = { consultation: 'bg-violet-500/85 border-violet-400/75',