From 08b1a78e3263e67405ad0750799e4d6cc605d2e3 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 5 Nov 2024 00:29:17 +0200 Subject: [PATCH 1/6] translations in location content, cleanup (commenting ) --- components/header.tsx | 229 +++++++++++++++++----------------- components/layout.tsx | 2 +- components/sidebar.tsx | 1 + pages/cart/locations/[id].tsx | 27 +++- 4 files changed, 141 insertions(+), 118 deletions(-) diff --git a/components/header.tsx b/components/header.tsx index 2dbaff8..6859f7e 100644 --- a/components/header.tsx +++ b/components/header.tsx @@ -1,124 +1,121 @@ -import Link from "next/link" -import { signIn, signOut, useSession } from "next-auth/react" -import styles from "../styles/header.module.css" +// import Link from "next/link" +// import { signIn, signOut, useSession } from "next-auth/react" +// import styles from "../styles/header.module.css" -// The approach used in this component shows how to build a sign in and sign out -// component that works on pages which support both client and server side -// rendering, and avoids any flash incorrect content on initial page load. -export default function Header() { - const { data: session, status } = useSession() - const loading = status === "loading" +// // The approach used in this component shows how to build a sign in and sign out +// // component that works on pages which support both client and server side +// // rendering, and avoids any flash incorrect content on initial page load. +// export default function Header() { +// const { data: session, status } = useSession() +// const loading = status === "loading" -//generate top header with sign in/out button and dropdown menu and user name/surname using tailwindcss +// //generate top header with sign in/out button and dropdown menu and user name/surname using tailwindcss - return ( -
- - {/* */} -
-

- {!session && ( - <> - - You are not signed in - - { - e.preventDefault() - signIn() - }} - > - Sign in - - - )} - {session?.user && ( - <> - {session.user.image && ( - - )} - - Signed in as -
- {session.user.email ?? session.user.name} -
- { - e.preventDefault() - signOut() - }} - > - Sign out - - - )} -

-
- */ } + +//
+// ) +// } diff --git a/components/layout.tsx b/components/layout.tsx index 39d91c5..25279f5 100644 --- a/components/layout.tsx +++ b/components/layout.tsx @@ -1,4 +1,4 @@ -import Header from "./header" + import Link from 'next/link' import Footer from "./footer" import Sidebar from "./sidebar" diff --git a/components/sidebar.tsx b/components/sidebar.tsx index 73ff708..52144db 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -16,6 +16,7 @@ import { UserRole } from "@prisma/client"; const packageVersion = require('../package.json').version; function SidebarMenuItem({ item, session, isSubmenu }) { + // const tMenu = useTranslations('menu'); // const [t, locale] = useState(useTranslations('menu')); // useEffect(() => { diff --git a/pages/cart/locations/[id].tsx b/pages/cart/locations/[id].tsx index c66f338..4a7de4d 100644 --- a/pages/cart/locations/[id].tsx +++ b/pages/cart/locations/[id].tsx @@ -5,6 +5,8 @@ import "react-responsive-carousel/lib/styles/carousel.min.css"; // requires a lo import { GetServerSideProps } from 'next'; import { Location, UserRole } from "@prisma/client"; import axiosServer from '../../../src/axiosServer'; +import { useTranslations, createTranslator } from 'next-intl'; +// import { getTranslations } from 'next-intl/server'; const ViewLocationPage: React.FC = ({ location }) => { const [activeTab, setActiveTab] = useState('mainLocation'); @@ -12,6 +14,7 @@ const ViewLocationPage: React.FC = ({ location }) => { const [images, setImages] = useState([]); const [mainLocationImageCount, setMainLocationImageCount] = useState(0); + const t = useTranslations('content'); useEffect(() => { const mainLocationImages = [location.picture1, location.picture2, location.picture3].filter(Boolean); @@ -98,15 +101,37 @@ const ViewLocationPage: React.FC = ({ location }) => { export const getServerSideProps: GetServerSideProps = async (context) => { const axios = await axiosServer(context); + // Get the locale from context or use default + const locale = context.locale || 'en'; + const messages = (await import(`../../../content/i18n/${locale}.json`)).default; + + const t = createTranslator({ locale, messages }); + // Function to replace placeholders in HTML content + const replacePlaceholders = (content: string) => { + if (!content) return ''; + const placeholderPattern = /{([^}]+)}/g; + return content.replace(placeholderPattern, (match, key) => { + try { + return t(key); + } catch (error) { + return match; + } + }); + }; + + + const { data: location } = await axios.get( `${process.env.NEXT_PUBLIC_PUBLIC_URL}/api/data/locations/${context.params.id}` ); + location.content = replacePlaceholders(location.content); + if (location.backupLocationId !== null) { const { data: backupLocation } = await axios.get( process.env.NEXT_PUBLIC_PUBLIC_URL + "/api/data/locations/" + location.backupLocationId ); location.backupLocationName = backupLocation.name; - location.backupLocationContent = backupLocation ? backupLocation.content : ""; + location.backupLocationContent = backupLocation ? replacePlaceholders(backupLocation.content) : ""; location.backupLocationImages = backupLocation ? [backupLocation.picture1, backupLocation.picture2, backupLocation.picture3].filter(Boolean) : []; } From 6b76f27351237f02c5aaaa1c32c629eee44f9dcf Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Tue, 5 Nov 2024 00:40:16 +0200 Subject: [PATCH 2/6] support for translations in location content; translations for locations --- content/i18n/bg.json | 8 ++++++++ content/i18n/en.json | 8 ++++++++ content/i18n/ru.json | 11 +++++++++-- pages/cart/locations/[id].tsx | 11 +++++++++-- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/content/i18n/bg.json b/content/i18n/bg.json index 26788cf..8ec5af7 100644 --- a/content/i18n/bg.json +++ b/content/i18n/bg.json @@ -32,5 +32,13 @@ "statistics": "Статистика", "coverMeLogs": "Замествания", "translations": "Преводи" + }, + "content": { + "location": { + "warehouse": { + "description": "- снимки как да се поставят количките\n- снимка с код за катинар\nвсеки може да всима/връща\n- преди да влизаме трябва да почистим краката.\n- зареждаме/почистваме извън\n- влизане/озлизане няма код\n- влизане/излизане има код", + "title": "СНЛАД" + } + } } } \ No newline at end of file diff --git a/content/i18n/en.json b/content/i18n/en.json index 19bce02..87077a2 100644 --- a/content/i18n/en.json +++ b/content/i18n/en.json @@ -32,5 +32,13 @@ "statistics": "View Statistics", "coverMeLogs": "View CoverMe logs", "translations": "Translation" + }, + "content": { + "location": { + "warehouse": { + "description": "- снимки как да се поставят количките\n- снимка с код за катинар\nвсеки може да всима/връща\n- преди да влизаме трябва да почистим краката.\n- зареждаме/почистваме извън\n- влизане/озлизане няма код\n- влизане/излизане има код", + "title": "СНЛАД" + } + } } } \ No newline at end of file diff --git a/content/i18n/ru.json b/content/i18n/ru.json index c68913e..fb8f07b 100644 --- a/content/i18n/ru.json +++ b/content/i18n/ru.json @@ -8,10 +8,17 @@ "BG": "болгарский", "EN": "английский", "RU": "русский", - "login": "вход", - "contacts": "Контакти te" + "login": "вход" }, "menu": { "dashboard": "Начало" + }, + "content": { + "location": { + "warehouse": { + "description": "- снимки как да се поставят количките\n- снимка с код за катинар\nвсеки може да всима/връща\n- преди да влизаме трябва да почистим краката.\n- зареждаме/почистваме извън\n- влизане/озлизане няма код\n- влизане/излизане има код", + "title": "СНЛАД" + } + } } } \ No newline at end of file diff --git a/pages/cart/locations/[id].tsx b/pages/cart/locations/[id].tsx index 4a7de4d..7101159 100644 --- a/pages/cart/locations/[id].tsx +++ b/pages/cart/locations/[id].tsx @@ -112,9 +112,16 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const placeholderPattern = /{([^}]+)}/g; return content.replace(placeholderPattern, (match, key) => { try { - return t(key); + const translation = t('content.' + key); + // Check if translation exists and is not empty + if (translation && translation !== 'content.' + key) { + return translation; + } + // Return formatted placeholder if translation not found + return `[${locale}:${key}]`; } catch (error) { - return match; + // Return formatted placeholder on error + return `[${locale}:${key}]`; } }); }; From 6495af9d0bde292c04d08c4c846ea2784e6c05fb Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 16 Nov 2024 23:06:58 +0200 Subject: [PATCH 3/6] fix report display --- pages/cart/reports/coverMe.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/pages/cart/reports/coverMe.tsx b/pages/cart/reports/coverMe.tsx index 0084e85..7d0eedf 100644 --- a/pages/cart/reports/coverMe.tsx +++ b/pages/cart/reports/coverMe.tsx @@ -12,6 +12,12 @@ import ProtectedRoute from '../../../components/protectedRoute'; import { Location, Shift, UserRole, EventLog, EventType, EventLogType } from "@prisma/client"; import { set } from 'date-fns'; +const timeFilters = [ + { label: "1 седмица", value: 7 }, + { label: "1 месец", value: 30 }, + { label: "3 месеца", value: 90 }, + { label: "Всички", value: null }, +]; export default function EventLogList() { const [eventLogs, setEventLog] = useState([]); @@ -92,7 +98,11 @@ export default function EventLogList() { {!showOpenRequests && (eventLogs.map((event) => ( {new Date(event.date).toLocaleString('bg')} - {event.publisher.firstName + " " + event.publisher.lastName} + + {event.publisher?.firstName && event.publisher?.lastName + ? `${event.publisher.firstName} ${event.publisher.lastName}` + : '???'} + {new Date(event.shift?.startTime).toLocaleString('bg')} {event.shift?.assignments.map((ass) => ( From 8037f4daf4efd4ad7380a79ddb12c08634050015 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 16 Nov 2024 23:20:47 +0200 Subject: [PATCH 4/6] add timeperiod filter to logs (default all) --- pages/cart/reports/coverMe.tsx | 199 +++++++++++++++++++-------------- 1 file changed, 113 insertions(+), 86 deletions(-) diff --git a/pages/cart/reports/coverMe.tsx b/pages/cart/reports/coverMe.tsx index 7d0eedf..9b06a68 100644 --- a/pages/cart/reports/coverMe.tsx +++ b/pages/cart/reports/coverMe.tsx @@ -1,16 +1,11 @@ -//page to show all repots in the database with a link to the report page -import axiosInstance from '../../../src/axiosSecure'; import { useEffect, useState } from "react"; -import toast from "react-hot-toast"; +import axiosInstance from "../../../src/axiosSecure"; import { useRouter } from "next/router"; import Link from "next/link"; -import { useSession } from "next-auth/react" -//const common = require('src/helpers/common'); -import common from '../../../src/helpers/common'; +import { useSession } from "next-auth/react"; import Layout from "../../../components/layout"; -import ProtectedRoute from '../../../components/protectedRoute'; -import { Location, Shift, UserRole, EventLog, EventType, EventLogType } from "@prisma/client"; -import { set } from 'date-fns'; +import ProtectedRoute from "../../../components/protectedRoute"; +import { UserRole, EventLogType } from "@prisma/client"; const timeFilters = [ { label: "1 седмица", value: 7 }, @@ -22,66 +17,105 @@ const timeFilters = [ export default function EventLogList() { const [eventLogs, setEventLog] = useState([]); const [requestedAssignments, setRequestedAssignments] = useState([]); - const router = useRouter(); - const { data: session } = useSession(); - const [locations, setLocations] = useState([]); const [showOpenRequests, setShowOpenRequests] = useState(false); + const [selectedTimeFilter, setSelectedTimeFilter] = useState(null); // Time filter state + const { data: session } = useSession(); useEffect(() => { const fetchLocations = async () => { try { - const { data: eventLogsDataold } = await axiosInstance.get(`/api/data/prisma/eventLog?where={"type":"${EventLogType.AssignmentReplacementAccepted}"}&include={"publisher":{"select":{"firstName":true,"lastName":true}},"shift":{"include":{"assignments":{"include":{"publisher":{"select":{"firstName":true,"lastName":true}}}}}}}`); - - // const where = encodeURIComponent(`{OR: [{type: "${EventLogType.AssignmentReplacementAccepted}"}, {type: "${EventLogType.AssignmentReplacementManual}"}]}`); const where = encodeURIComponent(JSON.stringify({ OR: [ { type: EventLogType.AssignmentReplacementAccepted }, - { type: EventLogType.AssignmentReplacementManual } - ] + { type: EventLogType.AssignmentReplacementManual }, + { type: EventLogType.AssignmentReplacementRequested } + ], })); - const { data: eventLogsData } = await axiosInstance.get(`/api/data/prisma/eventLog?where=${where}&include={"publisher":{"select":{"firstName":true,"lastName":true}},"shift":{"include":{"assignments":{"include":{"publisher":{"select":{"firstName":true,"lastName":true}}}}}}}`); + const { data: eventLogsData } = await axiosInstance.get( + `/api/data/prisma/eventLog?where=${where}&include={"publisher":{"select":{"firstName":true,"lastName":true}},"shift":{"include":{"assignments":{"include":{"publisher":{"select":{"firstName":true,"lastName":true}}}}}}}` + ); setEventLog(eventLogsData); - const { data: shiftsData } = await axiosInstance.get(`/api/data/prisma/assignment?where={"publicGuid":{"not":"null"}}&include={"shift":{"include":{"assignments":{"include":{"publisher":{"select":{"firstName":true,"lastName":true}}}}}},"publisher":{"select":{"firstName":true,"lastName":true}}}`); + const { data: shiftsData } = await axiosInstance.get( + `/api/data/prisma/assignment?where={"publicGuid":{"not":"null"}}&include={"shift":{"include":{"assignments":{"include":{"publisher":{"select":{"firstName":true,"lastName":true}}}}}},"publisher":{"select":{"firstName":true,"lastName":true}}}` + ); setRequestedAssignments(shiftsData); - } catch (error) { console.error(error); } }; + if (!locations.length) { fetchLocations(); } }, []); - + // Filter events based on the selected time range + const filteredEventLogs = eventLogs.filter((event) => { + if (!selectedTimeFilter) return true; + const eventDate = new Date(event.date); + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - selectedTimeFilter); + return eventDate >= cutoffDate; + }); return ( -

Заявки за заместване

- {/* - - */} -
-
-
- - +
+ +
+
); } - - From 3236286a2a4ffc5c4e538ace7a52e973a101660e Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Sat, 16 Nov 2024 23:32:25 +0200 Subject: [PATCH 5/6] enhanced filters in coverMe report --- pages/cart/reports/coverMe.tsx | 61 ++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/pages/cart/reports/coverMe.tsx b/pages/cart/reports/coverMe.tsx index 9b06a68..06557dc 100644 --- a/pages/cart/reports/coverMe.tsx +++ b/pages/cart/reports/coverMe.tsx @@ -1,7 +1,5 @@ import { useEffect, useState } from "react"; import axiosInstance from "../../../src/axiosSecure"; -import { useRouter } from "next/router"; -import Link from "next/link"; import { useSession } from "next-auth/react"; import Layout from "../../../components/layout"; import ProtectedRoute from "../../../components/protectedRoute"; @@ -14,24 +12,28 @@ const timeFilters = [ { label: "Всички", value: null }, ]; +const eventTypes = [ + { label: "Заявки", value: EventLogType.AssignmentReplacementRequested }, + { label: "Приети замествания", value: EventLogType.AssignmentReplacementAccepted }, + { label: "Ръчно въведени замествания", value: EventLogType.AssignmentReplacementManual }, +]; + export default function EventLogList() { const [eventLogs, setEventLog] = useState([]); const [requestedAssignments, setRequestedAssignments] = useState([]); - const [locations, setLocations] = useState([]); const [showOpenRequests, setShowOpenRequests] = useState(false); const [selectedTimeFilter, setSelectedTimeFilter] = useState(null); // Time filter state + const [selectedEventTypes, setSelectedEventTypes] = useState(eventTypes.map((et) => et.value)); // Default: all types const { data: session } = useSession(); useEffect(() => { - const fetchLocations = async () => { + const fetchEventLogs = async () => { try { - const where = encodeURIComponent(JSON.stringify({ - OR: [ - { type: EventLogType.AssignmentReplacementAccepted }, - { type: EventLogType.AssignmentReplacementManual }, - { type: EventLogType.AssignmentReplacementRequested } - ], - })); + const where = encodeURIComponent( + JSON.stringify({ + OR: selectedEventTypes.map((type) => ({ type })), + }) + ); const { data: eventLogsData } = await axiosInstance.get( `/api/data/prisma/eventLog?where=${where}&include={"publisher":{"select":{"firstName":true,"lastName":true}},"shift":{"include":{"assignments":{"include":{"publisher":{"select":{"firstName":true,"lastName":true}}}}}}}` @@ -48,10 +50,8 @@ export default function EventLogList() { } }; - if (!locations.length) { - fetchLocations(); - } - }, []); + fetchEventLogs(); + }, [selectedEventTypes]); // Filter events based on the selected time range const filteredEventLogs = eventLogs.filter((event) => { @@ -62,6 +62,15 @@ export default function EventLogList() { return eventDate >= cutoffDate; }); + // Toggle event type selection + const toggleEventType = (eventType) => { + setSelectedEventTypes((prev) => + prev.includes(eventType) + ? prev.filter((type) => type !== eventType) + : [...prev, eventType] + ); + }; + return ( @@ -82,10 +91,10 @@ export default function EventLogList() { checked={!showOpenRequests} className="sr-only" /> - Приети заявки + Приети/затворени заявки