Files
lootakalenteri-frontend/app/components/Calendar.tsx

288 lines
11 KiB
TypeScript

import { JSX, useEffect, useState } from "react";
import styles from "./Calendar.module.css";
import { getMonthName, daysInMonth } from "../utils/dateUtils";
import { customerState } from "../store";
import { Box } from "../types";
import { useAtom } from "jotai";
import { markBoxDelivered, markBoxPickedUp } from "../api";
import { Modal } from "@mui/material";
export default function Calendar() {
const boxStatuses = ["no boxes", "picked up", "delivered"]; //TODO: There's logic problem between "delivered" and "picked up"
const [customerData, setCustomerData] = useAtom(customerState);
const [selectedMonth, setSelectedMonth] = useState(new Date().getMonth());
const [calendarCells, setCalendarCells] = useState<JSX.Element[]>([]);
const [firstWeekPadding, setFirstWeekPadding] = useState<JSX.Element[]>([]);
const [showLatestBoxModal, setShowLatestBoxModal] = useState(false);
const [latestBoxStatus, setLatestBoxStatus] = useState<string>(boxStatuses[0]);
const [latestBox, setLatestBox] = useState<Box>({
id: "",
pickup_date: null,
delivery_date: new Date(),
});
const [showClickedBoxModal, setShowClickedBoxModal] = useState(false);
const [clickedBox, setClickedBox] = useState<Box>({
id: "",
pickup_date: null,
delivery_date: new Date(),
});
const generateFirstWeekPadding = (month: number) => {
const firstDay = new Date(new Date().getFullYear(), month, 1).getDay();
const padding = Array.from({ length: firstDay === 0 ? 6 : firstDay - 1 }, (_, index) => (
<div key={index}>
&nbsp;
</div>
));
return padding;
}
const generateCalendarCells = (month: number, boxes: Box[]) => {
const monthDays = daysInMonth[month];
const calendarCells = Array.from({ length: monthDays }, (_, index) => {
const day = index + 1;
const date = new Date(new Date().getFullYear(), month, day);
const matchingBox = boxes.find(box => {
const boxDate = new Date(box.delivery_date);
return boxDate.getDate() === day && boxDate.getMonth() === month && boxDate.getFullYear() === date.getFullYear();
});
if (matchingBox) {
return (
<div
key={index}
className={`${styles.calendarCell} ${matchingBox.pickup_date ? styles.pickedUpCell : styles.deliveredCell}`}
onClick={() => {
setClickedBox(matchingBox);
setShowClickedBoxModal(true);
}
}>
{
date.toLocaleDateString("fi-FI", {
day: "2-digit",
month: "2-digit",
})
}
< br />
{
matchingBox.pickup_date ? (
<span className={styles.statusText}>Haettu</span>
) : (
<span className={styles.statusText}>Jako</span>
)
}
</div >
);
}
return (
<div key={index} className={`${styles.calendarCell}`}>
{date.toLocaleDateString("fi-FI", {
day: "2-digit",
month: "2-digit",
})}
</div>
);
});
return calendarCells;
}
const changeClickedBoxStatus = () => {
if (clickedBox.pickup_date) {
// If the box is already picked up, mark it as delivered
markBoxDelivered(clickedBox.id).then((updatedBox) => {
setCustomerData((prev) => ({
...prev,
boxes: prev.boxes.map(box =>
box.id === updatedBox.id ? { ...box, pickup_date: null } : box
),
}));
}
).catch((error) => {
console.error("Error marking box as delivered:", error);
});
return;
} else {
markBoxPickedUp(clickedBox.id).then((updatedBox) => {
setCustomerData((prev) => ({
...prev,
boxes: prev.boxes.map(box =>
box.id === updatedBox.id ? { ...box, pickup_date: updatedBox.pickup_date } : box
),
}));
}
).catch((error) => {
console.error("Error marking box as picked up:", error);
})
}
}
const renderLatestBoxButton = () => {
switch (latestBoxStatus) {
case "no boxes":
return <button
className={styles.latestBoxNoBoxesBtn}
disabled
>
Ei laatikoita tässä kuussa
</button>;
case "picked up":
return <button
className={styles.latestBoxPickedUpBtn}
disabled
>
Uusin laatikko on jo haettu
</button>;
case "delivered":
return <button
className={styles.latestBoxDeliveredBtn}
onClick={handleLatestBoxClick}
>
Kuittaa viimeisin laatikko haetuksi
</button>;
default:
return <span className={styles.latestBoxStatus}>Virhe</span>;
}
}
useEffect(() => {
setCalendarCells(generateCalendarCells(selectedMonth, customerData.boxes));
setFirstWeekPadding(generateFirstWeekPadding(selectedMonth));
setLatestBoxStatus(() => {
if (!customerData.boxes || customerData.boxes.length === 0) {
return boxStatuses[0]; // no boxes
}
const monthBoxes = customerData.boxes.filter(box => new Date(box.delivery_date).getMonth() === selectedMonth);
const deliveredBoxes = customerData.boxes.filter(box => new Date(box.delivery_date).getMonth() === selectedMonth && !box.pickup_date);
if (monthBoxes.length === 0) {
return boxStatuses[0]; // no boxes
}
if (!deliveredBoxes || deliveredBoxes.length === 0) {
return boxStatuses[1]; // all boxes picked up
}
return boxStatuses[2]; // delivered
}
);
}, [selectedMonth, customerData.boxes]);
const handleLatestBoxClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
setShowLatestBoxModal(true);
}
const markLatestBoxPickedUp = () => {
markBoxPickedUp(latestBox.id).then((updatedBox) => {
setCustomerData((prev) => ({
...prev,
boxes: prev.boxes.map(box =>
box.id === updatedBox.id ? { ...box, pickup_date: updatedBox.pickup_date } : box
),
}));
}).catch((error) => {
console.error("Error marking latest box as picked up:", error);
});
}
useEffect(() => {
const deliveredBoxes = customerData.boxes.filter(box => new Date(box.delivery_date).getMonth() === selectedMonth && !box.pickup_date);
if (deliveredBoxes.length === 0) return;
const latestBox = deliveredBoxes[0];
if (latestBox) {
setLatestBox(latestBox);
}
}, [customerData.boxes, selectedMonth]);
return (
<>
<h2 className={styles.monthTitle}>
{getMonthName(selectedMonth)} {new Date().getFullYear()}
</h2>
<div className={styles.monthNavigation}>
<button onClick={() => setSelectedMonth((prev) => (prev - 1 + 12) % 12)} className={styles.monthNavBtn}>
&lt; Edellinen kuukausi
</button>
<button onClick={() => setSelectedMonth((prev) => (prev + 1) % 12)} className={styles.monthNavBtn}>
Seuraava kuukausi &gt;
</button>
</div>
<div className={styles.latestBoxBtnContainer}>
{renderLatestBoxButton()}
</div>
<div className={styles.calendarGrid}>
<span>Ma</span>
<span>Ti</span>
<span>Ke</span>
<span>To</span>
<span>Pe</span>
<span>La</span>
<span>Su</span>
{firstWeekPadding}
{calendarCells}
</div>
<Modal
open={showLatestBoxModal}
onClose={() => setShowLatestBoxModal(false)}
className={styles.latestBoxModal}
>
<div className={styles.latestBoxModalContent}>
<h2>Viimeisin laatikko</h2>
<p>Oletko varma, että haluat kuitata viimeisimmän laatikon ({new Date(latestBox.delivery_date).toLocaleDateString("fi-FI", {
day: "2-digit",
month: "2-digit",
})}) haetuksi?</p>
<button
className={styles.latestBoxConfirmButton}
onClick={() => {
markLatestBoxPickedUp();
setShowLatestBoxModal(false);
}}
>
Kyllä
</button>
<button
className={styles.latestBoxCancelButton}
onClick={() => setShowLatestBoxModal(false)}
>
Peruuta
</button>
</div>
</Modal>
<Modal
open={showClickedBoxModal}
onClose={() => setShowClickedBoxModal(false)}
className={styles.latestBoxModal}
>
<div className={styles.latestBoxModalContent}>
<h2>Satolaatikko {" "}
{new Date(clickedBox.delivery_date).toLocaleDateString("fi-FI", {
day: "2-digit",
month: "2-digit",
})}
</h2>
<p>Oletko varma, että haluat {clickedBox.pickup_date ? "peruuttaa " : "kuitata "}
{new Date(clickedBox.delivery_date).toLocaleDateString("fi-FI", {
day: "2-digit",
month: "2-digit",
})} päivän laatikon {clickedBox.pickup_date ? "haun" : "haetuksi"}?</p>
<button
className={styles.latestBoxConfirmButton}
onClick={() => {
changeClickedBoxStatus();
setShowClickedBoxModal(false);
}}
>
Kyllä
</button>
<button
className={styles.latestBoxCancelButton}
onClick={() => setShowClickedBoxModal(false)}
>
Peruuta
</button>
</div>
</Modal >
</>
);
}