services
parent
23af00a619
commit
a3c979c5b7
|
@ -6,14 +6,27 @@
|
||||||
"save": "Speichern",
|
"save": "Speichern",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
"create": "Erstellen"
|
"create": "Erstellen",
|
||||||
|
"edit": "Bearbeiten"
|
||||||
},
|
},
|
||||||
"action": "Aktion",
|
"action": "Aktion",
|
||||||
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
|
"contactAdmin": "Bitte kontaktieren Sie einen Administrator",
|
||||||
"username": "Anzeigename",
|
"username": "Anzeigename",
|
||||||
|
"usernamePlaceholder": "Geben Sie Ihren Anzeigename ein",
|
||||||
"accountName": "Benutzername",
|
"accountName": "Benutzername",
|
||||||
|
"accountNamePlaceholder": "Geben Sie Ihren Benutzernamen ein",
|
||||||
"password": "Passwort",
|
"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": {
|
"pageNotFound": {
|
||||||
"title": "Seite nicht gefunden",
|
"title": "Seite nicht gefunden",
|
||||||
|
@ -28,17 +41,73 @@
|
||||||
"banner": "Banner",
|
"banner": "Banner",
|
||||||
"socials": "Soziale Netzwerke"
|
"socials": "Soziale Netzwerke"
|
||||||
},
|
},
|
||||||
|
"store": {
|
||||||
|
"titleSingular": "Geschäft",
|
||||||
|
"titlePlural": "Geschäfte",
|
||||||
|
"settings": "Einstellungen",
|
||||||
"employees": "Mitarbeiter",
|
"employees": "Mitarbeiter",
|
||||||
"services": "Dienstleistungen",
|
"services": "Dienstleistungen",
|
||||||
"calendar": "Kalender",
|
"calendar": "Kalender"
|
||||||
|
},
|
||||||
"support": "Unterstützung",
|
"support": "Unterstützung",
|
||||||
"feedback": "Feedback"
|
"feedback": "Feedback"
|
||||||
},
|
},
|
||||||
"employees": {
|
"employees": {
|
||||||
"pageTitle": "Mitarbeiter",
|
"pageTitle": "Mitarbeiter",
|
||||||
"addEmployee": "Mitarbeiter anlegen",
|
"addEmployee": "Mitarbeiter anlegen",
|
||||||
|
"editEmployee": "Mitarbeiter bearbeiten",
|
||||||
"modalAddEmployee": {
|
"modalAddEmployee": {
|
||||||
"checkboxPasswordChange": "Mitarbeiter auffordern, das Passwort zu ändern (empfohlen)"
|
"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",
|
"save": "Save",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
"create": "Create"
|
"create": "Create",
|
||||||
|
"edit": "Edit"
|
||||||
},
|
},
|
||||||
"action": "Action",
|
"action": "Action",
|
||||||
"contactAdmin": "Please contact an administrator",
|
"contactAdmin": "Please contact an administrator",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
|
"usernamePlaceholder": "Enter your username",
|
||||||
"accountName": "Account name",
|
"accountName": "Account name",
|
||||||
|
"accountNamePlaceholder": "Enter your account name",
|
||||||
"password": "Password",
|
"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": {
|
"pageNotFound": {
|
||||||
"title": "Page Not Found",
|
"title": "Page Not Found",
|
||||||
|
@ -28,17 +41,76 @@
|
||||||
"banner": "Banner",
|
"banner": "Banner",
|
||||||
"socials": "Socials"
|
"socials": "Socials"
|
||||||
},
|
},
|
||||||
|
"store": {
|
||||||
|
"titleSingular": "Store",
|
||||||
|
"titlePlural": "Stores",
|
||||||
|
"settings": "Settings",
|
||||||
"employees": "Employees",
|
"employees": "Employees",
|
||||||
"services": "Services",
|
"services": "Services",
|
||||||
"calendar": "Calendar",
|
"calendar": "Calendar"
|
||||||
|
},
|
||||||
"support": "Support",
|
"support": "Support",
|
||||||
"feedback": "Feedback"
|
"feedback": "Feedback"
|
||||||
},
|
},
|
||||||
"employees": {
|
"employees": {
|
||||||
"pageTitle": "Employees",
|
"pageTitle": "Employees",
|
||||||
"addEmployee": "Add employee",
|
"addEmployee": "Add employee",
|
||||||
|
"editEmployee": "Edit employee",
|
||||||
"modalAddEmployee": {
|
"modalAddEmployee": {
|
||||||
"checkboxPasswordChange": "Require employee to change password (recommended)"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
src/App.js
11
src/App.js
|
@ -10,6 +10,7 @@ import { UserProfileProvider } from "./Contexts/UserProfileContext";
|
||||||
import { UsersProvider } from "./Contexts/UsersContext";
|
import { UsersProvider } from "./Contexts/UsersContext";
|
||||||
import HeaderProvider from "./Contexts/HeaderContext";
|
import HeaderProvider from "./Contexts/HeaderContext";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import StoresProvider from "./Contexts/StoresContext";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
/*const [notificationApi, notificationContextHolder] =
|
/*const [notificationApi, notificationContextHolder] =
|
||||||
|
@ -23,15 +24,11 @@ export default function App() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userSession) return;
|
if (!userSession) return;
|
||||||
|
|
||||||
console.log("userprofile");
|
|
||||||
|
|
||||||
myFetch("/user", "GET")
|
myFetch("/user", "GET")
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log(data);
|
|
||||||
|
|
||||||
setAppUserData(data);
|
setAppUserData(data);
|
||||||
})
|
})
|
||||||
.catch((errStatus) => {
|
.catch(() => {
|
||||||
setUserSession();
|
setUserSession();
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
});
|
});
|
||||||
|
@ -78,14 +75,16 @@ export default function App() {
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: "100vh" }}>
|
<Layout style={{ minHeight: "100vh" }}>
|
||||||
<HeaderProvider>
|
<HeaderProvider>
|
||||||
<SideBarProvider _username={appUserData.username}>
|
<SideBarProvider options={{ username: appUserData.user.username }}>
|
||||||
<UserProfileProvider>
|
<UserProfileProvider>
|
||||||
<UsersProvider>
|
<UsersProvider>
|
||||||
<AppProvider>
|
<AppProvider>
|
||||||
|
<StoresProvider options={{ stores: appUserData.stores }}>
|
||||||
<DashboardLayout
|
<DashboardLayout
|
||||||
userSession={userSession}
|
userSession={userSession}
|
||||||
setUserSession={setUserSession}
|
setUserSession={setUserSession}
|
||||||
/>
|
/>
|
||||||
|
</StoresProvider>
|
||||||
</AppProvider>
|
</AppProvider>
|
||||||
</UsersProvider>
|
</UsersProvider>
|
||||||
</UserProfileProvider>
|
</UserProfileProvider>
|
||||||
|
|
|
@ -7,9 +7,10 @@ import { MySupsenseFallback } from "../MySupsenseFallback";
|
||||||
// Lazy-loaded components
|
// Lazy-loaded components
|
||||||
const Dashboard = lazy(() => import("../../Pages/Dashboard"));
|
const Dashboard = lazy(() => import("../../Pages/Dashboard"));
|
||||||
const PageNotFound = lazy(() => import("../../Pages/PageNotFound"));
|
const PageNotFound = lazy(() => import("../../Pages/PageNotFound"));
|
||||||
const Employees = lazy(() => import("../../Pages/Employees"));
|
const StoreSettings = lazy(() => import("../../Pages/Store/Settings"));
|
||||||
const Services = lazy(() => import("../../Pages/Services"));
|
const StoreEmployees = lazy(() => import("../../Pages/Store/Employees"));
|
||||||
const Calendar = lazy(() => import("../../Pages/Calendar"));
|
const StoreServices = lazy(() => import("../../Pages/Store/Services"));
|
||||||
|
const StoreCalendar = lazy(() => import("../../Pages/Store/Calendar"));
|
||||||
const Support = lazy(() => import("../../Pages/Support"));
|
const Support = lazy(() => import("../../Pages/Support"));
|
||||||
const Feedback = lazy(() => import("../../Pages/Feedback"));
|
const Feedback = lazy(() => import("../../Pages/Feedback"));
|
||||||
const UserProfile = lazy(() => import("../../Pages/UserProfile"));
|
const UserProfile = lazy(() => import("../../Pages/UserProfile"));
|
||||||
|
@ -29,28 +30,37 @@ export default function AppRoutes({ userSession, setUserSession }) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={Constants.ROUTE_PATHS.EMPLOYEES}
|
path={`${Constants.ROUTE_PATHS.STORE.SETTINGS}/:storeId`}
|
||||||
element={
|
element={
|
||||||
<MySupsenseFallback>
|
<MySupsenseFallback>
|
||||||
<Employees />
|
<StoreSettings />
|
||||||
</MySupsenseFallback>
|
</MySupsenseFallback>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={Constants.ROUTE_PATHS.SERVICES}
|
path={`${Constants.ROUTE_PATHS.STORE.EMPLOYEES}/:storeId`}
|
||||||
element={
|
element={
|
||||||
<MySupsenseFallback>
|
<MySupsenseFallback>
|
||||||
<Services />
|
<StoreEmployees />
|
||||||
</MySupsenseFallback>
|
</MySupsenseFallback>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path={Constants.ROUTE_PATHS.CALENDAR}
|
path={`${Constants.ROUTE_PATHS.STORE.SERVICES}/:storeId`}
|
||||||
element={
|
element={
|
||||||
<MySupsenseFallback>
|
<MySupsenseFallback>
|
||||||
<Calendar />
|
<StoreServices />
|
||||||
|
</MySupsenseFallback>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path={`${Constants.ROUTE_PATHS.STORE.CALENDAR}/:storeId`}
|
||||||
|
element={
|
||||||
|
<MySupsenseFallback>
|
||||||
|
<StoreCalendar />
|
||||||
</MySupsenseFallback>
|
</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,
|
onCancel,
|
||||||
onSave,
|
onSave,
|
||||||
isSaveButtonLoading,
|
isSaveButtonLoading,
|
||||||
|
isSaveButtonDisabled,
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button onClick={onCancel}>{t("common.button.close")}</Button>
|
<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")}
|
{t("common.button.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,48 +1,33 @@
|
||||||
import {
|
import {
|
||||||
AppstoreOutlined,
|
AppstoreOutlined,
|
||||||
BgColorsOutlined,
|
BgColorsOutlined,
|
||||||
BookOutlined,
|
|
||||||
CalendarOutlined,
|
CalendarOutlined,
|
||||||
ClusterOutlined,
|
ClusterOutlined,
|
||||||
ControlOutlined,
|
|
||||||
DesktopOutlined,
|
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
FileImageOutlined,
|
FileImageOutlined,
|
||||||
FileTextOutlined,
|
|
||||||
HistoryOutlined,
|
|
||||||
MessageOutlined,
|
MessageOutlined,
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
RobotOutlined,
|
|
||||||
ScanOutlined,
|
|
||||||
ScissorOutlined,
|
ScissorOutlined,
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
SnippetsOutlined,
|
ShopOutlined,
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
UsergroupAddOutlined,
|
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Badge, Divider, Menu } from "antd";
|
import { Divider, Menu } from "antd";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import {
|
import { BreakpointLgWidth, Constants } from "../../utils";
|
||||||
BreakpointLgWidth,
|
|
||||||
BrowserTabSession,
|
|
||||||
Constants,
|
|
||||||
hasOnePermission,
|
|
||||||
hasOneXYPermission,
|
|
||||||
hasPermission,
|
|
||||||
wsConnectionCustomEventName,
|
|
||||||
} from "../../utils";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useSideBarContext } from "../../Contexts/SideBarContext";
|
import { useSideBarContext } from "../../Contexts/SideBarContext";
|
||||||
import { useAppContext } from "../../Contexts/AppContext";
|
import { useAppContext } from "../../Contexts/AppContext";
|
||||||
|
import { useStoresContext } from "../../Contexts/StoresContext";
|
||||||
|
|
||||||
export function SideMenuContent({
|
export function SideMenuContent({
|
||||||
setIsSideMenuCollapsed,
|
setIsSideMenuCollapsed,
|
||||||
contentFirstRender,
|
contentFirstRender,
|
||||||
}) {
|
}) {
|
||||||
const appContext = useAppContext();
|
|
||||||
const sideBarContext = useSideBarContext();
|
const sideBarContext = useSideBarContext();
|
||||||
|
const storesContext = useStoresContext();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [selectedKeys, setSelectedKeys] = useState("/");
|
const [selectedKeys, setSelectedKeys] = useState("/");
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
@ -88,50 +73,72 @@ export function SideMenuContent({
|
||||||
|
|
||||||
items.push(groupWebsite);
|
items.push(groupWebsite);
|
||||||
|
|
||||||
// employees
|
// stores
|
||||||
|
|
||||||
items.push({
|
let stores = {
|
||||||
label: t("sideMenu.employees"),
|
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 />,
|
icon: <TeamOutlined />,
|
||||||
key: Constants.ROUTE_PATHS.EMPLOYEES,
|
key: `${Constants.ROUTE_PATHS.STORE.EMPLOYEES}/${store.store_id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// services
|
groupStore.children.push({
|
||||||
|
label: t("sideMenu.store.services"),
|
||||||
items.push({
|
|
||||||
label: t("sideMenu.services"),
|
|
||||||
icon: <ScissorOutlined />,
|
icon: <ScissorOutlined />,
|
||||||
key: Constants.ROUTE_PATHS.SERVICES,
|
key: `${Constants.ROUTE_PATHS.STORE.SERVICES}/${store.store_id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
// calendar
|
groupStore.children.push({
|
||||||
|
label: t("sideMenu.store.calendar"),
|
||||||
items.push({
|
|
||||||
label: t("sideMenu.calendar"),
|
|
||||||
icon: <CalendarOutlined />,
|
icon: <CalendarOutlined />,
|
||||||
key: Constants.ROUTE_PATHS.CALENDAR,
|
key: `${Constants.ROUTE_PATHS.STORE.CALENDAR}/${store.store_id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
stores.children.push(groupStore);
|
||||||
|
});
|
||||||
|
|
||||||
|
items.push(stores);
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSecondMenuItems = () => {
|
const getSecondMenuItems = () => {
|
||||||
let items = [];
|
let items = [];
|
||||||
|
|
||||||
// connection status, userprofile, logout
|
|
||||||
items.push(
|
items.push(
|
||||||
{
|
{
|
||||||
label: "Support",
|
label: t("sideMenu.support"),
|
||||||
icon: <QuestionCircleOutlined />,
|
icon: <QuestionCircleOutlined />,
|
||||||
key: Constants.ROUTE_PATHS.SUPPORT,
|
key: Constants.ROUTE_PATHS.SUPPORT,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Feedback",
|
label: t("sideMenu.feedback"),
|
||||||
icon: <MessageOutlined />,
|
icon: <MessageOutlined />,
|
||||||
key: Constants.ROUTE_PATHS.FEEDBACK,
|
key: Constants.ROUTE_PATHS.FEEDBACK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: ` ${sideBarContext.username}`,
|
label: sideBarContext.username,
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
key: Constants.ROUTE_PATHS.USER_PROFILE,
|
key: Constants.ROUTE_PATHS.USER_PROFILE,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,22 @@
|
||||||
import { createContext, useContext, useState } from "react";
|
import { createContext, useContext, useState } from "react";
|
||||||
import { Constants } from "../utils";
|
|
||||||
|
|
||||||
const preview = {
|
const preview = {
|
||||||
connectionBadgeStatus: "",
|
|
||||||
connectedUsers: 0,
|
|
||||||
selectedScanner: "",
|
|
||||||
username: "",
|
username: "",
|
||||||
avatar: "",
|
setUsername: () => {},
|
||||||
availableCategories: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const SideBarContext = createContext(preview);
|
const SideBarContext = createContext(preview);
|
||||||
|
|
||||||
export const useSideBarContext = () => useContext(SideBarContext);
|
export const useSideBarContext = () => useContext(SideBarContext);
|
||||||
|
|
||||||
export default function SideBarProvider({ _username, children }) {
|
export default function SideBarProvider({ children, options }) {
|
||||||
const [connectionBadgeStatus, setConnectionBadgeStatus] = useState("error");
|
const [username, setUsername] = useState(options.username);
|
||||||
const [connectedUsers, setConnectedUsers] = useState(0);
|
|
||||||
const [selectedScanner, setSelectedScanner] = useState("");
|
|
||||||
const [username, setUsername] = useState(_username); //
|
|
||||||
const [avatar, setAvatar] = useState("");
|
|
||||||
const [availableCategories, setAvailableCategories] = useState([]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SideBarContext.Provider
|
<SideBarContext.Provider
|
||||||
value={{
|
value={{
|
||||||
connectionBadgeStatus,
|
|
||||||
setConnectionBadgeStatus,
|
|
||||||
connectedUsers,
|
|
||||||
setConnectedUsers,
|
|
||||||
selectedScanner,
|
|
||||||
setSelectedScanner,
|
|
||||||
username,
|
username,
|
||||||
setUsername,
|
setUsername,
|
||||||
avatar,
|
|
||||||
setAvatar,
|
|
||||||
availableCategories,
|
|
||||||
setAvailableCategories,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{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 { LoginOutlined } from "@ant-design/icons";
|
||||||
import { Button, Form, Input, Modal, Tabs, notification } from "antd";
|
import { Button, Form, Modal, Tabs, notification } from "antd";
|
||||||
import {
|
import {
|
||||||
Constants,
|
|
||||||
EncodeStringToBase64,
|
EncodeStringToBase64,
|
||||||
myFetch,
|
myFetch,
|
||||||
myFetchContentType,
|
myFetchContentType,
|
||||||
setUserSessionToLocalStorage,
|
setUserSessionToLocalStorage,
|
||||||
} from "../../utils";
|
} from "../../utils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
MyAccountNameFormInput,
|
||||||
|
MyPasswordFormInput,
|
||||||
|
MyUsernameFormInput,
|
||||||
|
} from "../../Components/MyFormInputs";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
const [username, setUsername] = useState("");
|
const { t } = useTranslation();
|
||||||
const [accountName, setAccountName] = useState("");
|
const [form] = Form.useForm();
|
||||||
const [password, setPassword] = useState("");
|
|
||||||
const [api, contextHolder] = notification.useNotification();
|
const [api, contextHolder] = notification.useNotification();
|
||||||
|
|
||||||
const [selectedMethod, setSelectedMethod] = useState("1");
|
const [selectedMethod, setSelectedMethod] = useState("1");
|
||||||
|
const [isRequesting, setIsRequesting] = useState(false);
|
||||||
|
|
||||||
const showErrorNotification = (errStatus) => {
|
const showErrorNotification = (errStatus) => {
|
||||||
if (errStatus === 401) {
|
if (errStatus === 401) {
|
||||||
|
@ -32,28 +38,69 @@ export default function Login() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
return (
|
||||||
if (
|
<>
|
||||||
accountName.length > Constants.GLOBALS.MAX_ACCOUNT_NAME_LENGTH ||
|
{contextHolder}
|
||||||
accountName.length < Constants.GLOBALS.MIN_ACCOUNT_NAME_LENGTH ||
|
<Modal
|
||||||
password.length > Constants.GLOBALS.MAX_PASSWORD_LENGTH ||
|
open={true}
|
||||||
password.length < Constants.GLOBALS.MIN_PASSWORD_LENGTH
|
closable={false}
|
||||||
) {
|
centered
|
||||||
showErrorNotification();
|
keyboard={false}
|
||||||
return;
|
footer={null}
|
||||||
}
|
>
|
||||||
|
<Tabs
|
||||||
|
defaultActiveKey="1"
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: "1",
|
||||||
|
label: t("login.login"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "2",
|
||||||
|
label: t("login.signUp"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
centered
|
||||||
|
onChange={(activeKey) => {
|
||||||
|
setSelectedMethod(activeKey);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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 = {
|
let body = {
|
||||||
accountName: accountName.toLocaleLowerCase(),
|
accountName: values.accountName.toLocaleLowerCase(),
|
||||||
password: EncodeStringToBase64(password),
|
password: EncodeStringToBase64(values.password),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (selectedMethod === "2") {
|
if (selectedMethod === "2") {
|
||||||
body.username = username;
|
body.username = values.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
myFetch(
|
myFetch(
|
||||||
`/user/auth/${selectedMethod === "1" ? "login" : "signup"}`,
|
`/user/auth/${
|
||||||
|
selectedMethod === "1" ? "login" : "signup"
|
||||||
|
}`,
|
||||||
"POST",
|
"POST",
|
||||||
body,
|
body,
|
||||||
{},
|
{},
|
||||||
|
@ -67,114 +114,19 @@ export default function Login() {
|
||||||
setUserSessionToLocalStorage(data.XAuthorization);
|
setUserSessionToLocalStorage(data.XAuthorization);
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
})
|
})
|
||||||
.catch((errStatus) => showErrorNotification(errStatus));
|
.catch((errStatus) => {
|
||||||
};
|
showErrorNotification(errStatus);
|
||||||
|
setIsRequesting(false);
|
||||||
return (
|
});
|
||||||
<>
|
})
|
||||||
{contextHolder}
|
.catch((info) => {
|
||||||
<Modal
|
console.log("Validate Failed:", info);
|
||||||
open={true}
|
});
|
||||||
closable={false}
|
|
||||||
centered
|
|
||||||
keyboard={false}
|
|
||||||
footer={
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
icon={<LoginOutlined />}
|
|
||||||
className="login-form-button"
|
|
||||||
onClick={() => handleSubmit()}
|
|
||||||
>
|
|
||||||
{selectedMethod === "1" ? "Anmelden" : "Registrieren"}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Tabs
|
|
||||||
defaultActiveKey="1"
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
key: "1",
|
|
||||||
label: "Anmelden",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "2",
|
|
||||||
label: "Registrieren",
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
centered
|
|
||||||
onChange={(activeKey) => {
|
|
||||||
setSelectedMethod(activeKey);
|
|
||||||
}}
|
}}
|
||||||
/>
|
|
||||||
|
|
||||||
<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}!`,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
>
|
||||||
<Input
|
{selectedMethod === "1" ? t("login.login") : t("login.signUp")}
|
||||||
prefix={<UserOutlined />}
|
</Button>
|
||||||
placeholder="Anzeigename"
|
</div>
|
||||||
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>
|
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Calendar</h1>
|
<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_COLOR_PALETTE: "/website/color-palette",
|
||||||
WEBSITE_SOCIALS: "/website/socials",
|
WEBSITE_SOCIALS: "/website/socials",
|
||||||
WEBSITE_BANNER: "/website/banner",
|
WEBSITE_BANNER: "/website/banner",
|
||||||
EMPLOYEES: "/employees",
|
STORE: {
|
||||||
SERVICES: "/services",
|
SETTINGS: "/store/settings",
|
||||||
CALENDAR: "/calendar",
|
EMPLOYEES: "/store/employees",
|
||||||
|
SERVICES: "/store/services",
|
||||||
|
CALENDAR: "/store/calendar",
|
||||||
|
},
|
||||||
FEEDBACK: "/feedback",
|
FEEDBACK: "/feedback",
|
||||||
SUPPORT: "/support",
|
SUPPORT: "/support",
|
||||||
USER_PROFILE: "/user-profile",
|
USER_PROFILE: "/user-profile",
|
||||||
},
|
},
|
||||||
|
|
||||||
GLOBALS: {
|
GLOBALS: {
|
||||||
MIN_USERNAME_LENGTH: 2,
|
MIN_USERNAME_LENGTH: 3,
|
||||||
MAX_USERNAME_LENGTH: 20,
|
MAX_USERNAME_LENGTH: 20,
|
||||||
MIN_ACCOUNT_NAME_LENGTH: 2,
|
MIN_ACCOUNT_NAME_LENGTH: 3,
|
||||||
MAX_ACCOUNT_NAME_LENGTH: 20,
|
MAX_ACCOUNT_NAME_LENGTH: 20,
|
||||||
MIN_PASSWORD_LENGTH: 8,
|
MIN_PASSWORD_LENGTH: 8,
|
||||||
MAX_PASSWORD_LENGTH: 64,
|
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,
|
MAX_AVATAR_SIZE: 5 * 1024 * 1024,
|
||||||
ACCEPTED_AVATAR_FILE_TYPES: [
|
ACCEPTED_AVATAR_FILE_TYPES: [
|
||||||
"image/png",
|
"image/png",
|
||||||
|
@ -1375,8 +1391,9 @@ export function myFetch(
|
||||||
// if status is not in range 200-299
|
// if status is not in range 200-299
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (!ignoreUnauthorized && response.status === 401) {
|
if (!ignoreUnauthorized && response.status === 401) {
|
||||||
setUserSessionToLocalStorage("");
|
console.error("Unauthorized");
|
||||||
window.location.href = "/";
|
//setUserSessionToLocalStorage("");
|
||||||
|
//window.location.href = "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
throw response.status;
|
throw response.status;
|
||||||
|
|
Loading…
Reference in New Issue