services
parent
23af00a619
commit
a3c979c5b7
|
@ -6,14 +6,27 @@
|
|||
"save": "Speichern",
|
||||
"delete": "Löschen",
|
||||
"confirm": "Bestätigen",
|
||||
"create": "Erstellen"
|
||||
"create": "Erstellen",
|
||||
"edit": "Bearbeiten"
|
||||
},
|
||||
"action": "Aktion",
|
||||
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
|
||||
"username": "Anzeigename",
|
||||
"usernamePlaceholder": "Geben Sie Ihren Anzeigename ein",
|
||||
"accountName": "Benutzername",
|
||||
"accountNamePlaceholder": "Geben Sie Ihren Benutzernamen ein",
|
||||
"password": "Passwort",
|
||||
"noDataFound": "Keine Daten gefunden"
|
||||
"passwordPlaceholder": "Geben Sie Ihr Passwort ein",
|
||||
"noDataFound": "Keine Einträge gefunden",
|
||||
"inputRules": {
|
||||
"usernameRequired": "Anzeigename ist erforderlich",
|
||||
"usernameMinLength": "Anzeigename muss mindestens {{minLength}} Zeichen lang sein",
|
||||
"accountNameRequired": "Benutzername ist erforderlich",
|
||||
"accountNameMinLength": "Benutzername muss mindestens {{minLength}} Zeichen lang sein",
|
||||
"accountNameTaken": "Benutzername ist bereits vergeben",
|
||||
"passwordRequired": "Passwort ist erforderlich",
|
||||
"passwordMinLength": "Passwort muss mindestens {{minLength}} Zeichen lang sein"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "Seite nicht gefunden",
|
||||
|
@ -28,17 +41,73 @@
|
|||
"banner": "Banner",
|
||||
"socials": "Soziale Netzwerke"
|
||||
},
|
||||
"employees": "Mitarbeiter",
|
||||
"services": "Dienstleistungen",
|
||||
"calendar": "Kalender",
|
||||
"store": {
|
||||
"titleSingular": "Geschäft",
|
||||
"titlePlural": "Geschäfte",
|
||||
"settings": "Einstellungen",
|
||||
"employees": "Mitarbeiter",
|
||||
"services": "Dienstleistungen",
|
||||
"calendar": "Kalender"
|
||||
},
|
||||
"support": "Unterstützung",
|
||||
"feedback": "Feedback"
|
||||
},
|
||||
"employees": {
|
||||
"pageTitle": "Mitarbeiter",
|
||||
"addEmployee": "Mitarbeiter anlegen",
|
||||
"editEmployee": "Mitarbeiter bearbeiten",
|
||||
"modalAddEmployee": {
|
||||
"checkboxPasswordChange": "Mitarbeiter auffordern, das Passwort zu ändern (empfohlen)"
|
||||
},
|
||||
"popConfirmDeleteEmployee": {
|
||||
"title": "Mitarbeiter löschen",
|
||||
"description": "Möchten Sie den Mitarbeiter wirklich löschen?"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"login": "Anmelden",
|
||||
"signUp": "Registrieren"
|
||||
},
|
||||
"storeServices": {
|
||||
"pageTitle": "Dienstleistungen",
|
||||
"buttonAddService": "Dienstleistung hinzufügen",
|
||||
"serviceName": "Name der Dienstleistung",
|
||||
"serviceNamePlaceholder": "Geben Sie den Namen der Dienstleistung ein",
|
||||
"serviceActivityName": "Name der Tätigkeit",
|
||||
"serviceActivityNamePlaceholder": "Geben Sie den Namen der Tätigkeit ein",
|
||||
"serviceActivityDescription": "Beschreibung der Tätigkeit",
|
||||
"serviceActivityDescriptionPlaceholder": "Geben Sie die Beschreibung der Tätigkeit ein",
|
||||
"serviceActivityPrice": "Preis der Tätigkeit",
|
||||
"serviceActivityPricePlaceholder": "Geben Sie den Preis der Tätigkeit ein",
|
||||
"serviceActivityPriceUnit": "€",
|
||||
"serviceActivityDurationHours": "Dauer der Tätigkeit (Stunden)",
|
||||
"serviceActivityDurationHoursPlaceholder": "Geben Sie die Dauer der Tätigkeit in Stunden ein",
|
||||
"serviceActivityDurationHoursUnit": "Stunden",
|
||||
"serviceActivityDurationMinutes": "Dauer der Tätigkeit (Minuten)",
|
||||
"serviceActivityDurationMinutesPlaceholder": "Geben Sie die Dauer der Tätigkeit in Minuten ein",
|
||||
"serviceActivityDurationMinutesUnit": "Minuten",
|
||||
"inputRules": {
|
||||
"serviceNameRequired": "Name der Dienstleistung ist erforderlich",
|
||||
"serviceNameMinLength": "Name der Dienstleistung muss mindestens {{minLength}} Zeichen lang sein",
|
||||
"serviceActivityNameRequired": "Name der Tätigkeit ist erforderlich",
|
||||
"serviceActivityNameMinLength": "Name der Tätigkeit muss mindestens {{minLength}} Zeichen lang sein",
|
||||
"serviceActivityDescriptionRequired": "Beschreibung der Tätigkeit ist erforderlich",
|
||||
"serviceActivityDescriptionMinLength": "Beschreibung der Tätigkeit muss mindestens {{minLength}} Zeichen lang sein",
|
||||
"serviceActivityPriceRequired": "Preis der Tätigkeit ist erforderlich",
|
||||
"serviceActivityDurationHoursRequired": "Dauer der Tätigkeit (Stunden) ist erforderlich",
|
||||
"serviceActivityDurationMinutesRequired": "Dauer der Tätigkeit (Minuten) ist erforderlich"
|
||||
},
|
||||
"modalAddService": {
|
||||
"title": "Dienstleistung hinzufügen"
|
||||
},
|
||||
"modalEditService": {
|
||||
"title": "Dienstleistung bearbeiten"
|
||||
},
|
||||
"modalAddServiceActivity": {
|
||||
"title": "Tätigkeit hinzufügen"
|
||||
},
|
||||
"modalEditServiceActivity": {
|
||||
"title": "Tätigkeit bearbeiten"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,14 +6,27 @@
|
|||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm",
|
||||
"create": "Create"
|
||||
"create": "Create",
|
||||
"edit": "Edit"
|
||||
},
|
||||
"action": "Action",
|
||||
"contactAdmin": "Please contact an administrator",
|
||||
"username": "Username",
|
||||
"usernamePlaceholder": "Enter your username",
|
||||
"accountName": "Account name",
|
||||
"accountNamePlaceholder": "Enter your account name",
|
||||
"password": "Password",
|
||||
"noDataFound": "No data found"
|
||||
"passwordPlaceholder": "Enter your password",
|
||||
"noDataFound": "No data found",
|
||||
"inputRules": {
|
||||
"usernameRequired": "Please enter your username",
|
||||
"usernameMinLength": "Username must be at least {{minLength}} characters",
|
||||
"accountNameRequired": "Please enter your account name",
|
||||
"accountNameMinLength": "Account name must be at least {{minLength}} characters",
|
||||
"accountNameTaken": "Account name already exists",
|
||||
"passwordRequired": "Please enter your password",
|
||||
"passwordMinLength": "Password must be at least {{minLength}} characters"
|
||||
}
|
||||
},
|
||||
"pageNotFound": {
|
||||
"title": "Page Not Found",
|
||||
|
@ -28,17 +41,76 @@
|
|||
"banner": "Banner",
|
||||
"socials": "Socials"
|
||||
},
|
||||
"employees": "Employees",
|
||||
"services": "Services",
|
||||
"calendar": "Calendar",
|
||||
"store": {
|
||||
"titleSingular": "Store",
|
||||
"titlePlural": "Stores",
|
||||
"settings": "Settings",
|
||||
"employees": "Employees",
|
||||
"services": "Services",
|
||||
"calendar": "Calendar"
|
||||
},
|
||||
"support": "Support",
|
||||
"feedback": "Feedback"
|
||||
},
|
||||
"employees": {
|
||||
"pageTitle": "Employees",
|
||||
"addEmployee": "Add employee",
|
||||
"editEmployee": "Edit employee",
|
||||
"modalAddEmployee": {
|
||||
"checkboxPasswordChange": "Require employee to change password (recommended)"
|
||||
},
|
||||
"popConfirmDeleteEmployee": {
|
||||
"title": "Delete employee",
|
||||
"description": "Are you sure you want to delete this employee?"
|
||||
}
|
||||
},
|
||||
"login": {
|
||||
"login": "Login",
|
||||
"signUp": "Sign up"
|
||||
},
|
||||
"services": {
|
||||
"pageTitle": "Services"
|
||||
},
|
||||
"storeServices": {
|
||||
"pageTitle": "Services",
|
||||
"buttonAddService": "Add Service",
|
||||
"serviceName": "Name of the Service",
|
||||
"serviceNamePlaceholder": "Enter the name of the service",
|
||||
"serviceActivityName": "Name of the activity",
|
||||
"serviceActivityNamePlaceholder": "Enter the name of the activity",
|
||||
"serviceActivityDescription": "Description of the activity",
|
||||
"serviceActivityDescriptionPlaceholder": "Enter the description of the activity",
|
||||
"serviceActivityPrice": "Price of the activity",
|
||||
"serviceActivityPricePlaceholder": "Enter the price of the activity",
|
||||
"serviceActivityPriceUnit": "€",
|
||||
"serviceActivityDurationHours": "Duration of activity (hours)",
|
||||
"serviceActivityDurationHoursPlaceholder": "Enter the duration of the activity in hours",
|
||||
"serviceActivityDurationHoursUnit": "Hours",
|
||||
"serviceActivityDurationMinutes": "Duration of activity (minutes)",
|
||||
"serviceActivityDurationMinutesPlaceholder": "Enter the duration of the activity in minutes",
|
||||
"serviceActivityDurationMinutesUnit": "Minutes",
|
||||
"inputRules": {
|
||||
"serviceNameRequired": "Service name is required",
|
||||
"serviceNameMinLength": "Service name must be at least {{minLength}} characters long",
|
||||
"serviceActivityNameRequired": "Activity name is required",
|
||||
"serviceActivityNameMinLength": "Activity name must be at least {{minLength}} characters long",
|
||||
"serviceActivityDescriptionRequired": "Activity description is required",
|
||||
"serviceActivityDescriptionMinLength": "Activity description must be at least {{minLength}} characters long",
|
||||
"serviceActivityPriceRequired": "Activity price is required",
|
||||
"serviceActivityDurationHoursRequired": "Activity duration (hours) is required",
|
||||
"serviceActivityDurationMinutesRequired": "Activity duration (minutes) is required"
|
||||
},
|
||||
"modalAddService": {
|
||||
"title": "Add Service"
|
||||
},
|
||||
"modalEditService": {
|
||||
"title": "Edit Service"
|
||||
},
|
||||
"modalAddServiceActivity": {
|
||||
"title": "Add activity"
|
||||
},
|
||||
"modalEditServiceActivity": {
|
||||
"title": "Edit activity"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
19
src/App.js
19
src/App.js
|
@ -10,6 +10,7 @@ import { UserProfileProvider } from "./Contexts/UserProfileContext";
|
|||
import { UsersProvider } from "./Contexts/UsersContext";
|
||||
import HeaderProvider from "./Contexts/HeaderContext";
|
||||
import { useEffect, useState } from "react";
|
||||
import StoresProvider from "./Contexts/StoresContext";
|
||||
|
||||
export default function App() {
|
||||
/*const [notificationApi, notificationContextHolder] =
|
||||
|
@ -23,15 +24,11 @@ export default function App() {
|
|||
useEffect(() => {
|
||||
if (!userSession) return;
|
||||
|
||||
console.log("userprofile");
|
||||
|
||||
myFetch("/user", "GET")
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
|
||||
setAppUserData(data);
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
.catch(() => {
|
||||
setUserSession();
|
||||
window.location.href = "/";
|
||||
});
|
||||
|
@ -78,14 +75,16 @@ export default function App() {
|
|||
return (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<HeaderProvider>
|
||||
<SideBarProvider _username={appUserData.username}>
|
||||
<SideBarProvider options={{ username: appUserData.user.username }}>
|
||||
<UserProfileProvider>
|
||||
<UsersProvider>
|
||||
<AppProvider>
|
||||
<DashboardLayout
|
||||
userSession={userSession}
|
||||
setUserSession={setUserSession}
|
||||
/>
|
||||
<StoresProvider options={{ stores: appUserData.stores }}>
|
||||
<DashboardLayout
|
||||
userSession={userSession}
|
||||
setUserSession={setUserSession}
|
||||
/>
|
||||
</StoresProvider>
|
||||
</AppProvider>
|
||||
</UsersProvider>
|
||||
</UserProfileProvider>
|
||||
|
|
|
@ -7,9 +7,10 @@ import { MySupsenseFallback } from "../MySupsenseFallback";
|
|||
// Lazy-loaded components
|
||||
const Dashboard = lazy(() => import("../../Pages/Dashboard"));
|
||||
const PageNotFound = lazy(() => import("../../Pages/PageNotFound"));
|
||||
const Employees = lazy(() => import("../../Pages/Employees"));
|
||||
const Services = lazy(() => import("../../Pages/Services"));
|
||||
const Calendar = lazy(() => import("../../Pages/Calendar"));
|
||||
const StoreSettings = lazy(() => import("../../Pages/Store/Settings"));
|
||||
const StoreEmployees = lazy(() => import("../../Pages/Store/Employees"));
|
||||
const StoreServices = lazy(() => import("../../Pages/Store/Services"));
|
||||
const StoreCalendar = lazy(() => import("../../Pages/Store/Calendar"));
|
||||
const Support = lazy(() => import("../../Pages/Support"));
|
||||
const Feedback = lazy(() => import("../../Pages/Feedback"));
|
||||
const UserProfile = lazy(() => import("../../Pages/UserProfile"));
|
||||
|
@ -29,28 +30,37 @@ export default function AppRoutes({ userSession, setUserSession }) {
|
|||
/>
|
||||
|
||||
<Route
|
||||
path={Constants.ROUTE_PATHS.EMPLOYEES}
|
||||
path={`${Constants.ROUTE_PATHS.STORE.SETTINGS}/:storeId`}
|
||||
element={
|
||||
<MySupsenseFallback>
|
||||
<Employees />
|
||||
<StoreSettings />
|
||||
</MySupsenseFallback>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Constants.ROUTE_PATHS.SERVICES}
|
||||
path={`${Constants.ROUTE_PATHS.STORE.EMPLOYEES}/:storeId`}
|
||||
element={
|
||||
<MySupsenseFallback>
|
||||
<Services />
|
||||
<StoreEmployees />
|
||||
</MySupsenseFallback>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={Constants.ROUTE_PATHS.CALENDAR}
|
||||
path={`${Constants.ROUTE_PATHS.STORE.SERVICES}/:storeId`}
|
||||
element={
|
||||
<MySupsenseFallback>
|
||||
<Calendar />
|
||||
<StoreServices />
|
||||
</MySupsenseFallback>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
path={`${Constants.ROUTE_PATHS.STORE.CALENDAR}/:storeId`}
|
||||
element={
|
||||
<MySupsenseFallback>
|
||||
<StoreCalendar />
|
||||
</MySupsenseFallback>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,227 @@
|
|||
import { Form, Input, InputNumber } from "antd";
|
||||
import { Constants, myFetch } from "../../utils";
|
||||
import { useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export function MyUsernameFormInput({ propsFormItem, propsInput }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
propsFormItem={propsFormItem}
|
||||
propsInput={propsInput}
|
||||
formItemName="username"
|
||||
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH}
|
||||
label={t("common.username")}
|
||||
ruleMessageValueRequired={t("common.inputRules.usernameRequired")}
|
||||
ruleMessageValueMinLengthRequired={t(
|
||||
"common.inputRules.usernameMinLength",
|
||||
{
|
||||
minLength: Constants.GLOBALS.MIN_USERNAME_LENGTH,
|
||||
}
|
||||
)}
|
||||
inputPlaceholder={t("common.usernamePlaceholder")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyAccountNameFormInput({
|
||||
propsFormItem,
|
||||
propsInput,
|
||||
disableAccountNameCheck,
|
||||
hasFeedback,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyAvailableCheckFormInput
|
||||
propsFormItem={propsFormItem}
|
||||
propsInput={propsInput}
|
||||
hasFeedback={hasFeedback}
|
||||
formItemName="accountName"
|
||||
minLength={Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_ACCOUNT_NAME_LENGTH}
|
||||
label={t("common.accountName")}
|
||||
ruleMessageValueRequired={t("common.inputRules.accountNameRequired")}
|
||||
ruleMessageValueMinLengthRequired={t(
|
||||
"common.inputRules.accountNameMinLength",
|
||||
{
|
||||
minLength: Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH,
|
||||
}
|
||||
)}
|
||||
ruleMessageValueNotAvailable={t("common.inputRules.accountNameTaken")}
|
||||
inputPlaceholder={t("common.accountNamePlaceholder")}
|
||||
inputType="text"
|
||||
disableAvailableCheck={disableAccountNameCheck}
|
||||
fetchUrl="/user/auth/check/accountname"
|
||||
fetchParameter="accountName"
|
||||
fetchDelay={Constants.DELAY_ACCOUNT_NAME_CHECK}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyPasswordFormInput({ propsFormItem, propsInput }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
propsFormItem={propsFormItem}
|
||||
propsInput={propsInput}
|
||||
formItemName="password"
|
||||
minLength={Constants.GLOBALS.MIN_PASSWORD_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_PASSWORD_LENGTH}
|
||||
label={t("common.password")}
|
||||
ruleMessageValueRequired={t("common.inputRules.passwordRequired")}
|
||||
ruleMessageValueMinLengthRequired={t(
|
||||
"common.inputRules.passwordMinLength",
|
||||
{
|
||||
minLength: Constants.GLOBALS.MIN_PASSWORD_LENGTH,
|
||||
}
|
||||
)}
|
||||
inputPlaceholder={t("common.passwordPlaceholder")}
|
||||
inputType="password"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyAvailableCheckFormInput({
|
||||
propsFormItem,
|
||||
propsInput,
|
||||
formItemName,
|
||||
minLength,
|
||||
maxLength,
|
||||
label,
|
||||
ruleMessageValueRequired,
|
||||
ruleMessageValueMinLengthRequired,
|
||||
ruleMessageValueNotAvailable,
|
||||
inputPlaceholder,
|
||||
inputType,
|
||||
disableAvailableCheck = false,
|
||||
fetchUrl,
|
||||
fetchParameter,
|
||||
fetchDelay,
|
||||
hasFeedback,
|
||||
}) {
|
||||
const delayTimeout = useRef();
|
||||
|
||||
const isValid = (value) => {
|
||||
return value.length >= minLength && value.length <= maxLength;
|
||||
};
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
propsFormItem={{
|
||||
...propsFormItem,
|
||||
hasFeedback: hasFeedback,
|
||||
}}
|
||||
propsInput={propsInput}
|
||||
formItemName={formItemName}
|
||||
minLength={minLength}
|
||||
maxLength={maxLength}
|
||||
label={label}
|
||||
ruleMessageValueRequired={ruleMessageValueRequired}
|
||||
ruleMessageValueMinLengthRequired={ruleMessageValueMinLengthRequired}
|
||||
inputPlaceholder={inputPlaceholder}
|
||||
inputType={inputType}
|
||||
formItemRules={[
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
if (!value || !isValid(value)) {
|
||||
return Promise.reject("");
|
||||
}
|
||||
|
||||
if (disableAvailableCheck) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (delayTimeout.current) {
|
||||
clearTimeout(delayTimeout.current);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
delayTimeout.current = setTimeout(() => {
|
||||
let body = {};
|
||||
|
||||
body[fetchParameter] = value; // like accountName: value
|
||||
|
||||
myFetch(fetchUrl, "POST", body)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
reject(ruleMessageValueNotAvailable);
|
||||
});
|
||||
}, fetchDelay);
|
||||
});
|
||||
},
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function MyFormInput({
|
||||
propsFormItem,
|
||||
propsInput,
|
||||
formItemName,
|
||||
formItemRules,
|
||||
minLength,
|
||||
maxLength,
|
||||
label,
|
||||
ruleMessageValueRequired,
|
||||
ruleMessageValueMinLengthRequired,
|
||||
inputPlaceholder,
|
||||
inputType,
|
||||
}) {
|
||||
const commonProps = {
|
||||
...propsInput,
|
||||
placeholder: inputPlaceholder,
|
||||
};
|
||||
|
||||
const myFormItemRules = [
|
||||
{
|
||||
required: true,
|
||||
message: ruleMessageValueRequired,
|
||||
},
|
||||
];
|
||||
|
||||
if (formItemRules) {
|
||||
myFormItemRules.push(...formItemRules);
|
||||
}
|
||||
|
||||
if (inputType === "number") {
|
||||
commonProps.min = minLength;
|
||||
commonProps.max = maxLength;
|
||||
} else {
|
||||
commonProps.minLength = minLength;
|
||||
commonProps.maxLength = maxLength;
|
||||
|
||||
myFormItemRules.push({
|
||||
min: minLength,
|
||||
message: ruleMessageValueMinLengthRequired,
|
||||
});
|
||||
}
|
||||
|
||||
const inputComponents = {
|
||||
textarea: (
|
||||
<Input.TextArea {...commonProps} autoSize={{ minRows: 2, maxRows: 6 }} />
|
||||
),
|
||||
number: <InputNumber {...commonProps} />,
|
||||
password: <Input.Password {...commonProps} />,
|
||||
default: <Input {...commonProps} />,
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
{...propsFormItem}
|
||||
label={label}
|
||||
name={formItemName}
|
||||
required
|
||||
rules={myFormItemRules}
|
||||
>
|
||||
{inputComponents[inputType] || inputComponents.default}
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
|
@ -108,13 +108,19 @@ export function MyModalCloseSaveButtonFooter({
|
|||
onCancel,
|
||||
onSave,
|
||||
isSaveButtonLoading,
|
||||
isSaveButtonDisabled,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
||||
<Button onClick={onSave} type="primary" loading={isSaveButtonLoading}>
|
||||
<Button
|
||||
onClick={onSave}
|
||||
type="primary"
|
||||
disabled={isSaveButtonDisabled}
|
||||
loading={isSaveButtonLoading}
|
||||
>
|
||||
{t("common.button.save")}
|
||||
</Button>
|
||||
</>
|
||||
|
|
|
@ -1,48 +1,33 @@
|
|||
import {
|
||||
AppstoreOutlined,
|
||||
BgColorsOutlined,
|
||||
BookOutlined,
|
||||
CalendarOutlined,
|
||||
ClusterOutlined,
|
||||
ControlOutlined,
|
||||
DesktopOutlined,
|
||||
EditOutlined,
|
||||
FileImageOutlined,
|
||||
FileTextOutlined,
|
||||
HistoryOutlined,
|
||||
MessageOutlined,
|
||||
QuestionCircleOutlined,
|
||||
RobotOutlined,
|
||||
ScanOutlined,
|
||||
ScissorOutlined,
|
||||
SettingOutlined,
|
||||
SnippetsOutlined,
|
||||
ShopOutlined,
|
||||
TeamOutlined,
|
||||
UserOutlined,
|
||||
UsergroupAddOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Badge, Divider, Menu } from "antd";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Divider, Menu } from "antd";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import {
|
||||
BreakpointLgWidth,
|
||||
BrowserTabSession,
|
||||
Constants,
|
||||
hasOnePermission,
|
||||
hasOneXYPermission,
|
||||
hasPermission,
|
||||
wsConnectionCustomEventName,
|
||||
} from "../../utils";
|
||||
import { BreakpointLgWidth, Constants } from "../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSideBarContext } from "../../Contexts/SideBarContext";
|
||||
import { useAppContext } from "../../Contexts/AppContext";
|
||||
import { useStoresContext } from "../../Contexts/StoresContext";
|
||||
|
||||
export function SideMenuContent({
|
||||
setIsSideMenuCollapsed,
|
||||
contentFirstRender,
|
||||
}) {
|
||||
const appContext = useAppContext();
|
||||
const sideBarContext = useSideBarContext();
|
||||
const storesContext = useStoresContext();
|
||||
const location = useLocation();
|
||||
const [selectedKeys, setSelectedKeys] = useState("/");
|
||||
const { t } = useTranslation();
|
||||
|
@ -88,29 +73,52 @@ export function SideMenuContent({
|
|||
|
||||
items.push(groupWebsite);
|
||||
|
||||
// employees
|
||||
// stores
|
||||
|
||||
items.push({
|
||||
label: t("sideMenu.employees"),
|
||||
icon: <TeamOutlined />,
|
||||
key: Constants.ROUTE_PATHS.EMPLOYEES,
|
||||
let stores = {
|
||||
label:
|
||||
storesContext.stores.length > 1
|
||||
? t("sideMenu.store.titlePlural")
|
||||
: t("sideMenu.store.titleSingular"),
|
||||
children: [],
|
||||
type: "group",
|
||||
};
|
||||
|
||||
storesContext.stores.forEach((store) => {
|
||||
let groupStore = {
|
||||
label: store.name,
|
||||
icon: <ShopOutlined />,
|
||||
children: [],
|
||||
};
|
||||
|
||||
groupStore.children.push({
|
||||
label: t("sideMenu.store.settings"),
|
||||
icon: <SettingOutlined />,
|
||||
key: `${Constants.ROUTE_PATHS.STORE.SETTINGS}/${store.store_id}`,
|
||||
});
|
||||
|
||||
groupStore.children.push({
|
||||
label: t("sideMenu.store.employees"),
|
||||
icon: <TeamOutlined />,
|
||||
key: `${Constants.ROUTE_PATHS.STORE.EMPLOYEES}/${store.store_id}`,
|
||||
});
|
||||
|
||||
groupStore.children.push({
|
||||
label: t("sideMenu.store.services"),
|
||||
icon: <ScissorOutlined />,
|
||||
key: `${Constants.ROUTE_PATHS.STORE.SERVICES}/${store.store_id}`,
|
||||
});
|
||||
|
||||
groupStore.children.push({
|
||||
label: t("sideMenu.store.calendar"),
|
||||
icon: <CalendarOutlined />,
|
||||
key: `${Constants.ROUTE_PATHS.STORE.CALENDAR}/${store.store_id}`,
|
||||
});
|
||||
|
||||
stores.children.push(groupStore);
|
||||
});
|
||||
|
||||
// services
|
||||
|
||||
items.push({
|
||||
label: t("sideMenu.services"),
|
||||
icon: <ScissorOutlined />,
|
||||
key: Constants.ROUTE_PATHS.SERVICES,
|
||||
});
|
||||
|
||||
// calendar
|
||||
|
||||
items.push({
|
||||
label: t("sideMenu.calendar"),
|
||||
icon: <CalendarOutlined />,
|
||||
key: Constants.ROUTE_PATHS.CALENDAR,
|
||||
});
|
||||
items.push(stores);
|
||||
|
||||
return items;
|
||||
};
|
||||
|
@ -118,20 +126,19 @@ export function SideMenuContent({
|
|||
const getSecondMenuItems = () => {
|
||||
let items = [];
|
||||
|
||||
// connection status, userprofile, logout
|
||||
items.push(
|
||||
{
|
||||
label: "Support",
|
||||
label: t("sideMenu.support"),
|
||||
icon: <QuestionCircleOutlined />,
|
||||
key: Constants.ROUTE_PATHS.SUPPORT,
|
||||
},
|
||||
{
|
||||
label: "Feedback",
|
||||
label: t("sideMenu.feedback"),
|
||||
icon: <MessageOutlined />,
|
||||
key: Constants.ROUTE_PATHS.FEEDBACK,
|
||||
},
|
||||
{
|
||||
label: ` ${sideBarContext.username}`,
|
||||
label: sideBarContext.username,
|
||||
icon: <UserOutlined />,
|
||||
key: Constants.ROUTE_PATHS.USER_PROFILE,
|
||||
}
|
||||
|
|
|
@ -1,42 +1,22 @@
|
|||
import { createContext, useContext, useState } from "react";
|
||||
import { Constants } from "../utils";
|
||||
|
||||
const preview = {
|
||||
connectionBadgeStatus: "",
|
||||
connectedUsers: 0,
|
||||
selectedScanner: "",
|
||||
username: "",
|
||||
avatar: "",
|
||||
availableCategories: [],
|
||||
setUsername: () => {},
|
||||
};
|
||||
|
||||
const SideBarContext = createContext(preview);
|
||||
|
||||
export const useSideBarContext = () => useContext(SideBarContext);
|
||||
|
||||
export default function SideBarProvider({ _username, children }) {
|
||||
const [connectionBadgeStatus, setConnectionBadgeStatus] = useState("error");
|
||||
const [connectedUsers, setConnectedUsers] = useState(0);
|
||||
const [selectedScanner, setSelectedScanner] = useState("");
|
||||
const [username, setUsername] = useState(_username); //
|
||||
const [avatar, setAvatar] = useState("");
|
||||
const [availableCategories, setAvailableCategories] = useState([]);
|
||||
export default function SideBarProvider({ children, options }) {
|
||||
const [username, setUsername] = useState(options.username);
|
||||
|
||||
return (
|
||||
<SideBarContext.Provider
|
||||
value={{
|
||||
connectionBadgeStatus,
|
||||
setConnectionBadgeStatus,
|
||||
connectedUsers,
|
||||
setConnectedUsers,
|
||||
selectedScanner,
|
||||
setSelectedScanner,
|
||||
username,
|
||||
setUsername,
|
||||
avatar,
|
||||
setAvatar,
|
||||
availableCategories,
|
||||
setAvailableCategories,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { createContext, useContext, useState } from "react";
|
||||
|
||||
const preview = {
|
||||
stores: [],
|
||||
setStores: () => {},
|
||||
};
|
||||
|
||||
const StoresContext = createContext(preview);
|
||||
|
||||
export const useStoresContext = () => useContext(StoresContext);
|
||||
|
||||
export default function StoresProvider({ children, options }) {
|
||||
const [stores, setStores] = useState(options.stores);
|
||||
|
||||
return (
|
||||
<StoresContext.Provider value={{ stores, setStores }}>
|
||||
{children}
|
||||
</StoresContext.Provider>
|
||||
);
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
import { LockOutlined, PlusOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { Button, Checkbox, Col, Form, Grid, Input, Row, Table } from "antd";
|
||||
import MyModal, {
|
||||
MyModalCloseCreateButtonFooter,
|
||||
} from "../../Components/MyModal";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Constants, myFetch, EncodeStringToBase64 } from "../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import MyTable from "../../Components/MyTable";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
export default function Employees() {
|
||||
const { t } = useTranslation();
|
||||
const screenBreakpoint = useBreakpoint();
|
||||
|
||||
const [employees, setEmployees] = useState([]);
|
||||
const [isAddEmployeeModalOpen, setIsAddEmployeeModalOpen] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
const [accountName, setAccountName] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
|
||||
const getTableColumns = () => {
|
||||
return [
|
||||
{
|
||||
title: t("common.accountName"),
|
||||
dataIndex: "account_name",
|
||||
key: "account_name",
|
||||
},
|
||||
{
|
||||
title: t("common.username"),
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
},
|
||||
{
|
||||
title: t("common.action"),
|
||||
dataIndex: "action",
|
||||
key: "actions",
|
||||
render: () => <a>{t("common.button.delete")}</a>,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getTableItems = () => {
|
||||
return employees.map((employee) => {
|
||||
return {
|
||||
key: employee.account_name,
|
||||
account_name: employee.account_name,
|
||||
username: employee.username,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddEmployeeModalClose = () => {
|
||||
setIsAddEmployeeModalOpen(false);
|
||||
setUsername("");
|
||||
setAccountName("");
|
||||
setPassword("");
|
||||
};
|
||||
|
||||
const fetchEmployees = () => {
|
||||
myFetch("/users", "GET")
|
||||
.then((data) => {
|
||||
setIsRequesting(false);
|
||||
setEmployees(data.employees);
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsRequesting(true);
|
||||
|
||||
fetchEmployees();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: screenBreakpoint.xs ? "column" : "row",
|
||||
}}
|
||||
>
|
||||
<h1>
|
||||
{t("employees.pageTitle")}
|
||||
{employees.length > 0 && ` (${employees.length})`}
|
||||
</h1>
|
||||
<Button
|
||||
type="primary"
|
||||
block={screenBreakpoint.xs}
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => setIsAddEmployeeModalOpen(true)}
|
||||
>
|
||||
{t("employees.addEmployee")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<MyTable
|
||||
props={{
|
||||
loading: isRequesting,
|
||||
columns: getTableColumns(),
|
||||
dataSource: getTableItems(),
|
||||
}}
|
||||
/>
|
||||
|
||||
<MyModal
|
||||
title={t("employees.addEmployee")}
|
||||
isOpen={isAddEmployeeModalOpen}
|
||||
onCancel={handleAddEmployeeModalClose}
|
||||
footer={
|
||||
<MyModalCloseCreateButtonFooter
|
||||
onCancel={handleAddEmployeeModalClose}
|
||||
isCreateButtonLoading={isRequesting}
|
||||
onCreate={() => {
|
||||
setIsRequesting(true);
|
||||
|
||||
myFetch("/users", "POST", {
|
||||
username: username,
|
||||
accountName: accountName,
|
||||
password: EncodeStringToBase64(password),
|
||||
})
|
||||
.then(() => {
|
||||
setIsRequesting(false);
|
||||
handleAddEmployeeModalClose();
|
||||
|
||||
fetchEmployees();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
|
||||
setIsRequesting(false);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Form>
|
||||
<Form.Item
|
||||
name="username"
|
||||
required
|
||||
rules={[
|
||||
{ required: true, message: "Please enter your username!" },
|
||||
{
|
||||
min: Constants.GLOBALS.MIN_USERNAME_LENGTH,
|
||||
message: `Please enter a username length of at least ${Constants.GLOBALS.MIN_USERNAME_LENGTH}!`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder={t("common.username")}
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="accountName"
|
||||
required
|
||||
rules={[
|
||||
{ required: true, message: "Please enter an account name!" },
|
||||
{
|
||||
min: Constants.GLOBALS.MIN_USERNAME_LENGTH,
|
||||
message: `Please enter an account name length of at least ${Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH}!`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder={t("common.accountName")}
|
||||
value={accountName}
|
||||
onChange={(e) => setAccountName(e.target.value)}
|
||||
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
required
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please enter your Password!",
|
||||
},
|
||||
{
|
||||
min: Constants.GLOBALS.MIN_PASSWORD_LENGTH,
|
||||
message: `Please enter a password length of at least ${Constants.GLOBALS.MIN_PASSWORD_LENGTH}!`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
placeholder={t("common.password")}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
minLength={Constants.GLOBALS.MIN_PASSWORD_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_PASSWORD_LENGTH}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Checkbox defaultChecked>
|
||||
{t("employees.modalAddEmployee.checkboxPasswordChange")}
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,21 +1,27 @@
|
|||
import { LockOutlined, LoginOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { Button, Form, Input, Modal, Tabs, notification } from "antd";
|
||||
import { LoginOutlined } from "@ant-design/icons";
|
||||
import { Button, Form, Modal, Tabs, notification } from "antd";
|
||||
import {
|
||||
Constants,
|
||||
EncodeStringToBase64,
|
||||
myFetch,
|
||||
myFetchContentType,
|
||||
setUserSessionToLocalStorage,
|
||||
} from "../../utils";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
MyAccountNameFormInput,
|
||||
MyPasswordFormInput,
|
||||
MyUsernameFormInput,
|
||||
} from "../../Components/MyFormInputs";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Login() {
|
||||
const [username, setUsername] = useState("");
|
||||
const [accountName, setAccountName] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [api, contextHolder] = notification.useNotification();
|
||||
|
||||
const [selectedMethod, setSelectedMethod] = useState("1");
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
|
||||
const showErrorNotification = (errStatus) => {
|
||||
if (errStatus === 401) {
|
||||
|
@ -32,44 +38,6 @@ export default function Login() {
|
|||
});
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (
|
||||
accountName.length > Constants.GLOBALS.MAX_ACCOUNT_NAME_LENGTH ||
|
||||
accountName.length < Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH ||
|
||||
password.length > Constants.GLOBALS.MAX_PASSWORD_LENGTH ||
|
||||
password.length < Constants.GLOBALS.MIN_PASSWORD_LENGTH
|
||||
) {
|
||||
showErrorNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
let body = {
|
||||
accountName: accountName.toLocaleLowerCase(),
|
||||
password: EncodeStringToBase64(password),
|
||||
};
|
||||
|
||||
if (selectedMethod === "2") {
|
||||
body.username = username;
|
||||
}
|
||||
|
||||
myFetch(
|
||||
`/user/auth/${selectedMethod === "1" ? "login" : "signup"}`,
|
||||
"POST",
|
||||
body,
|
||||
{},
|
||||
myFetchContentType.JSON,
|
||||
"",
|
||||
true
|
||||
)
|
||||
.then((data) => {
|
||||
console.log(data.XAuthorization);
|
||||
|
||||
setUserSessionToLocalStorage(data.XAuthorization);
|
||||
window.location.href = "/";
|
||||
})
|
||||
.catch((errStatus) => showErrorNotification(errStatus));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextHolder}
|
||||
|
@ -78,28 +46,18 @@ export default function Login() {
|
|||
closable={false}
|
||||
centered
|
||||
keyboard={false}
|
||||
footer={
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
icon={<LoginOutlined />}
|
||||
className="login-form-button"
|
||||
onClick={() => handleSubmit()}
|
||||
>
|
||||
{selectedMethod === "1" ? "Anmelden" : "Registrieren"}
|
||||
</Button>
|
||||
}
|
||||
footer={null}
|
||||
>
|
||||
<Tabs
|
||||
defaultActiveKey="1"
|
||||
items={[
|
||||
{
|
||||
key: "1",
|
||||
label: "Anmelden",
|
||||
label: t("login.login"),
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
label: "Registrieren",
|
||||
label: t("login.signUp"),
|
||||
},
|
||||
]}
|
||||
centered
|
||||
|
@ -108,73 +66,67 @@ export default function Login() {
|
|||
}}
|
||||
/>
|
||||
|
||||
<Form>
|
||||
{selectedMethod === "2" && (
|
||||
<Form.Item
|
||||
name="username"
|
||||
required
|
||||
rules={[
|
||||
{ required: true, message: "Please enter your username!" },
|
||||
{
|
||||
min: Constants.GLOBALS.MIN_USERNAME_LENGTH,
|
||||
message: `Please enter a username length of at least ${Constants.GLOBALS.MIN_USERNAME_LENGTH}!`,
|
||||
},
|
||||
]}
|
||||
<Form form={form} layout="vertical" requiredMark={false}>
|
||||
{selectedMethod === "2" && <MyUsernameFormInput />}
|
||||
|
||||
<MyAccountNameFormInput
|
||||
disableAccountNameCheck={selectedMethod === "1"}
|
||||
hasFeedback={selectedMethod === "2"}
|
||||
/>
|
||||
|
||||
<MyPasswordFormInput />
|
||||
|
||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
icon={<LoginOutlined />}
|
||||
loading={isRequesting}
|
||||
onClick={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
setIsRequesting(true);
|
||||
|
||||
let body = {
|
||||
accountName: values.accountName.toLocaleLowerCase(),
|
||||
password: EncodeStringToBase64(values.password),
|
||||
};
|
||||
|
||||
if (selectedMethod === "2") {
|
||||
body.username = values.username;
|
||||
}
|
||||
|
||||
myFetch(
|
||||
`/user/auth/${
|
||||
selectedMethod === "1" ? "login" : "signup"
|
||||
}`,
|
||||
"POST",
|
||||
body,
|
||||
{},
|
||||
myFetchContentType.JSON,
|
||||
"",
|
||||
true
|
||||
)
|
||||
.then((data) => {
|
||||
console.log(data.XAuthorization);
|
||||
|
||||
setUserSessionToLocalStorage(data.XAuthorization);
|
||||
window.location.href = "/";
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
showErrorNotification(errStatus);
|
||||
setIsRequesting(false);
|
||||
});
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log("Validate Failed:", info);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder="Anzeigename"
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
minLength={Constants.GLOBALS.MIN_USERNAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_USERNAME_LENGTH}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
hasFeedback
|
||||
name="accountName"
|
||||
validateStatus="validating"
|
||||
required
|
||||
rules={[
|
||||
{ required: true, message: "Please enter your accountName!" },
|
||||
{
|
||||
min: Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH,
|
||||
message: `Please enter a accountName length of at least ${Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH}!`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
placeholder="Benutzername"
|
||||
onChange={(e) => setAccountName(e.target.value)}
|
||||
minLength={Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_ACCOUNT_NAME_LENGTH}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
required
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: "Please enter your Password!",
|
||||
},
|
||||
{
|
||||
min: Constants.GLOBALS.MIN_PASSWORD_LENGTH,
|
||||
message: `Please enter a password length of at least ${Constants.GLOBALS.MIN_PASSWORD_LENGTH}!`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
placeholder="Passwort"
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
minLength={Constants.GLOBALS.MIN_PASSWORD_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_PASSWORD_LENGTH}
|
||||
/>
|
||||
</Form.Item>
|
||||
{selectedMethod === "1" ? t("login.login") : t("login.signUp")}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
</>
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export default function Services() {
|
||||
return (
|
||||
<>
|
||||
<h1>Services</h1>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
export default function Calendar() {
|
||||
export default function StoreCalendar() {
|
||||
return (
|
||||
<>
|
||||
<h1>Calendar</h1>
|
|
@ -0,0 +1,315 @@
|
|||
import { PlusOutlined } from "@ant-design/icons";
|
||||
import { Button, Checkbox, Form, Grid, Popconfirm, Space } from "antd";
|
||||
import MyModal, {
|
||||
MyModalCloseCreateButtonFooter,
|
||||
MyModalCloseSaveButtonFooter,
|
||||
} from "../../../Components/MyModal";
|
||||
import { useEffect, useState } from "react";
|
||||
import { myFetch, EncodeStringToBase64 } from "../../../utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import MyTable from "../../../Components/MyTable";
|
||||
import {
|
||||
MyAccountNameFormInput,
|
||||
MyPasswordFormInput,
|
||||
MyUsernameFormInput,
|
||||
} from "../../../Components/MyFormInputs";
|
||||
import { Link, useParams } from "react-router-dom";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
export default function StoreEmployees() {
|
||||
const { t } = useTranslation();
|
||||
const screenBreakpoint = useBreakpoint();
|
||||
const { storeId } = useParams();
|
||||
|
||||
const [employees, setEmployees] = useState([]);
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
|
||||
const [addEditEmployeeModalOptions, setAddEditEmployeeModalOptions] =
|
||||
useState({
|
||||
mode: "add",
|
||||
isOpen: false,
|
||||
selectedEmployee: null,
|
||||
});
|
||||
|
||||
const getTableColumns = () => {
|
||||
return [
|
||||
{
|
||||
title: t("common.accountName"),
|
||||
dataIndex: "account_name",
|
||||
key: "account_name",
|
||||
},
|
||||
{
|
||||
title: t("common.username"),
|
||||
dataIndex: "username",
|
||||
key: "username",
|
||||
},
|
||||
{
|
||||
title: t("common.action"),
|
||||
dataIndex: "action",
|
||||
key: "actions",
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Link
|
||||
to="#"
|
||||
onClick={() => {
|
||||
setAddEditEmployeeModalOptions({
|
||||
...addEditEmployeeModalOptions,
|
||||
mode: "edit",
|
||||
isOpen: true,
|
||||
selectedEmployee: record,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("common.button.edit")}
|
||||
</Link>
|
||||
<Popconfirm
|
||||
title={t("employees.popConfirmDeleteEmployee.title")}
|
||||
description={t("employees.popConfirmDeleteEmployee.description")}
|
||||
okText={t("common.button.delete")}
|
||||
onConfirm={() => {
|
||||
setIsRequesting(true);
|
||||
|
||||
myFetch("/users", "DELETE", {
|
||||
userId: record.key,
|
||||
})
|
||||
.then(() => fetchEmployees())
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
|
||||
setIsRequesting(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Link to="#">{t("common.button.delete")}</Link>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getTableItems = () => {
|
||||
return employees.map((employee) => {
|
||||
return {
|
||||
key: employee.user_id,
|
||||
account_name: employee.account_name,
|
||||
username: employee.username,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const fetchEmployees = () => {
|
||||
setIsRequesting(true);
|
||||
|
||||
myFetch(`/users/${storeId}`, "GET")
|
||||
.then((data) => {
|
||||
setIsRequesting(false);
|
||||
setEmployees(data.employees);
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => fetchEmployees(), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: screenBreakpoint.xs ? "column" : "row",
|
||||
}}
|
||||
>
|
||||
<h1>
|
||||
{t("employees.pageTitle")}
|
||||
{employees.length > 0 && ` (${employees.length})`}
|
||||
</h1>
|
||||
|
||||
<ModalAddEditEmployee
|
||||
modalOptions={addEditEmployeeModalOptions}
|
||||
setModalOptions={setAddEditEmployeeModalOptions}
|
||||
fetchEmployees={fetchEmployees}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<MyTable
|
||||
props={{
|
||||
loading: isRequesting,
|
||||
columns: getTableColumns(),
|
||||
dataSource: getTableItems(),
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ModalAddEditEmployee({
|
||||
modalOptions,
|
||||
setModalOptions,
|
||||
fetchEmployees,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { storeId } = useParams();
|
||||
const screenBreakpoint = useBreakpoint();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
|
||||
const handleModalClose = () => {
|
||||
setModalOptions({
|
||||
...modalOptions,
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!modalOptions.isOpen) return;
|
||||
|
||||
if (modalOptions.mode === "edit") {
|
||||
form.setFieldsValue({
|
||||
username: modalOptions.selectedEmployee.username,
|
||||
accountName: modalOptions.selectedEmployee.account_name,
|
||||
});
|
||||
}
|
||||
}, [modalOptions.isOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
block={screenBreakpoint.xs}
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() =>
|
||||
setModalOptions({ ...modalOptions, mode: "add", isOpen: true })
|
||||
}
|
||||
>
|
||||
{t("employees.addEmployee")}
|
||||
</Button>
|
||||
|
||||
<MyModal
|
||||
title={
|
||||
modalOptions.mode === "add"
|
||||
? t("employees.addEmployee")
|
||||
: t("employees.editEmployee")
|
||||
}
|
||||
isOpen={modalOptions.isOpen}
|
||||
onCancel={handleModalClose}
|
||||
footer={
|
||||
modalOptions.mode === "add" ? (
|
||||
<MyModalCloseCreateButtonFooter
|
||||
onCancel={handleModalClose}
|
||||
isCreateButtonLoading={isRequesting}
|
||||
onCreate={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
setIsRequesting(true);
|
||||
|
||||
myFetch("/users", "POST", {
|
||||
storeId: storeId,
|
||||
username: values.username,
|
||||
accountName: values.accountName,
|
||||
password: EncodeStringToBase64(values.password),
|
||||
})
|
||||
.then(() => {
|
||||
setIsRequesting(false);
|
||||
handleModalClose();
|
||||
|
||||
fetchEmployees();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
|
||||
setIsRequesting(false);
|
||||
});
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log("Validate Failed:", info);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MyModalCloseSaveButtonFooter
|
||||
onCancel={handleModalClose}
|
||||
isSaveButtonLoading={isRequesting}
|
||||
onSave={() => {
|
||||
// only validate if something has changed
|
||||
let formUsername = form.getFieldValue("username");
|
||||
let formAccountName = form.getFieldValue("accountName");
|
||||
|
||||
if (
|
||||
formUsername === modalOptions.selectedEmployee.username &&
|
||||
formAccountName === modalOptions.selectedEmployee.account_name
|
||||
) {
|
||||
handleModalClose();
|
||||
return;
|
||||
}
|
||||
|
||||
let validateFields = [];
|
||||
let body = {
|
||||
userId: modalOptions.selectedEmployee.key,
|
||||
};
|
||||
|
||||
if (formUsername !== modalOptions.selectedEmployee.username) {
|
||||
validateFields.push("username");
|
||||
body.username = formUsername;
|
||||
}
|
||||
|
||||
if (
|
||||
formAccountName !== modalOptions.selectedEmployee.account_name
|
||||
) {
|
||||
validateFields.push("accountName");
|
||||
body.accountName = formAccountName;
|
||||
}
|
||||
|
||||
form
|
||||
.validateFields(validateFields)
|
||||
.then(() => {
|
||||
setIsRequesting(true);
|
||||
|
||||
myFetch("/users/update", "POST", body)
|
||||
.then(() => {
|
||||
setIsRequesting(false);
|
||||
handleModalClose();
|
||||
|
||||
fetchEmployees();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
|
||||
setIsRequesting(false);
|
||||
});
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log("Validate Failed:", info);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical" requiredMark={false}>
|
||||
<MyUsernameFormInput />
|
||||
|
||||
<MyAccountNameFormInput hasFeedback />
|
||||
|
||||
{modalOptions.mode === "add" && (
|
||||
<>
|
||||
<MyPasswordFormInput />
|
||||
<Form.Item>
|
||||
<Checkbox defaultChecked disabled>
|
||||
{t("employees.modalAddEmployee.checkboxPasswordChange")}
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,787 @@
|
|||
import {
|
||||
ArrowDownOutlined,
|
||||
ArrowUpOutlined,
|
||||
DeleteOutlined,
|
||||
EditOutlined,
|
||||
PlusOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Card,
|
||||
Collapse,
|
||||
Empty,
|
||||
Form,
|
||||
Grid,
|
||||
Skeleton,
|
||||
Space,
|
||||
Tooltip,
|
||||
} from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Constants, myFetch } from "../../../utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import MyModal, {
|
||||
MyModalCloseCreateButtonFooter,
|
||||
MyModalCloseSaveButtonFooter,
|
||||
} from "../../../Components/MyModal";
|
||||
import { MyFormInput } from "../../../Components/MyFormInputs";
|
||||
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
export default function StoreServices() {
|
||||
const { t } = useTranslation();
|
||||
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(`/store/services/${storeId}`, "GET")
|
||||
.then((data) => {
|
||||
setIsRequestingServices(false);
|
||||
setServicesData(data);
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => fetchServices(), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: screenBreakpoint.xs ? "column" : "row",
|
||||
}}
|
||||
>
|
||||
<h1>{t("storeServices.pageTitle")}</h1>
|
||||
|
||||
<ModalAddEditService
|
||||
storeId={storeId}
|
||||
fetchServices={fetchServices}
|
||||
addEditServiceModalOptions={addEditServiceModalOptions}
|
||||
setAddEditServiceModalOptions={setAddEditServiceModalOptions}
|
||||
/>
|
||||
</div>
|
||||
<Space direction="vertical" style={{ width: "100%" }}>
|
||||
{isRequestingServices ? (
|
||||
<>
|
||||
<Skeleton.Input block active size="large" />
|
||||
<Skeleton.Input block active size="large" />
|
||||
<Skeleton.Input block active size="large" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{servicesData.services.map((service) => (
|
||||
<Service
|
||||
key={service.service_id}
|
||||
service={service}
|
||||
users={servicesData.users}
|
||||
storeId={storeId}
|
||||
setAddEditServiceActivityModalOptions={
|
||||
setAddEditServiceActivityModalOptions
|
||||
}
|
||||
setAddEditServiceModalOptions={setAddEditServiceModalOptions}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
|
||||
<ModalAddEditServiceActivity
|
||||
fetchServices={fetchServices}
|
||||
addEditServiceActivityModalOptions={addEditServiceActivityModalOptions}
|
||||
setAddEditServiceActivityModalOptions={
|
||||
setAddEditServiceActivityModalOptions
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Service({
|
||||
service,
|
||||
users,
|
||||
storeId,
|
||||
setAddEditServiceActivityModalOptions,
|
||||
setAddEditServiceModalOptions,
|
||||
}) {
|
||||
const [isRequestingActivities, setIsRequestingActivities] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [serviceActivities, setServiceActivities] = useState([]);
|
||||
|
||||
const fetchServiceActivities = () => {
|
||||
if (!isOpen) return;
|
||||
|
||||
setIsRequestingActivities(true);
|
||||
|
||||
myFetch(
|
||||
`/store/services/activities/${storeId}/${service.service_id}`,
|
||||
"GET"
|
||||
)
|
||||
.then((data) => {
|
||||
setIsRequestingActivities(false);
|
||||
setServiceActivities(data.activities);
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => fetchServiceActivities(), [isOpen]);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
key={service.service_id}
|
||||
onChange={(e) => setIsOpen(e.length !== 0)}
|
||||
items={[
|
||||
{
|
||||
key: "1",
|
||||
label: (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<span>{service.name}</span>
|
||||
|
||||
<Space>
|
||||
<ArrowUpOutlined
|
||||
disabled
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<ArrowDownOutlined
|
||||
disabled
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
<PlusOutlined
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setAddEditServiceActivityModalOptions({
|
||||
mode: "add",
|
||||
isOpen: true,
|
||||
service: service,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<EditOutlined
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setAddEditServiceModalOptions({
|
||||
mode: "edit",
|
||||
isOpen: true,
|
||||
service: service,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<DeleteOutlined onClick={(e) => e.stopPropagation()} />
|
||||
</Space>
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<Space
|
||||
key={`space-${service.service_id}`}
|
||||
direction="vertical"
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{isRequestingActivities ? (
|
||||
<Skeleton active>
|
||||
<Card title="Test">
|
||||
<p>Desc</p>
|
||||
<p>Preis: €</p>
|
||||
<p>Dauer: Minuten</p>
|
||||
</Card>
|
||||
</Skeleton>
|
||||
) : (
|
||||
<>
|
||||
{serviceActivities.length === 0 ? (
|
||||
<Empty />
|
||||
) : (
|
||||
serviceActivities.map((activity) => (
|
||||
<Card
|
||||
key={activity.activity_id}
|
||||
title={activity.name}
|
||||
extra={
|
||||
<Space>
|
||||
<Avatar.Group maxCount={1} size="small">
|
||||
{users.map((user) => (
|
||||
<Tooltip
|
||||
key={user.user_id}
|
||||
title={user.username}
|
||||
>
|
||||
<Avatar
|
||||
size="small"
|
||||
style={{ backgroundColor: "#87d068" }}
|
||||
>
|
||||
{user.username.charAt(0)}
|
||||
</Avatar>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Avatar.Group>
|
||||
|
||||
<ArrowUpOutlined disabled />
|
||||
<ArrowDownOutlined disabled />
|
||||
<EditOutlined
|
||||
onClick={() => {
|
||||
setAddEditServiceActivityModalOptions({
|
||||
mode: "edit",
|
||||
isOpen: true,
|
||||
activity: activity,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<DeleteOutlined />
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<p>{activity.description}</p>
|
||||
<p>Preis: {activity.price} €</p>
|
||||
<p>Dauer: {activity.duration} Minuten</p>
|
||||
</Card>
|
||||
))
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// this modal is used to create and edit services
|
||||
function ModalAddEditService({
|
||||
storeId,
|
||||
fetchServices,
|
||||
addEditServiceModalOptions,
|
||||
setAddEditServiceModalOptions,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const screenBreakpoint = useBreakpoint();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
|
||||
const handleModalClose = () => {
|
||||
setAddEditServiceModalOptions({
|
||||
...addEditServiceModalOptions,
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!addEditServiceModalOptions.isOpen) return;
|
||||
|
||||
if (addEditServiceModalOptions.mode === "edit") {
|
||||
form.setFieldsValue({
|
||||
serviceName: addEditServiceModalOptions.service.name,
|
||||
});
|
||||
}
|
||||
}, [addEditServiceModalOptions.isOpen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
block={screenBreakpoint.xs}
|
||||
onClick={() =>
|
||||
setAddEditServiceModalOptions({
|
||||
...addEditServiceModalOptions,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
{t("storeServices.buttonAddService")}
|
||||
</Button>
|
||||
|
||||
<MyModal
|
||||
title={
|
||||
addEditServiceModalOptions.mode === "add"
|
||||
? t("storeServices.modalAddService.title")
|
||||
: t("storeServices.modalEditService.title")
|
||||
}
|
||||
isOpen={addEditServiceModalOptions.isOpen}
|
||||
onCancel={handleModalClose}
|
||||
footer={
|
||||
addEditServiceModalOptions.mode === "add" ? (
|
||||
<MyModalCloseCreateButtonFooter
|
||||
onCancel={handleModalClose}
|
||||
isCreateButtonLoading={isRequesting}
|
||||
onCreate={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then((values) => {
|
||||
setIsRequesting(true);
|
||||
|
||||
myFetch("/store/services", "POST", {
|
||||
storeId: storeId,
|
||||
name: values.serviceName,
|
||||
})
|
||||
.then(() => {
|
||||
setIsRequesting(false);
|
||||
handleModalClose();
|
||||
|
||||
fetchServices();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log("Validate Failed:", info);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MyModalCloseSaveButtonFooter
|
||||
onCancel={handleModalClose}
|
||||
isSaveButtonLoading={isRequesting}
|
||||
onSave={() => {
|
||||
// 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("/store/services/update", "POST", {
|
||||
serviceId: addEditServiceModalOptions.service.service_id,
|
||||
name: formServiceName,
|
||||
})
|
||||
.then(() => {
|
||||
setIsRequesting(false);
|
||||
handleModalClose();
|
||||
|
||||
fetchServices();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
})
|
||||
.catch((info) => {
|
||||
console.log("Validate Failed:", info);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical" requiredMark={false}>
|
||||
<ServiceNameFormInput formItemName="serviceName" />
|
||||
</Form>
|
||||
</MyModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let defaultModalAddServiceFormValidOptions = {
|
||||
serviceActivityName: false,
|
||||
serviceActivityDescription: false,
|
||||
serviceActivityPrice: false,
|
||||
serviceActivityDurationHours: false,
|
||||
serviceActivityDurationMinutes: false,
|
||||
};
|
||||
|
||||
function ModalAddEditServiceActivity({
|
||||
fetchServices,
|
||||
addEditServiceActivityModalOptions,
|
||||
setAddEditServiceActivityModalOptions,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isFormValid, setIsFormValid] = useState(
|
||||
defaultModalAddServiceFormValidOptions
|
||||
);
|
||||
const [form] = Form.useForm();
|
||||
const [isRequesting, setIsRequesting] = useState(false);
|
||||
|
||||
const handleModalClose = () => {
|
||||
setAddEditServiceActivityModalOptions({
|
||||
...addEditServiceActivityModalOptions,
|
||||
isOpen: false,
|
||||
});
|
||||
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
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,
|
||||
serviceActivityDescription:
|
||||
addEditServiceActivityModalOptions.activity.description,
|
||||
serviceActivityPrice: addEditServiceActivityModalOptions.activity.price,
|
||||
serviceActivityDurationHours: Math.floor(
|
||||
addEditServiceActivityModalOptions.activity.duration / 60
|
||||
),
|
||||
serviceActivityDurationMinutes:
|
||||
addEditServiceActivityModalOptions.activity.duration % 60,
|
||||
});
|
||||
}
|
||||
}, [addEditServiceActivityModalOptions.isOpen]);
|
||||
|
||||
return (
|
||||
<MyModal
|
||||
title={
|
||||
addEditServiceActivityModalOptions.mode === "add"
|
||||
? t("storeServices.modalAddServiceActivity.title")
|
||||
: t("storeServices.modalEditServiceActivity.title")
|
||||
}
|
||||
isOpen={addEditServiceActivityModalOptions.isOpen}
|
||||
onCancel={handleModalClose}
|
||||
footer={
|
||||
addEditServiceActivityModalOptions.mode === "add" ? (
|
||||
<MyModalCloseCreateButtonFooter
|
||||
onCancel={handleModalClose}
|
||||
isCreateButtonDisabled={
|
||||
!isFormValid.serviceActivityName ||
|
||||
!isFormValid.serviceActivityDescription ||
|
||||
!isFormValid.serviceActivityPrice ||
|
||||
!isFormValid.serviceActivityDurationHours ||
|
||||
!isFormValid.serviceActivityDurationMinutes
|
||||
}
|
||||
isCreateButtonLoading={isRequesting}
|
||||
onCreate={() => {
|
||||
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();
|
||||
|
||||
fetchServices();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<MyModalCloseSaveButtonFooter
|
||||
onCancel={handleModalClose}
|
||||
isSaveButtonDisabled={
|
||||
!isFormValid.serviceActivityName ||
|
||||
!isFormValid.serviceActivityDescription ||
|
||||
!isFormValid.serviceActivityPrice ||
|
||||
!isFormValid.serviceActivityDurationHours ||
|
||||
!isFormValid.serviceActivityDurationMinutes
|
||||
}
|
||||
isSaveButtonLoading={isRequesting}
|
||||
onSave={() => {
|
||||
// if the service name didn't change, don't send a request
|
||||
if (
|
||||
addEditServiceActivityModalOptions.activity.name ===
|
||||
form.getFieldValue("serviceActivityName") &&
|
||||
addEditServiceActivityModalOptions.activity.description ===
|
||||
form.getFieldValue("serviceActivityDescription") &&
|
||||
addEditServiceActivityModalOptions.activity.price ===
|
||||
form.getFieldValue("serviceActivityPrice") &&
|
||||
addEditServiceActivityModalOptions.activity.duration ===
|
||||
form.getFieldValue("serviceActivityDurationHours") * 60 +
|
||||
form.getFieldValue("serviceActivityDurationMinutes")
|
||||
) {
|
||||
handleModalClose();
|
||||
console.log("same");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsRequesting(true);
|
||||
|
||||
myFetch("/store/services/activity/update", "POST", {
|
||||
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();
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
<Form form={form} layout="vertical" requiredMark={false}>
|
||||
<ServiceActivityNameFormInput
|
||||
formItemName="serviceActivityName"
|
||||
setIsInputValid={(isValid) =>
|
||||
setIsFormValid({
|
||||
...isFormValid,
|
||||
serviceActivityName: isValid,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<ServiceActivityDescriptionFormInput
|
||||
formItemName="serviceActivityDescription"
|
||||
setIsInputValid={(isValid) =>
|
||||
setIsFormValid({
|
||||
...isFormValid,
|
||||
serviceActivityDescription: isValid,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<ServiceActivityPriceFormInput
|
||||
formItemName="serviceActivityPrice"
|
||||
setIsInputValid={(isValid) =>
|
||||
setIsFormValid({
|
||||
...isFormValid,
|
||||
serviceActivityPrice: isValid,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<ServiceActivityDurationHoursFormInput
|
||||
formItemName="serviceActivityDurationHours"
|
||||
setIsInputValid={(isValid) =>
|
||||
setIsFormValid({
|
||||
...isFormValid,
|
||||
serviceActivityDurationHours: isValid,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
<ServiceActivityDurationMinutesFormInput
|
||||
formItemName="serviceActivityDurationMinutes"
|
||||
setIsInputValid={(isValid) =>
|
||||
setIsFormValid({
|
||||
...isFormValid,
|
||||
serviceActivityDurationMinutes: isValid,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Form>
|
||||
</MyModal>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceNameFormInput({ formItemName }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
formItemName={formItemName}
|
||||
minLength={Constants.GLOBALS.MIN_STORE_SERVICE_NAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_STORE_SERVICE_NAME_LENGTH}
|
||||
label={t("storeServices.serviceName")}
|
||||
ruleMessageValueRequired={t(
|
||||
"storeServices.inputRules.serviceNameRequired"
|
||||
)}
|
||||
ruleMessageValueMinLengthRequired={t(
|
||||
"storeServices.inputRules.serviceNameMinLength",
|
||||
{
|
||||
minLength: Constants.GLOBALS.MIN_STORE_SERVICE_NAME_LENGTH,
|
||||
}
|
||||
)}
|
||||
inputPlaceholder={t("storeServices.serviceNamePlaceholder")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceActivityNameFormInput({ formItemName, setIsInputValid }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
formItemName={formItemName}
|
||||
setIsInputValid={setIsInputValid}
|
||||
minLength={Constants.GLOBALS.MIN_STORE_SERVICE_ACTIVITY_NAME_LENGTH}
|
||||
maxLength={Constants.GLOBALS.MAX_STORE_SERVICE_ACTIVITY_NAME_LENGTH}
|
||||
label={t("storeServices.serviceActivityName")}
|
||||
ruleMessageValueRequired={t(
|
||||
"storeServices.inputRules.serviceActivityNameRequired"
|
||||
)}
|
||||
ruleMessageValueMinLengthRequired={t(
|
||||
"storeServices.inputRules.serviceActivityNameMinLength",
|
||||
{
|
||||
minLength: Constants.GLOBALS.MIN_STORE_SERVICE_ACTIVITY_NAME_LENGTH,
|
||||
}
|
||||
)}
|
||||
inputPlaceholder={t("storeServices.serviceActivityNamePlaceholder")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceActivityDescriptionFormInput({
|
||||
formItemName,
|
||||
setIsInputValid,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
inputType="textarea"
|
||||
formItemName={formItemName}
|
||||
setIsInputValid={setIsInputValid}
|
||||
minLength={
|
||||
Constants.GLOBALS.MIN_STORE_SERVICE_ACTIVITY_DESCRIPTION_LENGTH
|
||||
}
|
||||
maxLength={
|
||||
Constants.GLOBALS.MAX_STORE_SERVICE_ACTIVITY_DESCRIPTION_LENGTH
|
||||
}
|
||||
label={t("storeServices.serviceActivityDescription")}
|
||||
ruleMessageValueRequired={t(
|
||||
"storeServices.inputRules.serviceActivityDescriptionRequired"
|
||||
)}
|
||||
ruleMessageValueMinLengthRequired={t(
|
||||
"storeServices.inputRules.serviceActivityDescriptionMinLength",
|
||||
{
|
||||
minLength:
|
||||
Constants.GLOBALS.MIN_STORE_SERVICE_ACTIVITY_DESCRIPTION_LENGTH,
|
||||
}
|
||||
)}
|
||||
inputPlaceholder={t(
|
||||
"storeServices.serviceActivityDescriptionPlaceholder"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceActivityPriceFormInput({ formItemName, setIsInputValid }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
propsInput={{
|
||||
addonAfter: t("storeServices.serviceActivityPriceUnit"),
|
||||
}}
|
||||
inputType="number"
|
||||
formItemName={formItemName}
|
||||
setIsInputValid={setIsInputValid}
|
||||
minLength={Constants.GLOBALS.MIN_STORE_SERVICE_ACTIVITY_PRICE}
|
||||
maxLength={Constants.GLOBALS.MAX_STORE_SERVICE_ACTIVITY_PRICE}
|
||||
label={t("storeServices.serviceActivityPrice")}
|
||||
ruleMessageValueRequired={t(
|
||||
"storeServices.inputRules.serviceActivityPriceRequired"
|
||||
)}
|
||||
inputPlaceholder={t("storeServices.serviceActivityPricePlaceholder")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceActivityDurationHoursFormInput({
|
||||
formItemName,
|
||||
setIsInputValid,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
propsInput={{
|
||||
addonAfter: t("storeServices.serviceActivityDurationHoursUnit"),
|
||||
}}
|
||||
inputType="number"
|
||||
formItemName={formItemName}
|
||||
setIsInputValid={setIsInputValid}
|
||||
minLength={Constants.GLOBALS.MIN_STORE_SERVICE_ACTIVITY_DURATION_HOURS}
|
||||
maxLength={Constants.GLOBALS.MAX_STORE_SERVICE_ACTIVITY_DURATION_HOURS}
|
||||
label={t("storeServices.serviceActivityDurationHours")}
|
||||
ruleMessageValueRequired={t(
|
||||
"storeServices.inputRules.serviceActivityDurationHoursRequired"
|
||||
)}
|
||||
inputPlaceholder={t(
|
||||
"storeServices.serviceActivityDurationHoursPlaceholder"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ServiceActivityDurationMinutesFormInput({
|
||||
formItemName,
|
||||
setIsInputValid,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<MyFormInput
|
||||
propsInput={{
|
||||
addonAfter: t("storeServices.serviceActivityDurationMinutesUnit"),
|
||||
}}
|
||||
inputType="number"
|
||||
formItemName={formItemName}
|
||||
setIsInputValid={setIsInputValid}
|
||||
minLength={Constants.GLOBALS.MIN_STORE_SERVICE_ACTIVITY_DURATION_MINUTES}
|
||||
maxLength={Constants.GLOBALS.MAX_STORE_SERVICE_ACTIVITY_DURATION_MINUTES}
|
||||
label={t("storeServices.serviceActivityDurationMinutes")}
|
||||
ruleMessageValueRequired={t(
|
||||
"storeServices.inputRules.serviceActivityDurationMinutesRequired"
|
||||
)}
|
||||
inputPlaceholder={t(
|
||||
"storeServices.serviceActivityDurationMinutesPlaceholder"
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { myFetch } from "../../../utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
export default function StoreSettings() {
|
||||
const { storeId } = useParams();
|
||||
|
||||
const [storeData, setStoreData] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
myFetch(`/store/${storeId}`, "GET")
|
||||
.then((data) => {
|
||||
setStoreData(data.store);
|
||||
})
|
||||
.catch((errStatus) => {
|
||||
console.log(errStatus);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Store</h1>
|
||||
|
||||
<p>{storeData.name}</p>
|
||||
|
||||
<p>Name ändern</p>
|
||||
<p>Firmensitz angeben - Addresse</p>
|
||||
<p>Telefonnummer, Email (oder in Website einstellungen)</p>
|
||||
</>
|
||||
);
|
||||
}
|
31
src/utils.js
31
src/utils.js
|
@ -44,22 +44,38 @@ export const Constants = {
|
|||
WEBSITE_COLOR_PALETTE: "/website/color-palette",
|
||||
WEBSITE_SOCIALS: "/website/socials",
|
||||
WEBSITE_BANNER: "/website/banner",
|
||||
EMPLOYEES: "/employees",
|
||||
SERVICES: "/services",
|
||||
CALENDAR: "/calendar",
|
||||
STORE: {
|
||||
SETTINGS: "/store/settings",
|
||||
EMPLOYEES: "/store/employees",
|
||||
SERVICES: "/store/services",
|
||||
CALENDAR: "/store/calendar",
|
||||
},
|
||||
FEEDBACK: "/feedback",
|
||||
SUPPORT: "/support",
|
||||
USER_PROFILE: "/user-profile",
|
||||
},
|
||||
|
||||
GLOBALS: {
|
||||
MIN_USERNAME_LENGTH: 2,
|
||||
MIN_USERNAME_LENGTH: 3,
|
||||
MAX_USERNAME_LENGTH: 20,
|
||||
MIN_ACCOUNT_NAME_LENGTH: 2,
|
||||
MIN_ACCOUNT_NAME_LENGTH: 3,
|
||||
MAX_ACCOUNT_NAME_LENGTH: 20,
|
||||
MIN_PASSWORD_LENGTH: 8,
|
||||
MAX_PASSWORD_LENGTH: 64,
|
||||
MIN_STORE_SERVICE_NAME_LENGTH: 3,
|
||||
MAX_STORE_SERVICE_NAME_LENGTH: 32,
|
||||
MIN_STORE_SERVICE_ACTIVITY_NAME_LENGTH: 3,
|
||||
MAX_STORE_SERVICE_ACTIVITY_NAME_LENGTH: 32,
|
||||
MIN_STORE_SERVICE_ACTIVITY_DESCRIPTION_LENGTH: 3,
|
||||
MAX_STORE_SERVICE_ACTIVITY_DESCRIPTION_LENGTH: 1024,
|
||||
MIN_STORE_SERVICE_ACTIVITY_PRICE: 0,
|
||||
MAX_STORE_SERVICE_ACTIVITY_PRICE: 10000000,
|
||||
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,
|
||||
},
|
||||
DELAY_ACCOUNT_NAME_CHECK: 250,
|
||||
MAX_AVATAR_SIZE: 5 * 1024 * 1024,
|
||||
ACCEPTED_AVATAR_FILE_TYPES: [
|
||||
"image/png",
|
||||
|
@ -1375,8 +1391,9 @@ export function myFetch(
|
|||
// if status is not in range 200-299
|
||||
if (!response.ok) {
|
||||
if (!ignoreUnauthorized && response.status === 401) {
|
||||
setUserSessionToLocalStorage("");
|
||||
window.location.href = "/";
|
||||
console.error("Unauthorized");
|
||||
//setUserSessionToLocalStorage("");
|
||||
//window.location.href = "/";
|
||||
}
|
||||
|
||||
throw response.status;
|
||||
|
|
Loading…
Reference in New Issue