From ec9516e5e44de3b15f9d6aad1077c42c4cf5097c Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 13 Jan 2024 19:43:02 +0100 Subject: [PATCH] services --- public/locales/de/translation.json | 20 +- public/locales/en/translation.json | 20 +- src/Components/MyEmpty/index.js | 8 + src/Components/MyIcon/index.js | 165 +++++++++- src/Components/MyTable/index.js | 17 +- src/Pages/Login/index.js | 102 +++--- src/Pages/Store/Calendar/index.js | 6 +- src/Pages/Store/Services/index.js | 505 ++++++++++++++++++----------- src/Pages/UserProfile/index.js | 4 +- src/utils.js | 3 +- 10 files changed, 586 insertions(+), 264 deletions(-) create mode 100644 src/Components/MyEmpty/index.js diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json index 2c0bec9..b3e2af0 100644 --- a/public/locales/de/translation.json +++ b/public/locales/de/translation.json @@ -7,7 +7,13 @@ "delete": "Löschen", "confirm": "Bestätigen", "create": "Erstellen", - "edit": "Bearbeiten" + "edit": "Bearbeiten", + "logout": "Abmelden" + }, + "tooltip": { + "edit": "Bearbeiten", + "delete": "Löschen", + "add": "Hinzufügen" }, "action": "Aktion", "contactAdmin": "Bitte kontaktieren Sie einen Administrator", @@ -86,6 +92,7 @@ "serviceActivityDurationMinutes": "Dauer der Tätigkeit (Minuten)", "serviceActivityDurationMinutesPlaceholder": "Geben Sie die Dauer der Tätigkeit in Minuten ein", "serviceActivityDurationMinutesUnit": "Minuten", + "serviceActivityResponsible": "Verantwortliche Mitarbeiter", "inputRules": { "serviceNameRequired": "Name der Dienstleistung ist erforderlich", "serviceNameMinLength": "Name der Dienstleistung muss mindestens {{minLength}} Zeichen lang sein", @@ -108,6 +115,17 @@ }, "modalEditServiceActivity": { "title": "Tätigkeit bearbeiten" + }, + "popConfirmDeleteService": { + "title": "Dienstleistung löschen", + "description": "Sind Sie sicher, dass Sie diese Dienstleistung löschen möchten?" + }, + "popConfirmDeleteServiceActivity": { + "title": "Tätigkeit löschen", + "description": "Sind Sie sicher, dass Sie diese Tätigkeit löschen möchten?" } + }, + "calendar": { + "pageTitle": "Kalender" } } diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 991be4a..f8cc02b 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -7,7 +7,13 @@ "delete": "Delete", "confirm": "Confirm", "create": "Create", - "edit": "Edit" + "edit": "Edit", + "logout": "Logout" + }, + "tooltip": { + "edit": "Edit", + "delete": "Delete", + "add": "Add" }, "action": "Action", "contactAdmin": "Please contact an administrator", @@ -89,6 +95,7 @@ "serviceActivityDurationMinutes": "Duration of activity (minutes)", "serviceActivityDurationMinutesPlaceholder": "Enter the duration of the activity in minutes", "serviceActivityDurationMinutesUnit": "Minutes", + "serviceActivityResponsible": "Responsible employees", "inputRules": { "serviceNameRequired": "Service name is required", "serviceNameMinLength": "Service name must be at least {{minLength}} characters long", @@ -111,6 +118,17 @@ }, "modalEditServiceActivity": { "title": "Edit activity" + }, + "popConfirmDeleteService": { + "title": "Delete service", + "description": "Are you sure you want to delete this service?" + }, + "popConfirmDeleteServiceActivity": { + "title": "Delete activity", + "description": "Are you sure you want to delete this activity?" } + }, + "calendar": { + "pageTitle": "Calendar" } } diff --git a/src/Components/MyEmpty/index.js b/src/Components/MyEmpty/index.js new file mode 100644 index 0000000..f32343e --- /dev/null +++ b/src/Components/MyEmpty/index.js @@ -0,0 +1,8 @@ +import { Empty } from "antd"; +import { useTranslation } from "react-i18next"; + +export function MyEmpty({ children }) { + const { t } = useTranslation(); + + return {children}; +} diff --git a/src/Components/MyIcon/index.js b/src/Components/MyIcon/index.js index 559845e..c6b8f93 100644 --- a/src/Components/MyIcon/index.js +++ b/src/Components/MyIcon/index.js @@ -1,12 +1,166 @@ import { - CopyOutlined, - EyeInvisibleOutlined, - EyeOutlined, - ReloadOutlined, + DeleteOutlined, + EditOutlined, + PlusOutlined, + QuestionCircleOutlined, } from "@ant-design/icons"; -import { Tooltip } from "antd"; +import { Popconfirm, Tooltip } from "antd"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; +export function MyDeleteIcon({ + propsPopconfirm, + propsTooltip, + onConfirm, + onFetchSuccess, + popConfirmTitle, + popConfirmDescription, + onClick, +}) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + + return ( + , + open: open, + }} + propsTooltip={propsTooltip} + onConfirm={onConfirm} + onFetchSuccess={() => { + setOpen(false); + + onFetchSuccess(); + }} + onCancel={() => setOpen(false)} + popConfirmTitle={popConfirmTitle} + popConfirmDescription={popConfirmDescription} + popConfirmOkText={t("common.button.delete")} + tooltipTitle={t("common.tooltip.delete")} + icon={ + { + setOpen(true); + + if (onClick) onClick(e); + }} + /> + } + /> + ); +} + +export function MyEditIcon({ onClick }) { + const { t } = useTranslation(); + + return ( + } + /> + ); +} + +export function MyPlusIcon({ onClick }) { + const { t } = useTranslation(); + + return ( + } + /> + ); +} + +export function MyIcon({ + popconfirmDisabled, + propsPopconfirm, + propsTooltip, + onConfirm, + onFetchSuccess, + onCancel, + popConfirmTitle, + popConfirmDescription, + popConfirmOkText, + tooltipTitle, + icon, +}) { + const { t } = useTranslation(); + + const [isRequesting, setIsRequesting] = useState(false); + + const content = ( + + {icon} + + ); + + if (popconfirmDisabled) { + return content; + } + + return ( + { + if (onCancel) onCancel(); + }} + onConfirm={() => { + setIsRequesting(true); + + onConfirm() + .then(() => { + setIsRequesting(false); + + onFetchSuccess(); + }) + .catch(() => { + setIsRequesting(false); + }); + }} + > + {content} + + ); +} + +/* + { + setIsRequesting(true); + + myFetch("/users", "DELETE", { + userId: record.key, + }) + .then(() => fetchEmployees()) + .catch((errStatus) => { + console.log(errStatus); + + setIsRequesting(false); + }); + }} +> + + +*/ + +/* export function MyCopyIcon({ text, notificationApi }) { const { t } = useTranslation(); @@ -48,3 +202,4 @@ export function MyReloadIcon({ onClick }) { ); } +*/ diff --git a/src/Components/MyTable/index.js b/src/Components/MyTable/index.js index 1058b4d..8eb9a63 100644 --- a/src/Components/MyTable/index.js +++ b/src/Components/MyTable/index.js @@ -1,26 +1,13 @@ import { Table } from "antd"; -import { useTranslation } from "react-i18next"; +import { MyEmpty } from "../MyEmpty"; export default function MyTable({ props }) { - const { t } = useTranslation(); - return ( - {t("common.noDataFound")} - - ), + emptyText: , }} /> ); diff --git a/src/Pages/Login/index.js b/src/Pages/Login/index.js index b178e0c..49ffb35 100644 --- a/src/Pages/Login/index.js +++ b/src/Pages/Login/index.js @@ -46,7 +46,55 @@ export default function Login() { closable={false} centered keyboard={false} - footer={null} + footer={ + + } > - -
- -
diff --git a/src/Pages/Store/Calendar/index.js b/src/Pages/Store/Calendar/index.js index 3a9371b..0e14571 100644 --- a/src/Pages/Store/Calendar/index.js +++ b/src/Pages/Store/Calendar/index.js @@ -1,7 +1,11 @@ +import { useTranslation } from "react-i18next"; + export default function StoreCalendar() { + const { t } = useTranslation(); + return ( <> -

Calendar

+

{t("calendar.pageTitle")}

); } diff --git a/src/Pages/Store/Services/index.js b/src/Pages/Store/Services/index.js index a4af356..c8bc151 100644 --- a/src/Pages/Store/Services/index.js +++ b/src/Pages/Store/Services/index.js @@ -1,21 +1,16 @@ -import { - ArrowDownOutlined, - ArrowUpOutlined, - DeleteOutlined, - EditOutlined, - PlusOutlined, -} from "@ant-design/icons"; +import { PlusOutlined } from "@ant-design/icons"; import { Avatar, Button, Card, Collapse, - Empty, Form, Grid, Skeleton, Space, + Spin, Tooltip, + Typography, } from "antd"; import { useTranslation } from "react-i18next"; import { useEffect, useState } from "react"; @@ -26,6 +21,13 @@ import MyModal, { MyModalCloseSaveButtonFooter, } from "../../../Components/MyModal"; import { MyFormInput } from "../../../Components/MyFormInputs"; +import MyTable from "../../../Components/MyTable"; +import { + MyDeleteIcon, + MyEditIcon, + MyPlusIcon, +} from "../../../Components/MyIcon"; +import { MyEmpty } from "../../../Components/MyEmpty"; const { useBreakpoint } = Grid; @@ -89,11 +91,21 @@ export default function StoreServices() { {isRequestingServices ? ( <> - - - + {/* + + */} + +
+ +
- ) : ( + ) : servicesData.services.length > 0 ? ( <> {servicesData.services.map((service) => ( ))} + ) : ( + )}
@@ -117,6 +132,7 @@ export default function StoreServices() { setAddEditServiceActivityModalOptions={ setAddEditServiceActivityModalOptions } + users={servicesData.users} /> ); @@ -128,7 +144,10 @@ function Service({ storeId, setAddEditServiceActivityModalOptions, setAddEditServiceModalOptions, + fetchServices, }) { + const { t } = useTranslation(); + const [isRequestingActivities, setIsRequestingActivities] = useState(false); const [isOpen, setIsOpen] = useState(false); @@ -172,15 +191,17 @@ function Service({ {service.name} - e.stopPropagation()} /> e.stopPropagation()} - /> - + */} + { e.stopPropagation(); @@ -191,7 +212,8 @@ function Service({ }); }} /> - { e.stopPropagation(); @@ -202,7 +224,25 @@ function Service({ }); }} /> - e.stopPropagation()} /> + e.stopPropagation()} + propsPopconfirm={{ + placement: "left", + }} + onConfirm={() => { + return myFetch( + `/store/services/${service.service_id}`, + "DELETE" + ); + }} + onFetchSuccess={fetchServices} + popConfirmTitle={t( + "storeServices.popConfirmDeleteService.title" + )} + popConfirmDescription={t( + "storeServices.popConfirmDeleteService.description" + )} + /> ), @@ -223,50 +263,98 @@ function Service({ ) : ( <> {serviceActivities.length === 0 ? ( - + ) : ( - serviceActivities.map((activity) => ( - - - {users.map((user) => ( - - - {user.username.charAt(0)} - - - ))} - + serviceActivities.map((activity) => { + let userList = []; - - - { - setAddEditServiceActivityModalOptions({ - mode: "edit", - isOpen: true, - activity: activity, - }); - }} - /> - - + if (activity.StoreServiceActivityUsers.length > 0) { + // StoreServiceActivityUsers is only an array of user_ids + // we need to get the user object from the users array + + for (let i = 0; i < users.length; i++) { + for ( + let j = 0; + j < activity.StoreServiceActivityUsers.length; + j++ + ) { + if ( + users[i].user_id === + activity.StoreServiceActivityUsers[j].user_id + ) { + userList.push(users[i]); + } + } } - > -

{activity.description}

-

Preis: {activity.price} €

-

Dauer: {activity.duration} Minuten

-
- )) + } else { + // if there are no users assigned to this activity, we just use the whole users array + userList = users; + } + + return ( + + + {userList.map((user) => ( + + + {user.username.charAt(0)} + + + ))} + + + {/* + + + */} + + { + setAddEditServiceActivityModalOptions({ + mode: "edit", + isOpen: true, + activity: activity, + }); + }} + /> + + { + return myFetch( + `/store/services/activity/${activity.activity_id}`, + "DELETE" + ); + }} + onFetchSuccess={fetchServiceActivities} + popConfirmTitle={t( + "storeServices.popConfirmDeleteServiceActivity.title" + )} + popConfirmDescription={t( + "storeServices.popConfirmDeleteServiceActivity.description" + )} + /> + + } + > +

{activity.description}

+

Preis: {activity.price} €

+

Dauer: {activity.duration} Minuten

+
+ ); + }) )} )} @@ -413,26 +501,17 @@ function ModalAddEditService({ ); } -let defaultModalAddServiceFormValidOptions = { - serviceActivityName: false, - serviceActivityDescription: false, - serviceActivityPrice: false, - serviceActivityDurationHours: false, - serviceActivityDurationMinutes: false, -}; - function ModalAddEditServiceActivity({ fetchServices, addEditServiceActivityModalOptions, setAddEditServiceActivityModalOptions, + users, }) { const { t } = useTranslation(); - const [isFormValid, setIsFormValid] = useState( - defaultModalAddServiceFormValidOptions - ); const [form] = Form.useForm(); const [isRequesting, setIsRequesting] = useState(false); + const [selectedEmployeesRowKeys, setSelectedEmployeesRowKeys] = useState([]); const handleModalClose = () => { setAddEditServiceActivityModalOptions({ @@ -443,21 +522,28 @@ function ModalAddEditServiceActivity({ form.resetFields(); }; + const getTableColumns = () => { + return [ + { + title: t("common.username"), + dataIndex: "username", + key: "username", + }, + ]; + }; + + const getTableItems = () => { + return users.map((user) => { + return { + key: user.user_id, + username: user.username, + }; + }); + }; + useEffect(() => { if (!addEditServiceActivityModalOptions.isOpen) return; - setIsFormValid( - addEditServiceActivityModalOptions.mode === "add" - ? defaultModalAddServiceFormValidOptions - : { - serviceActivityName: true, - serviceActivityDescription: true, - serviceActivityPrice: true, - serviceActivityDurationHours: true, - serviceActivityDurationMinutes: true, - } - ); - if (addEditServiceActivityModalOptions.mode === "edit") { form.setFieldsValue({ serviceActivityName: addEditServiceActivityModalOptions.activity.name, @@ -470,6 +556,21 @@ function ModalAddEditServiceActivity({ serviceActivityDurationMinutes: addEditServiceActivityModalOptions.activity.duration % 60, }); + + setSelectedEmployeesRowKeys( + addEditServiceActivityModalOptions.activity.StoreServiceActivityUsers + .length === 0 + ? users.map((user) => user.user_id) + : addEditServiceActivityModalOptions.activity.StoreServiceActivityUsers.map( + (user) => user.user_id + ) + ); + } else { + form.setFieldsValue({ + serviceActivityDurationHours: 0, + }); + + setSelectedEmployeesRowKeys(users.map((user) => user.user_id)); } }, [addEditServiceActivityModalOptions.isOpen]); @@ -486,87 +587,146 @@ function ModalAddEditServiceActivity({ addEditServiceActivityModalOptions.mode === "add" ? ( { - setIsRequesting(true); + form + .validateFields() + .then((values) => { + setIsRequesting(true); - myFetch("/store/services/activity", "POST", { - serviceId: - addEditServiceActivityModalOptions.service.service_id, - name: form.getFieldValue("serviceActivityName"), - description: form.getFieldValue("serviceActivityDescription"), - price: form.getFieldValue("serviceActivityPrice"), - duration: - form.getFieldValue("serviceActivityDurationHours") * 60 + - form.getFieldValue("serviceActivityDurationMinutes"), - }) - .then(() => { - setIsRequesting(false); - handleModalClose(); + myFetch("/store/services/activity", "POST", { + serviceId: + addEditServiceActivityModalOptions.service.service_id, + name: values.serviceActivityName, + description: values.serviceActivityDescription, + price: values.serviceActivityPrice, + duration: + values.serviceActivityDurationHours * 60 + + values.serviceActivityDurationMinutes, + userIds: + selectedEmployeesRowKeys.length === users.length + ? [] + : selectedEmployeesRowKeys, + }) + .then(() => { + setIsRequesting(false); + handleModalClose(); - fetchServices(); + fetchServices(); + }) + .catch((errStatus) => { + console.log(errStatus); + }); }) - .catch((errStatus) => { - console.log(errStatus); + .catch((info) => { + console.log("Validate Failed:", info); }); }} /> ) : ( { + const formServiceActivityName = form.getFieldValue( + "serviceActivityName" + ); + const formServiceActivityDescription = form.getFieldValue( + "serviceActivityDescription" + ); + const formServiceActivityPrice = form.getFieldValue( + "serviceActivityPrice" + ); + const formServiceActivityDurationHours = form.getFieldValue( + "serviceActivityDurationHours" + ); + const formServiceActivityDurationMinutes = form.getFieldValue( + "serviceActivityDurationMinutes" + ); + // if the service name didn't change, don't send a request if ( addEditServiceActivityModalOptions.activity.name === - form.getFieldValue("serviceActivityName") && + formServiceActivityName && addEditServiceActivityModalOptions.activity.description === - form.getFieldValue("serviceActivityDescription") && + formServiceActivityDescription && addEditServiceActivityModalOptions.activity.price === - form.getFieldValue("serviceActivityPrice") && + formServiceActivityPrice && addEditServiceActivityModalOptions.activity.duration === - form.getFieldValue("serviceActivityDurationHours") * 60 + - form.getFieldValue("serviceActivityDurationMinutes") + formServiceActivityDurationHours * 60 + + formServiceActivityDurationMinutes && + (selectedEmployeesRowKeys.length === users.length || + addEditServiceActivityModalOptions.activity + .StoreServiceActivityUsers.length === + selectedEmployeesRowKeys.length) ) { handleModalClose(); - console.log("same"); return; } - setIsRequesting(true); - - myFetch("/store/services/activity/update", "POST", { + let validateFields = []; + let body = { activityId: addEditServiceActivityModalOptions.activity.activity_id, - name: form.getFieldValue("serviceActivityName"), - description: form.getFieldValue("serviceActivityDescription"), - price: form.getFieldValue("serviceActivityPrice"), - duration: - form.getFieldValue("serviceActivityDurationHours") * 60 + - form.getFieldValue("serviceActivityDurationMinutes"), - }) - .then(() => { - setIsRequesting(false); - handleModalClose(); + }; - fetchServices(); + if ( + formServiceActivityName !== + addEditServiceActivityModalOptions.activity.name + ) { + validateFields.push("serviceActivityName"); + body.name = formServiceActivityName; + } + + if ( + formServiceActivityDescription !== + addEditServiceActivityModalOptions.activity.description + ) { + validateFields.push("serviceActivityDescription"); + body.description = formServiceActivityDescription; + } + + if ( + formServiceActivityPrice !== + addEditServiceActivityModalOptions.activity.price + ) { + validateFields.push("serviceActivityPrice"); + body.price = formServiceActivityPrice; + } + + let formDuration = + formServiceActivityDurationHours * 60 + + formServiceActivityDurationMinutes; + + if ( + formDuration !== + addEditServiceActivityModalOptions.activity.duration + ) { + validateFields.push("serviceActivityDurationHours"); + validateFields.push("serviceActivityDurationMinutes"); + body.duration = formDuration; + } + + body.userIds = selectedEmployeesRowKeys; + + form + .validateFields() + .then(() => { + setIsRequesting(true); + + myFetch("/store/services/activity/update", "POST", body) + .then(() => { + setIsRequesting(false); + handleModalClose(); + + fetchServices(); + }) + .catch((errStatus) => { + console.log(errStatus); + }); }) - .catch((errStatus) => { - console.log(errStatus); + .catch((info) => { + console.log("Validate Failed:", info); }); }} /> @@ -574,55 +734,36 @@ function ModalAddEditServiceActivity({ } >
- - setIsFormValid({ - ...isFormValid, - serviceActivityName: isValid, - }) - } - /> + - - setIsFormValid({ - ...isFormValid, - serviceActivityDescription: isValid, - }) - } - /> + - - setIsFormValid({ - ...isFormValid, - serviceActivityPrice: isValid, - }) - } - /> + - - setIsFormValid({ - ...isFormValid, - serviceActivityDurationHours: isValid, - }) - } - /> + - - setIsFormValid({ - ...isFormValid, - serviceActivityDurationMinutes: isValid, - }) - } - /> + + + + + {t("storeServices.serviceActivityResponsible")} + + + + setSelectedEmployeesRowKeys(newSelectedRowKeys), + }, + loading: isRequesting, + columns: getTableColumns(), + dataSource: getTableItems(), + size: "small", + pagination: false, + }} + /> + ); @@ -651,13 +792,12 @@ function ServiceNameFormInput({ formItemName }) { ); } -function ServiceActivityNameFormInput({ formItemName, setIsInputValid }) { +function ServiceActivityNameFormInput({ formItemName }) { const { t } = useTranslation(); return ( { - console.log("Logout"); - setUserSession(); window.location.href = "/"; @@ -27,7 +25,7 @@ export default function UserProfile({ userSession, setUserSession }) { }).catch(console.error); }} > - Logout + {t("common.button.logout")} } > diff --git a/src/utils.js b/src/utils.js index cb6d9bf..425bebd 100644 --- a/src/utils.js +++ b/src/utils.js @@ -54,7 +54,6 @@ export const Constants = { SUPPORT: "/support", USER_PROFILE: "/user-profile", }, - GLOBALS: { MIN_USERNAME_LENGTH: 3, MAX_USERNAME_LENGTH: 20, @@ -73,7 +72,7 @@ export const Constants = { MIN_STORE_SERVICE_ACTIVITY_DURATION_HOURS: 0, MAX_STORE_SERVICE_ACTIVITY_DURATION_HOURS: 23, MIN_STORE_SERVICE_ACTIVITY_DURATION_MINUTES: 0, - MAX_STORE_SERVICE_ACTIVITY_DURATION_MINUTES: 60, + MAX_STORE_SERVICE_ACTIVITY_DURATION_MINUTES: 59, }, DELAY_ACCOUNT_NAME_CHECK: 250, MAX_AVATAR_SIZE: 5 * 1024 * 1024,