From 397259b6592f5c8dfbed4d106d0442d162555a90 Mon Sep 17 00:00:00 2001 From: dowl Date: Sat, 4 Jan 2025 23:49:58 +0100 Subject: [PATCH] feat: openinghours --- .../(authenticated)/(admin)/layout.tsx | 3 +- .../(with-footer)/[type]/[nameID]/loading.tsx | 4 +- .../(with-footer)/[type]/[nameID]/page.tsx | 2 +- components/tower/tiles/Admission.tsx | 38 +- .../tiles/openingHours/OpeningHoursDialog.tsx | 643 ++++++------------ ...{OpeningHours.tsx => OpeningHoursTile.tsx} | 34 +- .../tower/tiles/openingHours/steps/Step1.tsx | 166 +++++ .../tower/tiles/openingHours/steps/Step2.tsx | 53 ++ .../tiles/openingHours/steps/Step3Detail.tsx | 107 +++ .../tiles/openingHours/steps/Step3Hours.tsx | 116 ++++ components/tower/top/Legend.tsx | 2 +- ideas/ideas.txt | 45 -- scripts/download_first_photos.js | 43 -- scripts/operation_every_tower.ts | 113 --- scripts/upload_photos.js | 46 -- types/EditableParameter.ts | 2 +- types/OpeningHours.ts | 51 ++ typings.d.ts | 21 +- utils/constants.ts | 48 +- 19 files changed, 762 insertions(+), 775 deletions(-) rename components/tower/tiles/openingHours/{OpeningHours.tsx => OpeningHoursTile.tsx} (66%) create mode 100644 components/tower/tiles/openingHours/steps/Step1.tsx create mode 100644 components/tower/tiles/openingHours/steps/Step2.tsx create mode 100644 components/tower/tiles/openingHours/steps/Step3Detail.tsx create mode 100644 components/tower/tiles/openingHours/steps/Step3Hours.tsx delete mode 100644 ideas/ideas.txt delete mode 100644 scripts/download_first_photos.js delete mode 100644 scripts/operation_every_tower.ts delete mode 100644 scripts/upload_photos.js create mode 100644 types/OpeningHours.ts diff --git a/app/(with-navbar)/(authenticated)/(admin)/layout.tsx b/app/(with-navbar)/(authenticated)/(admin)/layout.tsx index 28f728f..44605a8 100644 --- a/app/(with-navbar)/(authenticated)/(admin)/layout.tsx +++ b/app/(with-navbar)/(authenticated)/(admin)/layout.tsx @@ -1,9 +1,10 @@ import { checkAuth } from "@/actions/checkAuth"; import { signIn } from "@/auth"; +import { redirect } from "next/navigation"; export default async function AuthLayout({ children }: { children: React.ReactNode }) { const user = await checkAuth(); if (!user) return await signIn(); - if (user.id !== "iMKZNJV5PE4XQjnKmZut") return
Unauthorized
; + if (user.id !== "iMKZNJV5PE4XQjnKmZut") return redirect("/"); return <>{children}; } diff --git a/app/(with-navbar)/(with-footer)/[type]/[nameID]/loading.tsx b/app/(with-navbar)/(with-footer)/[type]/[nameID]/loading.tsx index c4e2213..8d3dd83 100644 --- a/app/(with-navbar)/(with-footer)/[type]/[nameID]/loading.tsx +++ b/app/(with-navbar)/(with-footer)/[type]/[nameID]/loading.tsx @@ -1,7 +1,7 @@ import { loginRedirect } from "@/actions/login.redirect"; import Admission from "@/components/tower/tiles/Admission"; import HistoryText from "@/components/tower/tiles/HistoryText"; -import OpeningHours_ from "@/components/tower/tiles/openingHours/OpeningHours"; +import OpeningHoursTile from "@/components/tower/tiles/openingHours/OpeningHoursTile"; import ParameterTile from "@/components/tower/tiles/parameters/ParameterTile"; const Loading = () => { @@ -47,7 +47,7 @@ const Loading = () => {
- +
diff --git a/app/(with-navbar)/(with-footer)/[type]/[nameID]/page.tsx b/app/(with-navbar)/(with-footer)/[type]/[nameID]/page.tsx index 018737d..8970814 100644 --- a/app/(with-navbar)/(with-footer)/[type]/[nameID]/page.tsx +++ b/app/(with-navbar)/(with-footer)/[type]/[nameID]/page.tsx @@ -3,7 +3,7 @@ import { Metadata } from "next"; import HistoryText from "@/components/tower/tiles/HistoryText"; import Map from "@/components/tower/tiles/Map"; import Parameters from "@/components/tower/tiles/parameters/Parameters"; -import OpeningHours from "@/components/tower/tiles/openingHours/OpeningHours"; +import OpeningHours from "@/components/tower/tiles/openingHours/OpeningHoursTile"; import Admission from "@/components/tower/tiles/Admission"; import OpeningHoursDialog from "@/components/tower/tiles/openingHours/OpeningHoursDialog"; import { getTowerObjectByNameID, getTowerRatingAndCount } from "@/actions/towers/towers.action"; diff --git a/components/tower/tiles/Admission.tsx b/components/tower/tiles/Admission.tsx index 8a5349a..3396128 100644 --- a/components/tower/tiles/Admission.tsx +++ b/components/tower/tiles/Admission.tsx @@ -1,11 +1,45 @@ +type AccessType = "walk" | "bike" | "car" | "train" | "bus" | "cable-car" | "other" | "unknown"; +type TouristColor = "green" | "yellow" | "blue" | "red" | "nocolor" | "cyclePath"; +type Difficulty = "easy" | "medium" | "hard" | "unknown"; + +const simpleAcess = [ + { + type: ["bus", "car"], + touristColor: ["green"], + description: "Pěšky po zelené z obce Radošov.", + distance: 1.5, + duration: 30, + difficulty: "easy", + }, + { + type: ["car"], + description: "Parkoviště u rozhledny.", + distance: 0.1, + duration: 0, + difficulty: "easy", + }, + { + type: ["car", "train"], + touristColor: ["blue"], + description: "Po modré z obce Bystřice.", + distance: 2.5, + duration: 60, + difficulty: "medium", + }, +]; + function Admission() { return (
-

Vstupné

-

Bezplatný vstup.

+

Přístup

+

Bezplatný vstup.

+ {/*

+ Zpoplatněno: 60 Kč +

*/} +

Navrhnout úpravu
diff --git a/components/tower/tiles/openingHours/OpeningHoursDialog.tsx b/components/tower/tiles/openingHours/OpeningHoursDialog.tsx index f247c12..5c03987 100644 --- a/components/tower/tiles/openingHours/OpeningHoursDialog.tsx +++ b/components/tower/tiles/openingHours/OpeningHoursDialog.tsx @@ -1,176 +1,138 @@ "use client"; -import { DAYS_CZECH, MONTHS_CZECH, OpeningHoursForbiddenType, OpeningHoursType } from "@/utils/constants"; -import { useEffect, useRef, useState } from "react"; + +import { useRef, useState } from "react"; import { Tower } from "@/typings"; -import OpeningHours_ from "./OpeningHours"; +import { OpeningHours, OpeningHoursForbiddenType, OpeningHoursType } from "@/types/OpeningHours"; +import Step1 from "@/components/tower/tiles/openingHours/steps/Step1"; +import { cn } from "@/utils/cn"; +import Step2 from "@/components/tower/tiles/openingHours/steps/Step2"; +import Step3Hours from "@/components/tower/tiles/openingHours/steps/Step3Hours"; +import Step3Detail from "@/components/tower/tiles/openingHours/steps/Step3Detail"; +import OpeningHoursTile from "@/components/tower/tiles/openingHours/OpeningHoursTile"; +import { createChange } from "@/actions/changes/change.create"; +import { sendMail } from "@/actions/mail"; +import { createSubject } from "@/utils/mail"; +import { MailSubject } from "@/types/MailSubject"; function OpeningHoursDialog({ tower }: { tower: Tower }) { const [step, setStep] = useState(1); - const [type, setType] = useState(""); + const [openingHours, setOpeningHours] = useState(tower.openingHours); + const [errorText, setErrorText] = useState(""); + const [isSending, setIsSending] = useState(false); + + const handleTypeChange = (type: OpeningHoursType) => { + setOpeningHours((old) => ({ ...old, type })); + }; + + const handleMonthFromChange = (month: number) => { + setOpeningHours((old) => ({ ...old, monthFrom: month })); + }; + + const handleMonthToChange = (month: number) => { + setOpeningHours((old) => ({ ...old, monthTo: month })); + }; - const [occasionallyText, setOccasionallyText] = useState(""); + const handleIsLockedAtNightChange = (isLockedAtNight: boolean) => { + setOpeningHours((old) => ({ ...old, isLockedAtNight })); + }; - const [goneText, setGoneText] = useState(""); - const [goneType, setGoneType] = useState(""); + const handleDaysChange = (days: number[]) => { + setOpeningHours((old) => ({ ...old, days })); + }; - const [od_, setOd] = useState(""); - const [do_, setDo] = useState(""); + const handleDayFromChange = (dayFrom: number) => { + setOpeningHours((old) => ({ ...old, dayFrom })); + }; - const [weekType, setWeekType] = useState(""); - const [daysSelected, setDaysSelected] = useState([]); + const handleDayToChange = (dayTo: number) => { + setOpeningHours((old) => ({ ...old, dayTo })); + }; - const [dayOd, setDayOd] = useState(""); - const [dayDo, setDayDo] = useState(""); + const handleLunchBreakChange = (lunchBreak: boolean) => { + setOpeningHours((old) => ({ ...old, lunchBreak })); + }; - const [lunchBreak, setLunchBreak] = useState(false); + const handleLunchFromChange = (lunchFrom: number) => { + setOpeningHours((old) => ({ ...old, lunchFrom })); + }; - const [dayOdLunch, setDayOdLunch] = useState(""); - const [dayDoLunch, setDayDoLunch] = useState(""); + const handleLunchToChange = (lunchTo: number) => { + setOpeningHours((old) => ({ ...old, lunchTo })); + }; - const [errorText, setErrorText] = useState(""); + const handleDetailTextChange = (text: string) => { + setOpeningHours((old) => ({ ...old, detailText: text })); + }; + + const handleDetailUrlChange = (url: string) => { + setOpeningHours((old) => ({ ...old, detailUrl: url })); + }; + + const handleForbiddenTypeChange = (type: OpeningHoursForbiddenType) => { + setOpeningHours((old) => ({ ...old, forbiddenType: type })); + }; const manageStepper = () => { - if (type === "") return setErrorText("Nebyla zvolena žádná možnost."); switch (step) { case 1: - switch (type) { - case "freely_accessible": - setStep(4); - break; - case "some_months": - if (od_ === "") return setErrorText("Není vybráno od."); - if (do_ === "") return setErrorText("Není vybráno do."); - case "every_month": - setStep(2); - break; + switch (openingHours.type) { + case OpeningHoursType.NonStop: + return setStep(4); + case OpeningHoursType.SomeMonths: + if (openingHours.monthFrom === undefined || openingHours.monthFrom < 0) return setErrorText("Není vybrán měsíc od."); + if (openingHours.monthTo === undefined || openingHours.monthTo < 0) return setErrorText("Není vybrán měsíc do."); + case OpeningHoursType.EveryMonth: + return setStep(2); default: - setStep(3); - break; + return setStep(3); } - break; case 2: - if ((type === "some_months" || type === "every_month") && weekType === "") { - return setErrorText("Nebyla zvolena žádná možnost."); - } - if ((type === "some_months" || type === "every_month") && weekType === "some_days" && daysSelected.length === 0) { - return setErrorText("Není vybrán žádný den."); - } - setStep(3); - break; + if (openingHours.days === undefined || openingHours.days.length === 0) return setErrorText("Není vybrán žádný den."); + return setStep(3); default: - if (type === "forbidden" && goneType === "") return setErrorText("Nebyla zvolena žádná možnost."); - if (type === "some_months" || type === "every_month") { - if (dayOd === "") return setErrorText("Nebyl vybrán začátek otevírací doby."); - if (dayDo === "") return setErrorText("Nebyl vybrán konec otevírací doby."); - if (dayDo <= dayOd) return setErrorText("Otevírací doba musí začínat dříve, než končit."); - if (lunchBreak) { - if (dayOdLunch === "") return setErrorText("Nebyl vybrán začátek přestávky."); - if (dayDoLunch === "") return setErrorText("Nebyl vybrán konec přestávky."); - if (dayOdLunch <= dayOd) return setErrorText("Přestávka začíná dříve než otevírací doba."); - if (dayOdLunch >= dayDo) return setErrorText("Přestávka začíná později než otevírací doba."); - if (dayDoLunch >= dayDo) return setErrorText("Přestávka končí později, než končí otevírací doba."); - if (dayDoLunch <= dayOd) return setErrorText("Přestávka končí dříve, než začne otevírací doba."); - if (dayDoLunch <= dayOdLunch) return setErrorText("Přestávka musí začínat dříve, než končit."); + if ( + openingHours.type === OpeningHoursType.Forbidden && + (openingHours.forbiddenType === undefined || openingHours.forbiddenType === null) + ) + return setErrorText("Nebyla zvolena žádná možnost."); + + if (openingHours.type === OpeningHoursType.SomeMonths || openingHours.type === OpeningHoursType.EveryMonth) { + if (openingHours.dayFrom === -1 || openingHours.dayFrom === undefined) + return setErrorText("Nebyl vybrán začátek otevírací doby."); + if (openingHours.dayTo === -1 || openingHours.dayTo === undefined) return setErrorText("Nebyl vybrán konec otevírací doby."); + if (openingHours.dayTo <= openingHours.dayFrom) return setErrorText("Otevírací doba musí začínat dříve, než končit."); + if (openingHours.lunchBreak) { + if (openingHours.lunchFrom === -1 || openingHours.lunchFrom === undefined) + return setErrorText("Nebyl vybrán začátek přestávky."); + if (openingHours.lunchTo === -1 || openingHours.lunchTo === undefined) return setErrorText("Nebyl vybrán konec přestávky."); + if (openingHours.lunchFrom <= openingHours.dayFrom) return setErrorText("Přestávka začíná dříve než otevírací doba."); + if (openingHours.lunchFrom >= openingHours.dayTo) return setErrorText("Přestávka začíná později než otevírací doba."); + if (openingHours.lunchTo >= openingHours.dayTo) return setErrorText("Přestávka končí později, než končí otevírací doba."); + if (openingHours.lunchTo <= openingHours.dayFrom) return setErrorText("Přestávka končí dříve, než začne otevírací doba."); + if (openingHours.lunchTo <= openingHours.lunchFrom) return setErrorText("Přestávka musí začínat dříve, než končit."); } } - setStep(4); - break; + return setStep(4); } }; - const clearStepper = () => { - setStep(1); - setType(""); - setOccasionallyText(""); - setGoneText(""); - setGoneType(""); - setOd(""); - setDo(""); - setErrorText(""); - setWeekType(""); - setDaysSelected([]); - setDayDo(""); - setDayOd(""); - setLunchBreak(false); - setDayOdLunch(""); - setDayDoLunch(""); - }; - - const mapTypeEnum = (type: string): number => { - switch (type) { - case "freely_accessible": - return OpeningHoursType.NonStop; - case "some_months": - case "every_month": - return OpeningHoursType.Hours; - case "forbidden": - return OpeningHoursType.Forbidden; - case "occasionally": - return OpeningHoursType.Occasionally; - default: - return OpeningHoursType.Unknown; - } + const sendNewOpeningHours = async () => { + setIsSending(true); + await createChange({ + tower_id: tower.id, + field: "openingHours", + type: "object", + old_value: tower.openingHours, + new_value: openingHours, + }); + await sendMail({ + subject: createSubject(MailSubject.Info, "Změna otevírací doby"), + text: `Byl vytvořen návrh otevírací doby rozhledny ${tower.name}.`, + }); + setIsSending(false); + setStep(5); }; - const generateFinalOpeningHours = (): OpeningHours => { - let obj: OpeningHours = { - type: mapTypeEnum(type), - }; - if (type === "forbidden") { - if (goneType === "reconstruction") obj.forbidden_type = OpeningHoursForbiddenType.Reconstruction; - if (goneType === "gone") obj.forbidden_type = OpeningHoursForbiddenType.Gone; - if (goneType === "temporary") obj.forbidden_type = OpeningHoursForbiddenType.Temporary; - } - if (type === "forbidden" && goneText) obj.note = goneText; - if (type === "occasionally" && occasionallyText) obj.note = occasionallyText; - if (type === "every_month") obj.months = []; - if (type === "some_months") obj.months = [MONTHS_CZECH.indexOf(od_), MONTHS_CZECH.indexOf(do_)]; - if (type === "some_months" || type === "every_month") { - if (weekType === "every_day") obj.days = [0, 1, 2, 3, 4, 5, 6]; - if (weekType === "some_days") obj.days = daysSelected.map((e) => DAYS_CZECH.indexOf(e)).sort((a, b) => a - b); - obj.time_start = parseInt(dayOd.toString()); - obj.time_end = parseInt(dayDo.toString()); - if (lunchBreak) { - obj.lunch_break = true; - obj.lunch_start = parseInt(dayOdLunch.toString()); - obj.lunch_end = parseInt(dayDoLunch.toString()); - } - } - return obj; - }; - - // months checker - useEffect(() => { - if (step === 1 && type === "some_months" && od_ && do_) { - return setErrorText(""); - } - if (step === 1 && type !== "") setErrorText(""); - }, [od_, do_, step, type]); - - // gone type checker - useEffect(() => { - if (step === 3 && type === "forbidden") { - if (!goneType) return; - if (goneType) setErrorText(""); - } - }, [step, type, goneType]); - - // days selection checker - useEffect(() => { - if (daysSelected.length) setErrorText(""); - if (weekType === "every_day") setErrorText(""); - }, [daysSelected, weekType]); - - // time picker checker - useEffect(() => { - if (step === 3 && (type === "some_months" || type === "every_month") && (dayOd !== "" || dayDo !== "")) { - setErrorText(""); - } - if (step === 3 && (type === "some_months" || type === "every_month") && lunchBreak && (dayOdLunch !== "" || dayDoLunch !== "")) { - setErrorText(""); - } - }, [step, type, dayDo, dayOd, lunchBreak, dayDoLunch, dayOdLunch]); - const dialogRef = useRef(null); return ( @@ -181,342 +143,129 @@ function OpeningHoursDialog({ tower }: { tower: Tower }) { > Navrhnout úpravu
- + +

Úprava otevírací doby {tower.name}

  • Stav
  • - {(type === "" || type === "every_month" || type === "some_months") && ( -
  • = 2 && "step-primary"}`}>Dny
  • - )} + {openingHours.type === OpeningHoursType.EveryMonth || openingHours.type === OpeningHoursType.SomeMonths ? ( +
  • 1 })}>Dny
  • + ) : null} - {(type === "" || type === "every_month" || type === "some_months") && ( -
  • = 3 && "step-primary"}`}>Časy
  • - )} + {openingHours.type === OpeningHoursType.EveryMonth || openingHours.type === OpeningHoursType.SomeMonths ? ( +
  • 2 })}>Časy
  • + ) : null} - {(type === "forbidden" || type === "occasionally") &&
  • = 3 && "step-primary"}`}>Detail
  • } + {openingHours.type === OpeningHoursType.Forbidden || + openingHours.type === OpeningHoursType.Occasionally || + openingHours.type === OpeningHoursType.WillOpen ? ( +
  • 2 })}>Detail
  • + ) : null} -
  • Dokončení
  • +
  • = 4 })}>Dokončení
-
- -
- - -
- -
- -
-
- - -
- -
- - -
- {DAYS_CZECH.map((d) => ( - - ))} -
-
+ {step === 1 ? ( + + ) : null} + + {step === 2 ? : null} + + {step === 3 && (openingHours.type === OpeningHoursType.EveryMonth || openingHours.type === OpeningHoursType.SomeMonths) ? ( + + ) : null} + + {step === 3 && openingHours.type !== OpeningHoursType.EveryMonth && openingHours.type !== OpeningHoursType.SomeMonths ? ( + + ) : null} -
-

Otevírací doba (např. 9 - 18)

-
- -
- -
- -
- -
- -
-
- -
- - - - - +
+

Takto bude vypadat nová dlaždice s otevírací dobou:

+
-
- - -
- -
-

Takto bude vypadat nová dlaždice s otevírací dobou:

- +
+

+ Děkujeme za navržení změny otevírací doby. Tato změna bude zaslána správci a po schválení bude zveřejněna. +

{errorText &&

{errorText}

}
+ + {step > 1 && step < 4 ? ( + + ) : null} -
- +
diff --git a/components/tower/tiles/openingHours/OpeningHours.tsx b/components/tower/tiles/openingHours/OpeningHoursTile.tsx similarity index 66% rename from components/tower/tiles/openingHours/OpeningHours.tsx rename to components/tower/tiles/openingHours/OpeningHoursTile.tsx index b847d3c..7988d0a 100644 --- a/components/tower/tiles/openingHours/OpeningHours.tsx +++ b/components/tower/tiles/openingHours/OpeningHoursTile.tsx @@ -1,5 +1,7 @@ -import { OpeningHours, Tower } from "@/typings"; -import { DAYS_CZECH, MONTHS_CZECH, OpeningHoursForbiddenType, OpeningHoursType } from "@/utils/constants"; +import { OpeningHours, OpeningHoursForbiddenType, OpeningHoursType } from "@/types/OpeningHours"; +import { Tower } from "@/typings"; +import { cn } from "@/utils/cn"; +import { DAYS_CZECH, MONTHS_CZECH } from "@/utils/constants"; import { ReactNode } from "react"; function capitalizeFirstLetter(string: string): string { @@ -44,21 +46,31 @@ const generateHeading = (openingHours: OpeningHours, type: string): string => { } | ${getDaysString(openingHours.days)} | ${openingHours.time_start} - ${openingHours.time_end} h`; }; -function OpeningHours_({ tower, openingHours, children }: { tower?: Tower; openingHours?: OpeningHours; children?: ReactNode }) { +function OpeningHoursTile({ tower, openingHours, children }: { tower?: Tower; openingHours?: OpeningHours; children?: ReactNode }) { const OH: OpeningHours = tower ? tower.openingHours : openingHours || { type: 0 }; return ( -
+
-

Otevírací doba

-

{generateHeading(OH, tower?.type || "rozhledna")}

- {OH.lunch_break &&

{getLunchString(OH)}

} - {OH.note &&

{OH.note}

} +

+ Otevírací doba +

+

+ {generateHeading(OH, tower?.type ?? "rozhledna")} +

+

{getLunchString(OH)}

+

{OH.note}

{children}
); } -export default OpeningHours_; +export default OpeningHoursTile; diff --git a/components/tower/tiles/openingHours/steps/Step1.tsx b/components/tower/tiles/openingHours/steps/Step1.tsx new file mode 100644 index 0000000..b7ae054 --- /dev/null +++ b/components/tower/tiles/openingHours/steps/Step1.tsx @@ -0,0 +1,166 @@ +"use client"; + +import { OpeningHoursType } from "@/types/OpeningHours"; +import { cn } from "@/utils/cn"; +import { MONTHS_CZECH } from "@/utils/constants"; +import { useEffect } from "react"; + +type Step1Props = { + currentType: OpeningHoursType; + handleTypeChange: (type: OpeningHoursType) => void; + monthFrom: number; + monthTo: number; + handleMonthFromChange: (month: number) => void; + handleMonthToChange: (month: number) => void; + isLockedAtNight: boolean; + handleIsLockedAtNightChange: (isLockedAtNight: boolean) => void; + setErrorText: (text: string) => void; +}; + +const Step1 = ({ + currentType, + handleTypeChange, + monthFrom, + monthTo, + handleMonthFromChange, + handleMonthToChange, + isLockedAtNight, + handleIsLockedAtNightChange, + setErrorText, +}: Step1Props) => { + useEffect(() => { + if (currentType === OpeningHoursType.SomeMonths && monthFrom >= 0 && monthTo >= 0) { + return setErrorText(""); + } + if (currentType >= 0) setErrorText(""); + }, [currentType, monthFrom, monthTo]); + + return ( +
+ + + + +
+ + + + + +
+ + +
+ + +
+ +
+ + + + + +
+ + +
+ ); +}; + +export default Step1; diff --git a/components/tower/tiles/openingHours/steps/Step2.tsx b/components/tower/tiles/openingHours/steps/Step2.tsx new file mode 100644 index 0000000..340d451 --- /dev/null +++ b/components/tower/tiles/openingHours/steps/Step2.tsx @@ -0,0 +1,53 @@ +import { DAYS_CZECH } from "@/utils/constants"; +import { useEffect } from "react"; + +type Step2Props = { + days: number[]; + handleDaysChange: (days: number[]) => void; + setErrorText: (text: string) => void; +}; + +const Step2 = ({ days, handleDaysChange, setErrorText }: Step2Props) => { + useEffect(() => { + if (days.length) setErrorText(""); + }, [days]); + + return ( +
+ + +
+ {DAYS_CZECH.map((d, idx) => ( + + ))} +
+
+ ); +}; + +export default Step2; diff --git a/components/tower/tiles/openingHours/steps/Step3Detail.tsx b/components/tower/tiles/openingHours/steps/Step3Detail.tsx new file mode 100644 index 0000000..73c7bfa --- /dev/null +++ b/components/tower/tiles/openingHours/steps/Step3Detail.tsx @@ -0,0 +1,107 @@ +import { getOpeningHoursTypeName, OpeningHoursForbiddenType, OpeningHoursType } from "@/types/OpeningHours"; +import { useEffect } from "react"; + +type OpeningHours = { + detailText: string; + handleDetailTextChange: (text: string) => void; + detailUrl: string; + handleDetailUrlChange: (url: string) => void; + type: OpeningHoursType; + forbiddenType?: OpeningHoursForbiddenType; + handleForbiddenTypeChange: (type: OpeningHoursForbiddenType) => void; + setErrorText: (text: string) => void; +}; + +const Step3Detail = ({ + type, + detailText, + detailUrl, + handleDetailTextChange, + handleDetailUrlChange, + forbiddenType, + handleForbiddenTypeChange, + setErrorText, +}: OpeningHours) => { + const isForbidden = type === OpeningHoursType.Forbidden; + + useEffect(() => { + if (isForbidden) { + if (forbiddenType !== undefined && forbiddenType !== null) return setErrorText(""); + } + }, [isForbidden, forbiddenType]); + + return ( +
+

{getOpeningHoursTypeName(type)}

+ + {isForbidden ? ( +
+ + + + +
+ ) : null} + + + +
+ ); +}; + +export default Step3Detail; diff --git a/components/tower/tiles/openingHours/steps/Step3Hours.tsx b/components/tower/tiles/openingHours/steps/Step3Hours.tsx new file mode 100644 index 0000000..b8c3577 --- /dev/null +++ b/components/tower/tiles/openingHours/steps/Step3Hours.tsx @@ -0,0 +1,116 @@ +import { cn } from "@/utils/cn"; +import { useEffect } from "react"; + +type Step3HoursProps = { + dayFrom: number; + dayTo: number; + handleDayFrom: (dayFrom: number) => void; + handleDayTo: (dayTo: number) => void; + lunchBreak: boolean; + handleLunchBreak: (lunchBreak: boolean) => void; + lunchFrom: number; + handleLunchFrom: (lunchFrom: number) => void; + lunchTo: number; + handleLunchTo: (lunchTo: number) => void; + setErrorText: (text: string) => void; +}; + +const Step3Hours = ({ + dayFrom, + dayTo, + lunchBreak, + lunchFrom, + lunchTo, + handleDayFrom, + handleDayTo, + handleLunchFrom, + handleLunchBreak, + handleLunchTo, + setErrorText, +}: Step3HoursProps) => { + useEffect(() => { + if (dayFrom !== -1 || dayTo !== -1) { + setErrorText(""); + } + if (lunchBreak && (lunchFrom !== -1 || lunchTo !== -1)) { + setErrorText(""); + } + }, [dayFrom, dayTo, lunchBreak, lunchFrom, lunchTo]); + + return ( +
+

Otevírací doba (např. 9 - 18 hod)

+
+ + +
+ + +
+ + + +
+ +
+ +
+
+ ); +}; + +export default Step3Hours; diff --git a/components/tower/top/Legend.tsx b/components/tower/top/Legend.tsx index fbf173f..f0fdd1b 100644 --- a/components/tower/top/Legend.tsx +++ b/components/tower/top/Legend.tsx @@ -1,5 +1,5 @@ import { Tower } from "@/typings"; -import { OpeningHoursForbiddenType, OpeningHoursType } from "@/utils/constants"; +import { OpeningHoursForbiddenType, OpeningHoursType, OpeningHours } from "@/types/OpeningHours"; function capitalizeFirstLetter(string: string) { return string.charAt(0).toUpperCase() + string.slice(1); diff --git a/ideas/ideas.txt b/ideas/ideas.txt deleted file mode 100644 index 61f3f6a..0000000 --- a/ideas/ideas.txt +++ /dev/null @@ -1,45 +0,0 @@ ------------------------------------------------------------------------------- ---------------------------- HOTOVKA SODOVKA ---------------------------------- ------------------------------------------------------------------------------- - -OpeningHours Input - Stepper 3 steps - - 1. cely rok/ nektere mesice /volne pristupna/ ocassionally - - 2. kazdy den / nektere dny v tydnu -!!! - 3. časy + pauza na oběd - - 4. repeat if nektere mesice // nah man - -Stav rozhledny - v provozu / v rekonstrukci / zaniklá / dočasně uzavřeno - + nejaky info k tomu (webovy odkaz a popis asi) - - -------------------------------------------------------------------------------- ----------------------------- TODO ------------------------------------------- -------------------------------------------------------------------------------- - - -Vstupné - - 1. zpoplatnene / volny Vstupné - - 2. postupne pridavat tarify (dospely, student, dite, senior...) + rodina ! - - 3. moznost platit kartou - - - -- pridat vysku vyhlidkove plosiny do parametrů - -TAB Vybavení - - občerstení u rozhledny - - prodej - - výhledové tabule - - toaleta - - přístupné na kole - - bezbarierovy vystup? - -TAB kontakt? - - tel cislo - - email - - oficialni web - - - turisticky vizitky, znamky, whatever pridat.... no spis ani asi ne - - plaiceholder na obrazky? \ No newline at end of file diff --git a/scripts/download_first_photos.js b/scripts/download_first_photos.js deleted file mode 100644 index cfffa8f..0000000 --- a/scripts/download_first_photos.js +++ /dev/null @@ -1,43 +0,0 @@ -const admin = require("firebase-admin"); -const serviceAccount = require("./service_key.json"); // Replace with your service account key -const fs = require("fs"); -const path = require("path"); - -admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - storageBucket: "lookout-towers.appspot.com", // Replace with your storage bucket URL -}); - -const bucket = admin.storage().bucket(); -const firestore = admin.firestore(); - -async function getIDS() { - const towersSnap = await firestore.collection("towers").get(); - const towerIDS = []; - towersSnap.forEach((doc) => { - towerIDS.push(doc.id); - }); - return towerIDS; -} - -async function downloadFirstPhotoFromFolders(towerIDs) { - try { - for (const id of towerIDs) { - const [files] = await bucket.getFiles({ prefix: `towers/${id}` }); - - const destination = `./${files[0].name}`; - - // Create destination directory if it doesn't exist - const dirPath = path.dirname(destination); - fs.mkdirSync(dirPath, { recursive: true }); - - await files[0].download({ destination }); - console.log(`Downloaded ${files[0].name} to ${destination}`); - } - - console.log("Download completed."); - } catch (error) { - console.error("Error downloading files:", error); - } -} -getIDS().then((r) => downloadFirstPhotoFromFolders(r)); diff --git a/scripts/operation_every_tower.ts b/scripts/operation_every_tower.ts deleted file mode 100644 index 711c879..0000000 --- a/scripts/operation_every_tower.ts +++ /dev/null @@ -1,113 +0,0 @@ -const admin = require("firebase-admin"); -const serviceAccount = require("./service_key.json"); - -admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - databaseURL: "https://lookout-towers-default-rtdb.europe-west1.firebasedatabase.app", -}); - -const db = admin.firestore(); -const towersRef = db.collection("towers"); - -enum OpeningHoursType { - Unknown, - NonStop, - Occasionally, - Hours, - Forbidden -} - -enum OpeningHoursForbiddenType { - Reconstruction, - Temporary, - Gone -} - -type OpeningHours = { - type: OpeningHoursType; - months?: number[]; - days?: number[]; - forbidden_type?: OpeningHoursForbiddenType; - time_start?: number; - time_end?: number; - lunch_break?: boolean; - lunch_start?: number; - lunch_end?: number; - note?: string; -} - -const unknownObject: OpeningHours = { - type: OpeningHoursType.Unknown -} - -const freeObject: OpeningHours = { - type: OpeningHoursType.NonStop -} - -const goneObject : OpeningHours = { - type: OpeningHoursType.Forbidden, - forbidden_type: OpeningHoursForbiddenType.Gone -} - -const generateNoteObject = (note: string): OpeningHours => { - return { - type: OpeningHoursType.Unknown, - note: note - } -} - -towersRef.get().then((querySnapshot : any) => { - querySnapshot.forEach(async (doc : any) => { - const towerDoc = towersRef.doc(doc.id) - const data = await towerDoc.get().then((e : any) => e.data()) - console.log("changing ", data.name) - if (!data.openingHours) { - await towerDoc.update({openingHours: unknownObject}) - } else { - if (typeof data.openingHours === 'object') { - return - } - if (data.openingHours === "neznámé") { - await towerDoc.update({openingHours: unknownObject}) - } - else if (data.openingHours === "volně přístupná") { - await towerDoc.update({openingHours: freeObject}) - } - else if (data.openingHours === "dlouhodobě uzavřena") { - await towerDoc.update({openingHours: goneObject}) - } - else { - await towerDoc.update({openingHours: generateNoteObject(data.openingHours || "")}) - } - } - }); -}); - - - - -/* const dod = async (id: string) => { - const towerDoc = towersRef.doc(id) - const data = await towerDoc.get().then((e : any) => e.data()) - if (!data.openingHours) { - await towerDoc.update({openingHours: unknownObject}) - } else { - if ("type" in data.openingHours) { - return - } - if (data.openingHours === "neznámé") { - await towerDoc.update({openingHours: unknownObject}) - } - else if (data.openingHours === "volně přístupná") { - await towerDoc.update({openingHours: freeObject}) - } - else if (data.openingHours === "dlouhodobě uzavřena") { - await towerDoc.update({openingHours: goneObject}) - } - else { - await towerDoc.update({openingHours: generateNoteObject(data.openingHours || "")}) - } - } -}; - -dod("fo9s2ELP7TtsD61xXM5e"); */ diff --git a/scripts/upload_photos.js b/scripts/upload_photos.js deleted file mode 100644 index c69b9a0..0000000 --- a/scripts/upload_photos.js +++ /dev/null @@ -1,46 +0,0 @@ -const admin = require("firebase-admin"); -const path = require("path"); -const fs = require("fs"); - -const TOWER_ID = "lkYyxlVrzEX0x0D8xH12"; - -// Replace with the path to your Firebase credentials file -const FIREBASE_CREDENTIALS_PATH = "./service_key.json"; -const FIREBASE_STORAGE_BUCKET = "lookout-towers.appspot.com"; - -async function initializeFirebase() { - const serviceAccount = require(FIREBASE_CREDENTIALS_PATH); - admin.initializeApp({ - credential: admin.credential.cert(serviceAccount), - storageBucket: FIREBASE_STORAGE_BUCKET, - }); -} - -async function uploadFolderToFirebase(folderPath) { - const bucket = admin.storage().bucket(); - const files = await fs.promises.readdir(folderPath); - let counter = 1; - for (const file of files) { - const filename = `${TOWER_ID}_${counter}.jpg`; - const filePath = path.join(folderPath, file); - const destinationBlobName = "towers/" + TOWER_ID + "/" + filename; - console.log(destinationBlobName); - const blob = bucket.file(destinationBlobName); - await blob.save(await fs.promises.readFile(filePath)); - await blob.makePublic(); - if (counter === 1) console.log(blob.publicUrl()); - counter += 1; - } -} - -async function main() { - try { - const folderPath = "/home/dowl/Downloads/" + TOWER_ID; - await initializeFirebase(); - await uploadFolderToFirebase(folderPath); - } catch (error) { - console.error("Error uploading photos to Firebase Storage:", error); - } -} - -main(); diff --git a/types/EditableParameter.ts b/types/EditableParameter.ts index e5c7aab..a7554a3 100644 --- a/types/EditableParameter.ts +++ b/types/EditableParameter.ts @@ -1,6 +1,6 @@ import { Tower } from "@/typings"; -export type EditableParameterType = "text" | "select" | "number" | "array" | "date"; +export type EditableParameterType = "text" | "select" | "number" | "array" | "date" | "object"; export type EditableParameter = { name: keyof Tower; diff --git a/types/OpeningHours.ts b/types/OpeningHours.ts new file mode 100644 index 0000000..4e239df --- /dev/null +++ b/types/OpeningHours.ts @@ -0,0 +1,51 @@ +export enum OpeningHoursType { + Unknown, + NonStop, + Occasionally, + SomeMonths, + Forbidden, + WillOpen, + EveryMonth, +} + +export enum OpeningHoursForbiddenType { + Reconstruction, + Temporary, + Gone, + Banned, +} + +export type OpeningHours = { + type: OpeningHoursType; + monthFrom?: number; + monthTo?: number; + isLockedAtNight?: boolean; + days?: number[]; + forbiddenType?: OpeningHoursForbiddenType; + dayFrom?: number; + dayTo?: number; + lunchBreak?: boolean; + lunchFrom?: number; + lunchTo?: number; + detailText?: string; + detailUrl?: string; +}; + +export const getOpeningHoursTypeName = (type: OpeningHoursType): string => { + switch (type) { + case OpeningHoursType.NonStop: + return "Volně přístupná"; + case OpeningHoursType.Occasionally: + return "Příležitostně otevřená"; + case OpeningHoursType.SomeMonths: + return "Pouze některé měsíce"; + case OpeningHoursType.Forbidden: + return "Nepřístupná"; + case OpeningHoursType.WillOpen: + return "Před zpřístupněním"; + case OpeningHoursType.EveryMonth: + return "Otevřeno celoročně"; + default: + return "Neznámá otevírací doba"; + } +}; diff --git a/typings.d.ts b/typings.d.ts index a60f847..bc42b36 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -1,5 +1,5 @@ import { GeoPoint, Timestamp } from "firebase/firestore"; -import { OpeningHoursForbiddenType, OpeningHoursType } from "./utils/constants"; +import { OpeningHours } from "@/types/OpeningHours"; export type GPS = { latitude: number; @@ -30,12 +30,6 @@ export type UserFromDB = { image: string; }; -export type Filter = { - searchTerm: string; - province: string; - county: string; -}; - export type TowerFirebase = { access?: string; country: string; @@ -119,16 +113,3 @@ export type Visit = { created: string; urls?: string[]; }; - -export type OpeningHours = { - type: OpeningHoursType; - months?: number[]; - days?: number[]; - forbidden_type?: OpeningHoursForbiddenType; - time_start?: number; - time_end?: number; - lunch_break?: boolean; - lunch_start?: number; - lunch_end?: number; - note?: string; -}; diff --git a/utils/constants.ts b/utils/constants.ts index b6160d7..0b6e481 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -32,11 +32,10 @@ export const provincesShortList = [ "Ústecký", ]; - -export const provincesMappedCounty : { [char: string]: string } = { - "Benešov": "Středočeský kraj", - "Beroun": "Středočeský kraj", - "Blansko": "Jihomoravský kraj", +export const provincesMappedCounty: { [char: string]: string } = { + Benešov: "Středočeský kraj", + Beroun: "Středočeský kraj", + Blansko: "Jihomoravský kraj", "Brno-město": "Jihomoravský kraj", "Brno-venkov": "Jihomoravský kraj", Bruntál: "Moravskoslezský kraj", @@ -280,41 +279,6 @@ export const towerTypeMappedUrl = { "kostelní věž": "kostelni_vez", }; -export const MONTHS_CZECH = [ - "Leden", - "Únor", - "Březen", - "Duben", - "Květen", - "Červen", - "Červenec", - "Srpen", - "Září", - "Říjen", - "Listopad", - "Prosinec" -] - -export const DAYS_CZECH = [ - "Pondělí", - "Úterý", - "Středa", - "Čtvrtek", - "Pátek", - "Sobota", - "Neděle" -] - -export enum OpeningHoursType { - Unknown, - NonStop, - Occasionally, - Hours, - Forbidden -} +export const MONTHS_CZECH = ["Leden", "Únor", "Březen", "Duben", "Květen", "Červen", "Červenec", "Srpen", "Září", "Říjen", "Listopad", "Prosinec"]; -export enum OpeningHoursForbiddenType { - Reconstruction, - Temporary, - Gone -} \ No newline at end of file +export const DAYS_CZECH = ["Pondělí", "Úterý", "Středa", "Čtvrtek", "Pátek", "Sobota", "Neděle"];