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}
}
block={screenBreakpoint.xs}
onClick={() =>
setAddEditServiceModalOptions({
...addEditServiceModalOptions,
isOpen: true,
})
}
>
{t("storeServices.buttonAddService")}
{
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)
);
}}
/>
)
}
>
>
);
}
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 (
);
}