import { CaretRightOutlined, PlusOutlined } from "@ant-design/icons"; import { Avatar, Button, Card, Collapse, Flex, Form, Grid, Skeleton, Space, Spin, Tooltip, Typography, notification, } from "antd"; import { useTranslation } from "react-i18next"; import { useEffect, useState } from "react"; import { AppStyle, Constants, myFetch, showInputsInvalidNotification, } from "../../../utils"; import { useParams } from "react-router-dom"; import MyModal, { MyModalCloseCreateButtonFooter, 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"; import CountUp from "react-countup"; const { useBreakpoint } = Grid; export default function StoreServices() { const { t } = useTranslation(); const [notificationApi, notificationContextHolder] = notification.useNotification(); const screenBreakpoint = useBreakpoint(); const { storeId } = useParams(); const [isRequestingServices, setIsRequestingServices] = useState(false); const [addEditServiceModalOptions, setAddEditServiceModalOptions] = useState({ mode: "add", // add | edit isOpen: false, service: null, }); const [ addEditServiceActivityModalOptions, setAddEditServiceActivityModalOptions, ] = useState({ mode: "add", // add | edit isOpen: false, activity: null, }); const [servicesData, setServicesData] = useState({ services: [], users: [], }); const fetchServices = () => { setIsRequestingServices(true); myFetch({ url: `/store/services/${storeId}`, method: "GET", notificationApi: notificationApi, t: t, }) .then((data) => { setIsRequestingServices(false); setServicesData(data); }) .catch(() => {}); }; useEffect(() => fetchServices(), []); return ( <> {notificationContextHolder}

{t("storeServices.pageTitle")} {" ("} {")"}

{isRequestingServices ? ( <> {/* */}
) : servicesData.services.length > 0 ? ( <> {servicesData.services.map((service) => ( ))} ) : ( )}
); } function Service({ service, users, storeId, setAddEditServiceActivityModalOptions, setAddEditServiceModalOptions, fetchServices, }) { const { t } = useTranslation(); const [notificationApi, notificationContextHolder] = notification.useNotification(); const [isRequestingActivities, setIsRequestingActivities] = useState(false); const [isOpen, setIsOpen] = useState(false); const [serviceActivities, setServiceActivities] = useState([]); const fetchServiceActivities = () => { if (!isOpen) return; setIsRequestingActivities(true); myFetch({ url: `/store/services/activities/${storeId}/${service.service_id}`, method: "GET", notificationApi: notificationApi, t: t, }) .then((data) => { setIsRequestingActivities(false); setServiceActivities(data.activities); }) .catch((errStatus) => { console.log(errStatus); }); }; useEffect(() => fetchServiceActivities(), [isOpen]); const expandIcon = ({ isActive }) => { if (isRequestingActivities) return ; return ; }; return ( <> {notificationContextHolder} setIsOpen(e.length !== 0)} expandIcon={expandIcon} items={[ { key: "1", label: (
{service.name} {/* e.stopPropagation()} /> e.stopPropagation()} /> */} { e.stopPropagation(); setAddEditServiceActivityModalOptions({ mode: "add", isOpen: true, service: service, }); }} /> { e.stopPropagation(); setAddEditServiceModalOptions({ mode: "edit", isOpen: true, service: service, }); }} /> e.stopPropagation()} propsPopconfirm={{ placement: "left", }} onConfirm={() => { return myFetch({ url: `/store/services/${service.service_id}`, method: "DELETE", notificationApi: notificationApi, t: t, }); }} onFetchSuccess={fetchServices} popConfirmTitle={t( "storeServices.popConfirmDeleteService.title" )} popConfirmDescription={t( "storeServices.popConfirmDeleteService.description" )} />
), children: ( {isRequestingActivities ? (

loading

loading

loading

) : ( <> {serviceActivities.length === 0 ? ( ) : ( serviceActivities.map((activity) => { let userList = []; 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]); } } } } 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({ url: `/store/services/activity/${activity.activity_id}`, method: "DELETE", notificationApi: notificationApi, t: t, }); }} onFetchSuccess={fetchServiceActivities} popConfirmTitle={t( "storeServices.popConfirmDeleteServiceActivity.title" )} popConfirmDescription={t( "storeServices.popConfirmDeleteServiceActivity.description" )} />
} > {activity.description}

{activity.price} €

{activity.duration}{" "} {activity.duration === 1 ? t("common.unit.minute") : t("common.unit.minutes")}{" "} {/* {durationToHoursAndMinutes( t, activity.duration )} */}
); }) )} )} ), }, ]} /> ); } function durationToHoursAndMinutes(t, duration) { const MINUTES_IN_HOUR = 60; const singularHourText = t("common.unit.hour"); const pluralHourText = t("common.unit.hours"); const singularMinuteText = t("common.unit.minute"); const pluralMinuteText = t("common.unit.minutes"); const hours = Math.floor(duration / MINUTES_IN_HOUR); const minutes = duration % MINUTES_IN_HOUR; if (hours === 0 && minutes < 61) { return ""; } let response = ""; if (hours === 0) { response = `${minutes} ${ minutes === 1 ? singularMinuteText : pluralMinuteText }`; } else if (minutes === 0) { response = `${hours} ${hours === 1 ? singularHourText : pluralHourText}`; } else { response = `${hours} ${hours === 1 ? singularHourText : pluralHourText} ${t( "common.unit.separator" )} ${minutes} ${minutes === 1 ? singularMinuteText : pluralMinuteText}`; } return `(${response})`; } // this modal is used to create and edit services function ModalAddEditService({ storeId, fetchServices, addEditServiceModalOptions, setAddEditServiceModalOptions, }) { const { t } = useTranslation(); const [notificationApi, notificationContextHolder] = notification.useNotification(); const screenBreakpoint = useBreakpoint(); const [form] = Form.useForm(); const [isRequesting, setIsRequesting] = useState(false); const handleModalClose = () => { setAddEditServiceModalOptions({ ...addEditServiceModalOptions, isOpen: false, mode: "add", }); form.resetFields(); }; useEffect(() => { if (!addEditServiceModalOptions.isOpen) return; if (addEditServiceModalOptions.mode === "edit") { form.setFieldsValue({ serviceName: addEditServiceModalOptions.service.name, }); } }, [addEditServiceModalOptions.isOpen]); return ( <> {notificationContextHolder} { form .validateFields() .then((values) => { setIsRequesting(true); myFetch({ url: "/store/services/service", method: "POST", body: { storeId: storeId, name: values.serviceName, }, notificationApi: notificationApi, t: t, }) .then(() => { setIsRequesting(false); handleModalClose(); fetchServices(); }) .catch((errStatus) => { setIsRequesting(false); if (errStatus === 400) { notificationApi["error"]({ message: t("common.request.inputsInvalid.title"), description: t( "common.request.inputsInvalid.description" ), }); } }); }) .catch(() => showInputsInvalidNotification(notificationApi, t) ); }} /> ) : ( { // if the service name didn't change, don't send a request let formServiceName = form.getFieldValue("serviceName"); if ( addEditServiceModalOptions.service.name === formServiceName ) { handleModalClose(); return; } form .validateFields() .then(() => { setIsRequesting(true); myFetch({ url: "/store/services/update", method: "POST", body: { serviceId: addEditServiceModalOptions.service.service_id, name: formServiceName, }, notificationApi: notificationApi, t: t, }) .then(() => { setIsRequesting(false); handleModalClose(); fetchServices(); }) .catch((errStatus) => { setIsRequesting(false); if (errStatus === 400) { notificationApi["error"]({ message: t("common.request.inputsInvalid.title"), description: t( "common.request.inputsInvalid.description" ), }); } }); }) .catch(() => showInputsInvalidNotification(notificationApi, t) ); }} /> ) } >
); } function ModalAddEditServiceActivity({ fetchServices, addEditServiceActivityModalOptions, setAddEditServiceActivityModalOptions, users, }) { const { t } = useTranslation(); const [notificationApi, notificationContextHolder] = notification.useNotification(); const [form] = Form.useForm(); const [isRequesting, setIsRequesting] = useState(false); const [selectedEmployeesRowKeys, setSelectedEmployeesRowKeys] = useState([]); const handleModalClose = () => { setAddEditServiceActivityModalOptions({ ...addEditServiceActivityModalOptions, isOpen: false, }); 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; if (addEditServiceActivityModalOptions.mode === "edit") { form.setFieldsValue({ serviceActivityName: addEditServiceActivityModalOptions.activity.name, serviceActivityDescription: addEditServiceActivityModalOptions.activity.description, serviceActivityPrice: addEditServiceActivityModalOptions.activity.price, serviceActivityDurationMinutes: addEditServiceActivityModalOptions.activity.duration, }); setSelectedEmployeesRowKeys( addEditServiceActivityModalOptions.activity.StoreServiceActivityUsers .length === 0 ? users.map((user) => user.user_id) : addEditServiceActivityModalOptions.activity.StoreServiceActivityUsers.map( (user) => user.user_id ) ); } else { setSelectedEmployeesRowKeys(users.map((user) => user.user_id)); } }, [addEditServiceActivityModalOptions.isOpen]); return ( <> {notificationContextHolder} { form .validateFields() .then((values) => { setIsRequesting(true); myFetch({ url: "/store/services/activity", method: "POST", body: { serviceId: addEditServiceActivityModalOptions.service.service_id, name: values.serviceActivityName, description: values.serviceActivityDescription, price: values.serviceActivityPrice, duration: values.serviceActivityDurationMinutes, userIds: selectedEmployeesRowKeys.length === users.length ? [] : selectedEmployeesRowKeys, }, notificationApi: notificationApi, t: t, }) .then(() => { setIsRequesting(false); handleModalClose(); fetchServices(); }) .catch((errStatus) => { setIsRequesting(false); if (errStatus === 400) { notificationApi["error"]({ message: t("common.request.inputsInvalid.title"), description: t( "common.request.inputsInvalid.description" ), }); } }); }) .catch(() => showInputsInvalidNotification(notificationApi, t) ); }} /> ) : ( { const formServiceActivityName = form.getFieldValue( "serviceActivityName" ); const formServiceActivityDescription = form.getFieldValue( "serviceActivityDescription" ); const formServiceActivityPrice = form.getFieldValue( "serviceActivityPrice" ); const formServiceActivityDurationMinutes = form.getFieldValue( "serviceActivityDurationMinutes" ); // if the service didn't change, don't send a request if ( addEditServiceActivityModalOptions.activity.name === formServiceActivityName && addEditServiceActivityModalOptions.activity.description === formServiceActivityDescription && addEditServiceActivityModalOptions.activity.price === formServiceActivityPrice && addEditServiceActivityModalOptions.activity.duration === formServiceActivityDurationMinutes && (selectedEmployeesRowKeys.length === users.length || addEditServiceActivityModalOptions.activity .StoreServiceActivityUsers.length === selectedEmployeesRowKeys.length) ) { handleModalClose(); return; } let validateFields = []; let body = { activityId: addEditServiceActivityModalOptions.activity.activity_id, }; if ( formServiceActivityName !== addEditServiceActivityModalOptions.activity.name ) { validateFields.push("serviceActivityName"); body.name = formServiceActivityName; } // description is not required body.description = formServiceActivityDescription; if ( formServiceActivityPrice !== addEditServiceActivityModalOptions.activity.price ) { validateFields.push("serviceActivityPrice"); body.price = formServiceActivityPrice; } let formDuration = formServiceActivityDurationMinutes; if ( formDuration !== addEditServiceActivityModalOptions.activity.duration ) { validateFields.push("serviceActivityDurationMinutes"); body.duration = formDuration; } body.userIds = selectedEmployeesRowKeys; form .validateFields() .then(() => { setIsRequesting(true); myFetch({ url: "/store/services/activity/update", method: "POST", body: body, notificationApi: notificationApi, t: t, }) .then(() => { setIsRequesting(false); handleModalClose(); fetchServices(); }) .catch((errStatus) => { setIsRequesting(false); if (errStatus === 400) { notificationApi["error"]({ message: t("common.request.inputsInvalid.title"), description: t( "common.request.inputsInvalid.description" ), }); } }); }) .catch(() => showInputsInvalidNotification(notificationApi, t) ); }} /> ) } >
{t("storeServices.serviceActivityResponsible")} {t("storeServices.serviceActivityResponsibleDescription")} setSelectedEmployeesRowKeys(newSelectedRowKeys), }, loading: isRequesting, columns: getTableColumns(), dataSource: getTableItems(), size: "small", pagination: false, }} />
); } function ServiceNameFormInput({ formItemName }) { const { t } = useTranslation(); return ( ); } function ServiceActivityNameFormInput({ formItemName }) { const { t } = useTranslation(); return ( ); } function ServiceActivityDescriptionFormInput({ formItemName, setIsInputValid, }) { const { t } = useTranslation(); return ( ); } function ServiceActivityPriceFormInput({ formItemName }) { const { t } = useTranslation(); return ( ); } /* function ServiceActivityDurationHoursFormInput({ formItemName, setIsInputValid, }) { const { t } = useTranslation(); return ( ); } */ function ServiceActivityDurationMinutesFormInput({ formItemName, setIsInputValid, }) { const { t } = useTranslation(); return ( ); }