improvement: some improvement done for appointment and treatment shared components.

This commit is contained in:
2026-07-17 11:21:08 +03:30
parent d2ad1fd7b6
commit 1b63bfff00
12 changed files with 204 additions and 38 deletions

View File

@@ -81,6 +81,10 @@ interface AppointmentScheduleGridProps {
appointments: AppointmentRecord[];
treatmentCatalog: TreatmentCatalogEntry[];
canBook: boolean;
/** Same message used for title + click toast when booking is blocked. */
slotBlockReason?: string | null;
/** Same message used for title + click toast when editing an appointment is blocked. */
appointmentEditBlockReason?: (appointment: AppointmentRecord) => string | null;
onSlotClick: (startMinute: number, providerUserId: string, providerName: string) => void;
onAppointmentClick?: (appointment: AppointmentRecord) => void;
onAppointmentOutsideHours?: (appointment: AppointmentRecord) => void;
@@ -92,6 +96,8 @@ export function AppointmentScheduleGrid({
appointments,
treatmentCatalog,
canBook,
slotBlockReason = null,
appointmentEditBlockReason,
onSlotClick,
onAppointmentClick,
onAppointmentOutsideHours,
@@ -161,9 +167,6 @@ export function AppointmentScheduleGrid({
});
return;
}
if (!canBook) {
return;
}
onAppointmentClick?.(apt);
}
@@ -216,7 +219,7 @@ export function AppointmentScheduleGrid({
return (
<div
key={hour}
className="absolute left-0 right-0 text-[11px] text-text-muted flex items-start justify-end pr-1.5 pt-0.5 border-b border-border/50"
className="absolute inset-x-0 text-[11px] text-text-muted flex items-center justify-center px-0.5 border-b border-border/50"
style={{ top, height }}
>
{formatMinuteLabel(hour * 60, locale)}
@@ -247,13 +250,15 @@ export function AppointmentScheduleGrid({
SCHEDULE_SLOT_MINUTES,
p.dayBlocks,
);
const slotDisabled = !canBook || columnFullyDisabled || !slotActive;
const structuralDisabled = columnFullyDisabled || !slotActive;
const bookingBlocked = Boolean(slotBlockReason) || !canBook;
const slotLooksDisabled = structuralDisabled || bookingBlocked;
return (
<button
key={`${p.userId}-${slotStartMinute}`}
type="button"
disabled={slotDisabled}
disabled={structuralDisabled}
title={
columnFullyDisabled
? p.hasWorkingHours
@@ -261,14 +266,16 @@ export function AppointmentScheduleGrid({
: t('slotHoursNotConfigured')
: !slotActive
? t('slotOutsideHours')
: slotDisabled
? t('slotCannotCreate')
: t('slotBookAt', {
time: formatMinuteLabel(slotStartMinute, locale),
})
: slotBlockReason
? slotBlockReason
: bookingBlocked
? t('slotCannotCreate')
: t('slotBookAt', {
time: formatMinuteLabel(slotStartMinute, locale),
})
}
className={`absolute left-0 right-0 border-b border-border/50 transition-colors ${
slotDisabled
slotLooksDisabled
? 'cursor-not-allowed bg-background-secondary/35 opacity-60'
: 'hover:bg-primary/8 cursor-pointer'
}`}
@@ -302,8 +309,10 @@ export function AppointmentScheduleGrid({
p.dayBlocks,
);
const patientName = `${apt.patient.firstName} ${apt.patient.lastName}`;
const editBlockReason = appointmentEditBlockReason?.(apt) ?? null;
const bannerTitle = [
patientName,
editBlockReason,
outsideHours ? t('outsideHoursBlocked') : null,
clusterSize > 1
? t('overlappingChoose', { count: clusterSize })
@@ -328,7 +337,11 @@ export function AppointmentScheduleGrid({
)
}
className={`absolute min-h-0 overflow-hidden rounded-[var(--radius-sm)] border pointer-events-auto z-10 flex text-center focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/35 ${
outsideHours ? 'opacity-70 ring-1 ring-amber-500/60' : ''
outsideHours || editBlockReason
? 'opacity-70'
: ''
} ${
outsideHours ? 'ring-1 ring-amber-500/60' : ''
} ${
isUnderOneHour
? 'items-center justify-center px-0.5 py-0'
@@ -377,10 +390,6 @@ export function AppointmentScheduleGrid({
treatmentCatalog={treatmentCatalog}
anchorRect={overlapPopover.anchorRect}
onSelect={(apt) => {
if (!canBook) {
setOverlapPopover(null);
return;
}
const provider = providers.find((p) => p.userId === apt.providerUserId);
if (
provider &&