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,
Text,
useMantineTheme,
Modal,
SimpleGrid,
} from "@mantine/core";
import { UseFormReturn } from "react-hook-form";
import { BookingFormInputValues, BookingFormValues } from "./schema";
@@ -52,6 +54,7 @@ interface DayData {
export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
const theme = useMantineTheme();
const [currentDate, setCurrentDate] = useState(new Date());
const [selectedDayForModal, setSelectedDayForModal] = useState<DayData | null>(null);
const selectedDate = form.watch("scheduledDate");
const selectedScheduleId = form.watch("trainScheduleId");
@@ -148,10 +151,35 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
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 (
<Group gap={24} align="flex-start" wrap="nowrap">
<Group gap={24} align="flex-start" wrap="wrap" style={{ width: "100%" }}>
{/* ── Calendar Card ───────────────────────────────────── */}
<Box style={{ flex: 1, minWidth: 0 }}>
<Box style={{ flex: 1, minWidth: 300 }}>
<Card p={0} style={{ overflow: "hidden" }}>
{/* Card header */}
<Group
@@ -244,15 +272,7 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
<DayCell
key={di}
day={d}
selectedScheduleId={selectedScheduleId}
onSelectSchedule={(scheduleId, dateString) => {
form.setValue("scheduledDate", dateString, {
shouldValidate: true,
});
form.setValue("trainScheduleId", scheduleId, {
shouldValidate: true,
});
}}
onDayClick={handleDayClick}
/>
))}
</Box>
@@ -263,7 +283,7 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
</Box>
{/* ── Side Panel ──────────────────────────────────────── */}
<Stack gap={20} w={340} style={{ flexShrink: 0 }}>
<Stack gap={20} style={{ width: 340, flexShrink: 0 }}>
{/* Booking summary card */}
<Card p={0} style={{ overflow: "hidden" }}>
<Group
@@ -361,17 +381,81 @@ export function StepScheduling({ form, referenceData }: StepSchedulingProps) {
</Stack>
</Box>
</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>
);
}
interface DayCellProps {
day: DayData;
selectedScheduleId: string;
onSelectSchedule: (scheduleId: string, dateString: string) => void;
onDayClick: (day: DayData) => void;
}
function DayCell({ day: d, selectedScheduleId, onSelectSchedule }: DayCellProps) {
function DayCell({ day: d, selectedScheduleId, onDayClick, onSelectSchedule }: DayCellProps) {
const theme = useMantineTheme();
if (!d.isCurrentMonth) {
@@ -400,125 +484,113 @@ function DayCell({ day: d, selectedScheduleId, onSelectSchedule }: DayCellProps)
: "transparent";
const cellBorder = d.isSelectedDate
? `1.5px solid ${theme.colors["edr-green"][5]}`
? `2px solid ${theme.colors["edr-green"][5]}`
: d.hasSchedule
? "1px solid #E7E8E5"
? `1px solid ${theme.colors["edr-border"][0]}`
: "none";
return (
<Box
onClick={() => d.hasSchedule && onDayClick(d)}
style={{
height: 92,
borderRadius: theme.radius.md,
backgroundColor: cellBg,
border: cellBorder,
overflow: "hidden",
padding: "4px 6px 6px",
padding: "8px 10px 10px",
display: "flex",
flexDirection: "column",
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 */}
<Group justify="space-between" align="center" style={{ flexShrink: 0 }}>
<Text
fz={14}
fw={d.hasSchedule ? 800 : 600}
c={
d.isToday && !d.isSelectedDate
? "edr-green.6"
: d.hasSchedule
? "edr-text.0"
: "edr-muted"
}
>
{d.day}
</Text>
<Stack gap={0}>
<Text
fz={16}
fw={800}
c={
d.isToday && !d.isSelectedDate
? "edr-green.6"
: d.hasSchedule
? "edr-text.0"
: "edr-muted"
}
>
{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 && (
<Check
size={15}
color={theme.colors["edr-green"][5]}
strokeWidth={2.5}
/>
<Box
style={{
width: 24,
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>
{/* Departure chips */}
{/* Schedule times */}
{d.hasSchedule && (
<Stack gap={3} style={{ flex: 1, overflow: "hidden" }}>
{d.schedules.slice(0, 2).map((s) => {
const isChipSelected = s.id === selectedScheduleId;
const isFull = s.remainingWagons <= 0;
const canSelect = !isFull;
return (
<Stack gap={3} style={{ flex: 1, overflow: "hidden", minWidth: 0 }}>
{d.schedules.slice(0, 2).map((s) => (
<Group key={s.id} gap={6} align="center" style={{ minWidth: 0 }}>
<Box
key={s.id}
onClick={() =>
canSelect && onSelectSchedule(s.id, d.dateString)
}
style={{
display: "flex",
alignItems: "center",
gap: 4,
borderRadius: 7,
padding: "4px 6px",
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"
}`,
width: 4,
height: 4,
borderRadius: "50%",
backgroundColor: theme.colors["edr-green"][5],
flexShrink: 0,
}}
/>
<Text
fz={12}
fw={700}
c="edr-text.0"
style={{ flex: 1, minWidth: 0 }}
>
<Box
style={{
width: 6,
height: 6,
borderRadius: "50%",
backgroundColor: isChipSelected
? "white"
: isFull
? 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>
);
})}
{format(new Date(s.scheduleDate), "HH:mm")}
</Text>
</Group>
))}
{d.schedules.length > 2 && (
<Text fz={11} fw={600} c="edr-green.7" style={{ paddingTop: 2 }}>
+{d.schedules.length - 2} more
</Text>
)}
</Stack>
)}
</Box>