style: ui fix in scheduling

This commit is contained in:
Nathnael
2026-06-17 07:40:52 +00:00
parent c96c863070
commit c59e100e5e

View File

@@ -6,6 +6,8 @@ import {
Stack, Stack,
Text, Text,
useMantineTheme, useMantineTheme,
Modal,
SimpleGrid,
} from "@mantine/core"; } from "@mantine/core";
import { UseFormReturn } from "react-hook-form"; import { UseFormReturn } from "react-hook-form";
import { BookingFormInputValues, BookingFormValues } from "./schema"; import { BookingFormInputValues, BookingFormValues } from "./schema";
@@ -52,6 +54,7 @@ interface DayData {
export function StepScheduling({ form, referenceData }: StepSchedulingProps) { export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
const theme = useMantineTheme(); const theme = useMantineTheme();
const [currentDate, setCurrentDate] = useState(new Date()); const [currentDate, setCurrentDate] = useState(new Date());
const [selectedDayForModal, setSelectedDayForModal] = useState<DayData | null>(null);
const selectedDate = form.watch("scheduledDate"); const selectedDate = form.watch("scheduledDate");
const selectedScheduleId = form.watch("trainScheduleId"); const selectedScheduleId = form.watch("trainScheduleId");
@@ -148,10 +151,35 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
const weeksCount = Math.ceil(days.length / 7); const weeksCount = Math.ceil(days.length / 7);
const handleDayClick = (day: DayData) => {
if (day.schedules.length > 1) {
setSelectedDayForModal(day);
} else if (day.schedules.length === 1) {
form.setValue("scheduledDate", day.dateString, {
shouldValidate: true,
});
form.setValue("trainScheduleId", day.schedules[0].id, {
shouldValidate: true,
});
}
};
const handleSelectScheduleFromModal = (scheduleId: string) => {
if (selectedDayForModal) {
form.setValue("scheduledDate", selectedDayForModal.dateString, {
shouldValidate: true,
});
form.setValue("trainScheduleId", scheduleId, {
shouldValidate: true,
});
setSelectedDayForModal(null);
}
};
return ( return (
<Group gap={24} align="flex-start" wrap="nowrap"> <Group gap={24} align="flex-start" wrap="wrap" style={{ width: "100%" }}>
{/* ── Calendar Card ───────────────────────────────────── */} {/* ── Calendar Card ───────────────────────────────────── */}
<Box style={{ flex: 1, minWidth: 0 }}> <Box style={{ flex: 1, minWidth: 300 }}>
<Card p={0} style={{ overflow: "hidden" }}> <Card p={0} style={{ overflow: "hidden" }}>
{/* Card header */} {/* Card header */}
<Group <Group
@@ -244,15 +272,7 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
<DayCell <DayCell
key={di} key={di}
day={d} day={d}
selectedScheduleId={selectedScheduleId} onDayClick={handleDayClick}
onSelectSchedule={(scheduleId, dateString) => {
form.setValue("scheduledDate", dateString, {
shouldValidate: true,
});
form.setValue("trainScheduleId", scheduleId, {
shouldValidate: true,
});
}}
/> />
))} ))}
</Box> </Box>
@@ -263,7 +283,7 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
</Box> </Box>
{/* ── Side Panel ──────────────────────────────────────── */} {/* ── Side Panel ──────────────────────────────────────── */}
<Stack gap={20} w={340} style={{ flexShrink: 0 }}> <Stack gap={20} style={{ width: 340, flexShrink: 0 }}>
{/* Booking summary card */} {/* Booking summary card */}
<Card p={0} style={{ overflow: "hidden" }}> <Card p={0} style={{ overflow: "hidden" }}>
<Group <Group
@@ -361,17 +381,81 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
</Stack> </Stack>
</Box> </Box>
</Stack> </Stack>
{/* ── Schedule Selection Modal ──────────────────────────── */}
<Modal
opened={!!selectedDayForModal}
onClose={() => setSelectedDayForModal(null)}
title={selectedDayForModal ? format(new Date(selectedDayForModal.dateString + "T00:00:00"), "EEE, MMM d yyyy") : ""}
centered
size="sm"
styles={{
header: { borderBottom: `1px solid ${theme.colors["edr-border"][0]}` },
body: { padding: 24 },
}}
>
<Stack gap={12}>
<Text fz={13} c="edr-muted" fw={500}>
Choose a departure time
</Text>
{selectedDayForModal?.schedules.map((schedule) => (
<Button
key={schedule.id}
variant="outline"
fullWidth
onClick={() => handleSelectScheduleFromModal(schedule.id)}
style={{ height: 64, justifyContent: "flex-start" }}
styles={{
inner: { justifyContent: "flex-start" },
root: {
borderColor: theme.colors["edr-border"][0],
transition: "all 150ms ease",
"&:hover": {
borderColor: theme.colors["edr-green"][5],
backgroundColor: theme.colors["edr-soft"][0],
},
},
}}
>
<Group gap={16} w="100%">
<Box
style={{
width: 48,
height: 48,
borderRadius: theme.radius.md,
backgroundColor: theme.colors["edr-soft"][0],
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Train size={24} color={theme.colors["edr-green"][5]} />
</Box>
<Stack gap={3} style={{ flex: 1, alignItems: "flex-start" }}>
<Text fw={700} fz={18} c="edr-text.0">
{format(new Date(schedule.scheduleDate), "HH:mm")}
</Text>
{schedule.trainNumber && (
<Text fz={12} c="edr-muted">
Train {schedule.trainNumber}
</Text>
)}
</Stack>
</Group>
</Button>
))}
</Stack>
</Modal>
</Group> </Group>
); );
} }
interface DayCellProps { interface DayCellProps {
day: DayData; day: DayData;
selectedScheduleId: string; onDayClick: (day: DayData) => void;
onSelectSchedule: (scheduleId: string, dateString: string) => void;
} }
function DayCell({ day: d, selectedScheduleId, onSelectSchedule }: DayCellProps) { function DayCell({ day: d, selectedScheduleId, onDayClick, onSelectSchedule }: DayCellProps) {
const theme = useMantineTheme(); const theme = useMantineTheme();
if (!d.isCurrentMonth) { if (!d.isCurrentMonth) {
@@ -400,125 +484,113 @@ function DayCell({ day: d, selectedScheduleId, onSelectSchedule }: DayCellProps)
: "transparent"; : "transparent";
const cellBorder = d.isSelectedDate const cellBorder = d.isSelectedDate
? `1.5px solid ${theme.colors["edr-green"][5]}` ? `2px solid ${theme.colors["edr-green"][5]}`
: d.hasSchedule : d.hasSchedule
? "1px solid #E7E8E5" ? `1px solid ${theme.colors["edr-border"][0]}`
: "none"; : "none";
return ( return (
<Box <Box
onClick={() => d.hasSchedule && onDayClick(d)}
style={{ style={{
height: 92, height: 92,
borderRadius: theme.radius.md, borderRadius: theme.radius.md,
backgroundColor: cellBg, backgroundColor: cellBg,
border: cellBorder, border: cellBorder,
overflow: "hidden", overflow: "hidden",
padding: "4px 6px 6px", padding: "8px 10px 10px",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
gap: 4, gap: 4,
cursor: d.hasSchedule ? "pointer" : "default",
transition: "all 150ms ease",
boxShadow: d.hasSchedule && !d.isSelectedDate ? "0 1px 3px rgba(0, 0, 0, 0.05)" : "none",
}}
onMouseEnter={(e) => {
if (d.hasSchedule && !d.isSelectedDate) {
e.currentTarget.style.boxShadow = "0 4px 12px rgba(0, 0, 0, 0.1)";
e.currentTarget.style.borderColor = theme.colors["edr-border"][0];
}
}}
onMouseLeave={(e) => {
if (d.hasSchedule && !d.isSelectedDate) {
e.currentTarget.style.boxShadow = "0 1px 3px rgba(0, 0, 0, 0.05)";
e.currentTarget.style.borderColor = theme.colors["edr-border"][0];
}
}} }}
> >
{/* Day number + check icon */} {/* Day number + check icon */}
<Group justify="space-between" align="center" style={{ flexShrink: 0 }}> <Group justify="space-between" align="center" style={{ flexShrink: 0 }}>
<Text <Stack gap={0}>
fz={14} <Text
fw={d.hasSchedule ? 800 : 600} fz={16}
c={ fw={800}
d.isToday && !d.isSelectedDate c={
? "edr-green.6" d.isToday && !d.isSelectedDate
: d.hasSchedule ? "edr-green.6"
? "edr-text.0" : d.hasSchedule
: "edr-muted" ? "edr-text.0"
} : "edr-muted"
> }
{d.day} >
</Text> {d.day}
</Text>
{d.isToday && !d.isSelectedDate && (
<Text fz={9} fw={700} c="edr-green.6" style={{ letterSpacing: "0.04em" }}>
TODAY
</Text>
)}
</Stack>
{d.isSelectedDate && ( {d.isSelectedDate && (
<Check <Box
size={15} style={{
color={theme.colors["edr-green"][5]} width: 24,
strokeWidth={2.5} height: 24,
/> borderRadius: "50%",
backgroundColor: theme.colors["edr-green"][5],
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Check
size={14}
color="white"
strokeWidth={3}
/>
</Box>
)} )}
</Group> </Group>
{/* Departure chips */} {/* Schedule times */}
{d.hasSchedule && ( {d.hasSchedule && (
<Stack gap={3} style={{ flex: 1, overflow: "hidden" }}> <Stack gap={3} style={{ flex: 1, overflow: "hidden", minWidth: 0 }}>
{d.schedules.slice(0, 2).map((s) => { {d.schedules.slice(0, 2).map((s) => (
const isChipSelected = s.id === selectedScheduleId; <Group key={s.id} gap={6} align="center" style={{ minWidth: 0 }}>
const isFull = s.remainingWagons <= 0;
const canSelect = !isFull;
return (
<Box <Box
key={s.id}
onClick={() =>
canSelect && onSelectSchedule(s.id, d.dateString)
}
style={{ style={{
display: "flex", width: 4,
alignItems: "center", height: 4,
gap: 4, borderRadius: "50%",
borderRadius: 7, backgroundColor: theme.colors["edr-green"][5],
padding: "4px 6px", flexShrink: 0,
cursor: canSelect ? "pointer" : "default",
backgroundColor: isChipSelected
? theme.colors["edr-green"][5]
: isFull
? theme.colors["edr-red-soft"][0]
: theme.colors["edr-soft"][0],
border: `1px solid ${
isChipSelected
? theme.colors["edr-green"][5]
: isFull
? "#EFCFCA"
: "#BFE3D4"
}`,
}} }}
/>
<Text
fz={12}
fw={700}
c="edr-text.0"
style={{ flex: 1, minWidth: 0 }}
> >
<Box {format(new Date(s.scheduleDate), "HH:mm")}
style={{ </Text>
width: 6, </Group>
height: 6, ))}
borderRadius: "50%", {d.schedules.length > 2 && (
backgroundColor: isChipSelected <Text fz={11} fw={600} c="edr-green.7" style={{ paddingTop: 2 }}>
? "white" +{d.schedules.length - 2} more
: isFull </Text>
? theme.colors["edr-red"][0] )}
: theme.colors["edr-green"][5],
flexShrink: 0,
}}
/>
<Text
fz={11}
fw={700}
style={{ flex: 1, minWidth: 0, overflow: "hidden" }}
truncate
c={
isChipSelected ? "white" : isFull ? "edr-red.0" : "edr-green.7"
}
>
{isFull
? "Full"
: s.trainNumber
? s.trainNumber
: `${s.remainingWagons} wgn`}
</Text>
<ChevronRight
size={12}
color={
isChipSelected
? "white"
: isFull
? theme.colors["edr-muted"][0]
: theme.colors["edr-green"][7]
}
/>
</Box>
);
})}
</Stack> </Stack>
)} )}
</Box> </Box>