add promocode datafetching

promocode datagrid displays data
This commit is contained in:
nflnkr 2024-03-03 10:57:12 +03:00
parent 3485eca257
commit 0a2413a077
10 changed files with 614 additions and 495 deletions

@ -41,6 +41,7 @@
"reconnecting-eventsource": "^1.6.2",
"start-server-and-test": "^2.0.0",
"styled-components": "^5.3.5",
"swr": "^2.2.5",
"typescript": "^4.8.2",
"use-debounce": "^9.0.4",
"web-vitals": "^2.1.4",

@ -1,48 +0,0 @@
import { makeRequest } from "@frontend/kitui";
import { GetPromocodeListBody, PromocodeList, CreatePromocodeBody, Promocode } from "@root/model/promocodes";
import { parseAxiosError } from "@root/utils/parse-error";
const baseUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode";
export const getPromocodeList = async (
body: GetPromocodeListBody
): Promise<[PromocodeList | null, string?]> => {
try {
const promocodeListResponse = await makeRequest<
GetPromocodeListBody,
PromocodeList
>({
url: baseUrl + "/getList",
body,
useToken: false,
});
return [promocodeListResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Ошибка при получении списка промокодов. ${error}`];
}
};
export const createPromocode = async (
body: CreatePromocodeBody
): Promise<[Promocode | null, string?]> => {
try {
const createPromocodeResponse = await makeRequest<
CreatePromocodeBody,
Promocode
>({
url: baseUrl + "/create",
body,
useToken: false,
});
return [createPromocodeResponse];
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
return [null, `Ошибка создания промокода. ${error}`];
}
};

@ -0,0 +1,67 @@
import { makeRequest } from "@frontend/kitui";
import { CreatePromocodeBody, GetPromocodeListBody, Promocode, PromocodeList } from "@root/model/promocodes";
import { parseAxiosError } from "@root/utils/parse-error";
const baseUrl = process.env.REACT_APP_DOMAIN + "/codeword/promocode";
const getPromocodeList = async (
body: GetPromocodeListBody
): Promise<Promocode[]> => {
try {
const promocodeListResponse = await makeRequest<
GetPromocodeListBody,
PromocodeList
>({
url: baseUrl + "/getList",
method: "POST",
body,
useToken: false,
});
return promocodeListResponse.items;
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
throw new Error(`Ошибка при получении списка промокодов. ${error}`);
}
};
const createPromocode = async (
body: CreatePromocodeBody
): Promise<Promocode> => {
try {
const createPromocodeResponse = await makeRequest<
CreatePromocodeBody,
Promocode
>({
url: baseUrl + "/create",
method: "POST",
body,
useToken: false,
});
return createPromocodeResponse;
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
throw new Error(`Ошибка создания промокода. ${error}`);
}
};
const deletePromocode = async (id: string): Promise<void> => {
try {
await makeRequest<never, never>({
url: `${baseUrl}/${id}`,
method: "DELETE",
useToken: false,
});
} catch (nativeError) {
const [error] = parseAxiosError(nativeError);
throw new Error(`Ошибка удаления промокода. ${error}`);
}
};
export const promocodeApi = {
getPromocodeList,
createPromocode,
deletePromocode,
};

69
src/api/promocode/swr.ts Normal file

@ -0,0 +1,69 @@
import { CreatePromocodeBody, Promocode } from "@root/model/promocodes";
import { enqueueSnackbar } from "notistack";
import useSwr, { mutate } from "swr";
import { promocodeApi } from "./requests";
export function usePromocodes() {
return useSwr(
"promocodes",
() => promocodeApi.getPromocodeList({
limit: 100,
filter: {
active: true,
},
page: 0,
}),
{
onError(err) {
console.log("Error fetching promocodes", err);
enqueueSnackbar(err.message, { variant: "error" });
},
focusThrottleInterval: 60e3,
}
);
}
export async function createPromocode(body: CreatePromocodeBody) {
try {
await mutate<Promocode[] | undefined, Promocode>(
"promocodes",
promocodeApi.createPromocode(body),
{
populateCache(result, currentData) {
if (!currentData) return;
return [...currentData, result];
},
revalidate: false,
}
);
} catch (error) {
console.log("Error creating promocode", error);
if (error instanceof Error) enqueueSnackbar(error.message, { variant: "error" });
}
}
export async function deletePromocode(id: string) {
try {
await mutate<Promocode[] | undefined, void>(
"promocodes",
promocodeApi.deletePromocode(id),
{
optimisticData(currentData, displayedData) {
if (!displayedData) return;
return displayedData.filter((item) => item.id !== id);
},
rollbackOnError: true,
populateCache(result, currentData) {
if (!currentData) return;
return currentData.filter((item) => item.id !== id);
},
}
);
} catch (error) {
console.log("Error deleting promocode", error);
if (error instanceof Error) enqueueSnackbar(error.message, { variant: "error" });
}
}

@ -28,6 +28,7 @@ export type GetPromocodeListBody = {
};
export type Promocode = CreatePromocodeBody & {
id: string;
outdated: boolean;
offLimit: boolean;
delete: boolean;

@ -1,365 +1,368 @@
import { useEffect, useState } from "react";
import moment from "moment";
import { Formik, Field, Form } from "formik";
import {
Typography,
TextField,
Button,
RadioGroup,
Radio,
FormControlLabel,
Select,
MenuItem,
Button,
FormControlLabel,
MenuItem,
Radio,
RadioGroup,
Select,
TextField,
Typography,
} from "@mui/material";
import { DesktopDatePicker } from "@mui/x-date-pickers/DesktopDatePicker";
import { Field, Form, Formik } from "formik";
import moment from "moment";
import { useEffect, useState } from "react";
import { requestPrivileges } from "@root/services/privilegies.service";
import { usePrivilegeStore } from "@root/stores/privilegesStore";
import { createPromocode } from "@root/api/promocode";
import { SERVICE_LIST } from "@root/model/privilege";
import theme from "@root/theme";
import type { ChangeEvent } from "react";
import type { TextFieldProps } from "@mui/material";
import { createPromocode } from "@root/api/promocode/swr";
import type { ChangeEvent } from "react";
type BonusType = "discount" | "privilege";
type FormValues = {
codeword: string;
description: string;
greetings: string;
dueTo: number;
activationCount: number;
privilegeId: string;
amount: number;
layer: 1 | 2;
factor: number;
target: string;
threshold: number;
codeword: string;
description: string;
greetings: string;
dueTo: number;
activationCount: number;
privilegeId: string;
amount: number;
layer: 1 | 2;
factor: number;
target: string;
threshold: number;
};
type CustomTextFieldProps = {
name: string;
label: string;
required?: boolean;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
};
const CustomTextField = ({
name,
label,
required = false,
onChange,
}: CustomTextFieldProps) => (
<Field
name={name}
label={label}
required={required}
variant="filled"
color="secondary"
as={TextField}
onChange={onChange}
sx={{ width: "100%", marginTop: "15px" }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
},
}}
InputLabelProps={{
style: { color: theme.palette.secondary.main },
}}
/>
);
const initialValues: FormValues = {
codeword: "",
description: "",
greetings: "",
dueTo: 0,
activationCount: 0,
privilegeId: "",
amount: 0,
layer: 1,
factor: 0,
target: "",
threshold: 0,
codeword: "",
description: "",
greetings: "",
dueTo: 0,
activationCount: 0,
privilegeId: "",
amount: 0,
layer: 1,
factor: 0,
target: "",
threshold: 0,
};
export const CreatePromocodeForm = () => {
const [bonusType, setBonusType] = useState<BonusType>("discount");
const { privileges } = usePrivilegeStore();
const [bonusType, setBonusType] = useState<BonusType>("discount");
const { privileges } = usePrivilegeStore();
useEffect(() => {
requestPrivileges();
}, []);
useEffect(() => {
requestPrivileges();
}, []);
const submitForm = async (values: FormValues) => {
const body = {
...values,
threshold: values.layer === 1 ? values.threshold : values.threshold * 100,
const submitForm = (values: FormValues) => {
const body = {
...values,
threshold: values.layer === 1 ? values.threshold : values.threshold * 100,
};
const factorFromDiscountValue = 1 - body.factor / 100;
return createPromocode({
codeword: body.codeword,
description: body.description,
greetings: body.greetings,
dueTo: body.dueTo,
activationCount: body.activationCount,
bonus: {
privilege: { privilegeID: body.privilegeId, amount: body.amount },
discount: {
layer: body.layer,
factor: factorFromDiscountValue,
target: body.target,
threshold: body.threshold,
},
},
});
};
await createPromocode({
codeword: body.codeword,
description: body.description,
greetings: body.greetings,
dueTo: body.dueTo,
activationCount: body.activationCount,
bonus: {
privilege: { privilegeID: body.privilegeId, amount: body.amount },
discount: {
layer: body.layer,
factor: body.factor,
target: body.target,
threshold: body.threshold,
},
},
});
};
return (
<Formik initialValues={initialValues} onSubmit={submitForm}>
{({ values, handleChange, handleBlur, setFieldValue }) => (
<Form
style={{
width: "100%",
maxWidth: "600px",
padding: "0 10px",
}}
>
<CustomTextField
name="codeword"
label="Кодовое слово"
required
onChange={handleChange}
/>
<CustomTextField
name="description"
label="Описание"
required
onChange={handleChange}
/>
<CustomTextField
name="greetings"
label="Приветственное сообщение"
required
onChange={handleChange}
/>
<Typography
variant="h4"
sx={{
height: "40px",
fontWeight: "normal",
marginTop: "15px",
color: theme.palette.secondary.main,
}}
>
Время существования промокода
</Typography>
<Field
name="dueTo"
as={DesktopDatePicker}
inputFormat="DD/MM/YYYY"
value={values.dueTo ? new Date(Number(values.dueTo) * 1000) : null}
onChange={(date: Date | null) => {
if (date) {
setFieldValue("dueTo", moment(date).unix() || null);
}
}}
renderInput={(params: TextFieldProps) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
},
}}
/>
<CustomTextField
name="activationCount"
label="Количество активаций промокода"
onChange={({ target }) =>
setFieldValue(
"activationCount",
Number(target.value.replace(/\D/g, ""))
)
}
/>
<RadioGroup
row
name="bonusType"
value={bonusType}
sx={{ marginTop: "15px" }}
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
setBonusType(target.value as BonusType);
}}
onBlur={handleBlur}
>
<FormControlLabel
value="discount"
control={<Radio color="secondary" />}
label="Скидка"
/>
<FormControlLabel
value="privilege"
control={<Radio color="secondary" />}
label="Привилегия"
/>
</RadioGroup>
{bonusType === "discount" && (
<>
<RadioGroup
row
name="layer"
value={values.layer}
sx={{ marginTop: "15px" }}
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
setFieldValue("target", "");
setFieldValue("layer", Number(target.value));
}}
onBlur={handleBlur}
>
<FormControlLabel
value="1"
control={<Radio color="secondary" />}
label="Привилегия"
/>
<FormControlLabel
value="2"
control={<Radio color="secondary" />}
label="Сервис"
/>
</RadioGroup>
<CustomTextField
name="factor"
label="Процент скидки"
required
onChange={({ target }) =>
setFieldValue(
"factor",
Number(target.value.replace(/\D/g, ""))
)
}
/>
<Typography
variant="h4"
sx={{
height: "40px",
fontWeight: "normal",
marginTop: "15px",
padding: "0 12px",
color: theme.palette.secondary.main,
}}
>
{values.layer === 1 ? "Выбор привилегии" : "Выбор сервиса"}
</Typography>
<Field
name="target"
as={Select}
label={values.layer === 1 ? "Привилегия" : "Сервис"}
sx={{
width: "100%",
border: "2px solid",
color: theme.palette.secondary.main,
borderColor: theme.palette.secondary.main,
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
border: "none",
},
".MuiSvgIcon-root ": { fill: theme.palette.secondary.main },
}}
children={
values.layer === 1
? privileges.map(({ name, privilegeId }) => (
<MenuItem key={privilegeId} value={privilegeId}>
{name}
</MenuItem>
))
: SERVICE_LIST.map(({ displayName, serviceKey }) => (
<MenuItem key={serviceKey} value={serviceKey}>
{displayName}
</MenuItem>
))
}
/>
<CustomTextField
name="threshold"
label="При каком значении применяется скидка"
onChange={({ target }) =>
setFieldValue(
"threshold",
Number(target.value.replace(/\D/g, ""))
)
}
/>
</>
)}
{bonusType === "privilege" && (
<>
<Typography
variant="h4"
sx={{
height: "40px",
fontWeight: "normal",
marginTop: "15px",
padding: "0 12px",
color: theme.palette.secondary.main,
}}
>
Выбор привилегии
</Typography>
<Field
name="privilegeId"
as={Select}
label="Привилегия"
sx={{
width: "100%",
border: "2px solid",
color: theme.palette.secondary.main,
borderColor: theme.palette.secondary.main,
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
border: "none",
},
".MuiSvgIcon-root ": { fill: theme.palette.secondary.main },
}}
children={privileges.map(({ name, privilegeId }) => (
<MenuItem key={privilegeId} value={privilegeId}>
{name}
</MenuItem>
))}
/>
<CustomTextField
name="amount"
label="Количество"
required
onChange={({ target }) =>
setFieldValue(
"amount",
Number(target.value.replace(/\D/g, ""))
)
}
/>
</>
)}
<Button
variant="contained"
sx={{
display: "block",
padding: "10px",
margin: "15px auto 0",
fontWeight: "normal",
fontSize: "18px",
backgroundColor: theme.palette.menu.main,
"&:hover": { backgroundColor: theme.palette.grayMedium.main },
}}
type="submit"
>
Cоздать
</Button>
</Form>
)}
</Formik>
);
return (
<Formik initialValues={initialValues} onSubmit={submitForm}>
{({ values, handleChange, handleBlur, setFieldValue }) => (
<Form
style={{
width: "100%",
maxWidth: "600px",
padding: "0 10px",
}}
>
<CustomTextField
name="codeword"
label="Кодовое слово"
required
onChange={handleChange}
/>
<CustomTextField
name="description"
label="Описание"
required
onChange={handleChange}
/>
<CustomTextField
name="greetings"
label="Приветственное сообщение"
required
onChange={handleChange}
/>
<Typography
variant="h4"
sx={{
height: "40px",
fontWeight: "normal",
marginTop: "15px",
color: theme.palette.secondary.main,
}}
>
Время существования промокода
</Typography>
<Field
name="dueTo"
as={DesktopDatePicker}
inputFormat="DD/MM/YYYY"
value={values.dueTo ? new Date(Number(values.dueTo) * 1000) : null}
onChange={(date: Date | null) => {
if (date) {
setFieldValue("dueTo", moment(date).unix() || null);
}
}}
renderInput={(params: TextFieldProps) => <TextField {...params} />}
InputProps={{
sx: {
height: "40px",
color: theme.palette.secondary.main,
border: "1px solid",
borderColor: theme.palette.secondary.main,
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
},
}}
/>
<CustomTextField
name="activationCount"
label="Количество активаций промокода"
onChange={({ target }) =>
setFieldValue(
"activationCount",
Number(target.value.replace(/\D/g, ""))
)
}
/>
<RadioGroup
row
name="bonusType"
value={bonusType}
sx={{ marginTop: "15px" }}
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
setBonusType(target.value as BonusType);
}}
onBlur={handleBlur}
>
<FormControlLabel
value="discount"
control={<Radio color="secondary" />}
label="Скидка"
/>
<FormControlLabel
value="privilege"
control={<Radio color="secondary" />}
label="Привилегия"
/>
</RadioGroup>
{bonusType === "discount" && (
<>
<RadioGroup
row
name="layer"
value={values.layer}
sx={{ marginTop: "15px" }}
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
setFieldValue("target", "");
setFieldValue("layer", Number(target.value));
}}
onBlur={handleBlur}
>
<FormControlLabel
value="1"
control={<Radio color="secondary" />}
label="Привилегия"
/>
<FormControlLabel
value="2"
control={<Radio color="secondary" />}
label="Сервис"
/>
</RadioGroup>
<CustomTextField
name="factor"
label="Процент скидки"
required
onChange={({ target }) => {
setFieldValue(
"factor",
Number(target.value.replace(/\D/g, ""))
);
}}
/>
<Typography
variant="h4"
sx={{
height: "40px",
fontWeight: "normal",
marginTop: "15px",
padding: "0 12px",
color: theme.palette.secondary.main,
}}
>
{values.layer === 1 ? "Выбор привилегии" : "Выбор сервиса"}
</Typography>
<Field
name="target"
as={Select}
label={values.layer === 1 ? "Привилегия" : "Сервис"}
sx={{
width: "100%",
border: "2px solid",
color: theme.palette.secondary.main,
borderColor: theme.palette.secondary.main,
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
border: "none",
},
".MuiSvgIcon-root ": { fill: theme.palette.secondary.main },
}}
children={
values.layer === 1
? privileges.map(({ name, privilegeId }) => (
<MenuItem key={privilegeId} value={privilegeId}>
{name}
</MenuItem>
))
: SERVICE_LIST.map(({ displayName, serviceKey }) => (
<MenuItem key={serviceKey} value={serviceKey}>
{displayName}
</MenuItem>
))
}
/>
<CustomTextField
name="threshold"
label="При каком значении применяется скидка"
onChange={({ target }) =>
setFieldValue(
"threshold",
Number(target.value.replace(/\D/g, ""))
)
}
/>
</>
)}
{bonusType === "privilege" && (
<>
<Typography
variant="h4"
sx={{
height: "40px",
fontWeight: "normal",
marginTop: "15px",
padding: "0 12px",
color: theme.palette.secondary.main,
}}
>
Выбор привилегии
</Typography>
<Field
name="privilegeId"
as={Select}
label="Привилегия"
sx={{
width: "100%",
border: "2px solid",
color: theme.palette.secondary.main,
borderColor: theme.palette.secondary.main,
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
border: "none",
},
".MuiSvgIcon-root ": { fill: theme.palette.secondary.main },
}}
children={privileges.map(({ name, privilegeId }) => (
<MenuItem key={privilegeId} value={privilegeId}>
{name}
</MenuItem>
))}
/>
<CustomTextField
name="amount"
label="Количество"
required
onChange={({ target }) =>
setFieldValue(
"amount",
Number(target.value.replace(/\D/g, ""))
)
}
/>
</>
)}
<Button
variant="contained"
sx={{
display: "block",
padding: "10px",
margin: "15px auto 0",
fontWeight: "normal",
fontSize: "18px",
backgroundColor: theme.palette.menu.main,
"&:hover": { backgroundColor: theme.palette.grayMedium.main },
}}
type="submit"
>
Cоздать
</Button>
</Form>
)}
</Formik>
);
};
type CustomTextFieldProps = {
name: string;
label: string;
required?: boolean;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
};
const CustomTextField = ({
name,
label,
required = false,
onChange,
}: CustomTextFieldProps) => (
<Field
name={name}
label={label}
required={required}
variant="filled"
color="secondary"
as={TextField}
onChange={onChange}
sx={{ width: "100%", marginTop: "15px" }}
InputProps={{
style: {
backgroundColor: theme.palette.content.main,
color: theme.palette.secondary.main,
},
}}
InputLabelProps={{
style: { color: theme.palette.secondary.main },
}}
/>
);

@ -1,56 +1,93 @@
import { Box } from "@mui/material";
import { Box, IconButton } from "@mui/material";
import { DataGrid, GridColDef, GridToolbar } from "@mui/x-data-grid";
import { usePromocodeStore } from "@root/stores/promocodes";
import { Promocode } from "@root/model/promocodes";
import DeleteIcon from '@mui/icons-material/Delete';
import theme from "@root/theme";
import { deletePromocode } from "@root/api/promocode/swr";
const columns: GridColDef[] = [
{ field: "id", headerName: "ID", width: 30, sortable: false },
{
field: "name",
headerName: "Название промокода",
width: 200,
sortable: false,
},
{ field: "endless", headerName: "Бесконечный", width: 120, sortable: false },
{ field: "from", headerName: "От", width: 120, sortable: false },
{ field: "dueTo", headerName: "До", width: 120, sortable: false },
{
field: "privileges",
headerName: "Привилегии",
width: 210,
sortable: false,
},
const columns: GridColDef<Promocode, string | number>[] = [
{
field: "id",
headerName: "ID",
width: 30,
sortable: false,
valueGetter: ({ row }) => row.id,
},
{
field: "codeword",
headerName: "Кодовое слово",
width: 160,
sortable: false,
valueGetter: ({ row }) => row.codeword,
},
{
field: "factor",
headerName: "Коэф. скидки",
width: 140,
sortable: false,
valueGetter: ({ row }) => row.bonus.discount.factor,
},
{
field: "activationCount",
headerName: "Кол-во активаций",
width: 140,
sortable: false,
valueGetter: ({ row }) => row.activationCount,
},
{
field: "dueTo",
headerName: "Истекает",
width: 160,
sortable: false,
valueGetter: ({ row }) => new Date(row.dueTo * 1000).toLocaleString(),
},
{
field: "delete",
headerName: "",
width: 60,
renderCell: ({ row }) => {
return (
<IconButton onClick={() => deletePromocode(row.id)}>
<DeleteIcon />
</IconButton>
);
},
},
];
export const PromocodesList = () => {
const { promocodes } = usePromocodeStore();
return (
<Box style={{ width: "80%", marginTop: "55px" }}>
<Box style={{ height: 400 }}>
<DataGrid
checkboxSelection={true}
rows={promocodes}
columns={columns}
sx={{
color: theme.palette.secondary.main,
"& .MuiDataGrid-iconSeparator": { display: "none" },
"& .css-levciy-MuiTablePagination-displayedRows": {
color: theme.palette.secondary.main,
},
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
"& .MuiTablePagination-selectLabel": {
color: theme.palette.secondary.main,
},
"& .MuiInputBase-root": { color: theme.palette.secondary.main },
"& .MuiButton-text": { color: theme.palette.secondary.main },
}}
components={{ Toolbar: GridToolbar }}
onSelectionModelChange={(ids) => console.log("datagrid select")}
/>
</Box>
</Box>
);
type Props = {
promocodes: Promocode[];
};
export const PromocodesList = ({ promocodes }: Props) => {
return (
<Box style={{ width: "80%", marginTop: "55px" }}>
<Box style={{ height: 400 }}>
<DataGrid
disableSelectionOnClick={true}
checkboxSelection={true}
rows={promocodes}
columns={columns}
sx={{
minHeight: "1000px",
color: theme.palette.secondary.main,
"& .MuiDataGrid-iconSeparator": { display: "none" },
"& .css-levciy-MuiTablePagination-displayedRows": {
color: theme.palette.secondary.main,
},
"& .MuiSvgIcon-root": { color: theme.palette.secondary.main },
"& .MuiTablePagination-selectLabel": {
color: theme.palette.secondary.main,
},
"& .MuiInputBase-root": { color: theme.palette.secondary.main },
"& .MuiButton-text": { color: theme.palette.secondary.main },
}}
components={{ Toolbar: GridToolbar }}
onSelectionModelChange={(ids) => console.log("datagrid select")}
/>
</Box>
</Box>
);
};

@ -1,30 +1,39 @@
import { Typography } from "@mui/material";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { CircularProgress, Typography } from "@mui/material";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
import { PromocodesList } from "./PromocodesList";
import { CreatePromocodeForm } from "./CreatePromocodeForm";
import { PromocodesList } from "./PromocodesList";
import { usePromocodes } from "@root/api/promocode/swr";
import theme from "@root/theme";
export const PromocodeManagement = () => (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
textTransform: "uppercase",
color: theme.palette.secondary.main,
}}
>
Создание промокода
</Typography>
<CreatePromocodeForm />
<PromocodesList />
</LocalizationProvider>
);
export const PromocodeManagement = () => {
const { data, error, isLoading } = usePromocodes();
if (error) return <Typography>Ошибка загрузки промокодов</Typography>;
if (isLoading) return <CircularProgress />;
if (!data) return null;
return (
<LocalizationProvider dateAdapter={AdapterDayjs}>
<Typography
variant="subtitle1"
sx={{
width: "90%",
height: "60px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
textTransform: "uppercase",
color: theme.palette.secondary.main,
}}
>
Создание промокода
</Typography>
<CreatePromocodeForm />
<PromocodesList promocodes={data} />
</LocalizationProvider>
);
};

@ -1,33 +0,0 @@
import { Promocode } from "@root/model/cart";
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
interface PromocodeStore {
promocodes: Promocode[];
addPromocodes: (newPromocodes: Promocode[]) => void;
deletePromocodes: (promocodeIdsToDelete: string[]) => void;
}
export const usePromocodeStore = create<PromocodeStore>()(
devtools(
// persist(
(set, get) => ({
promocodes: [],
addPromocodes: newPromocodes => set(state => (
{ promocodes: [...state.promocodes, ...newPromocodes] }
)),
deletePromocodes: promocodeIdsToDelete => set(state => (
{ promocodes: state.promocodes.filter(promocode => !promocodeIdsToDelete.includes(promocode.id)) }
)),
}),
// {
// name: "promocodes",
// getStorage: () => localStorage,
// }
// ),
{
name: "Promocode store"
}
)
);

@ -4327,6 +4327,11 @@ cli-truncate@^2.1.0:
slice-ansi "^3.0.0"
string-width "^4.2.0"
client-only@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
cliui@^7.0.2:
version "7.0.4"
resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz"
@ -11801,6 +11806,14 @@ svgo@^2.7.0:
picocolors "^1.0.0"
stable "^0.1.8"
swr@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.5.tgz#063eea0e9939f947227d5ca760cc53696f46446b"
integrity sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==
dependencies:
client-only "^0.0.1"
use-sync-external-store "^1.2.0"
symbol-tree@^3.2.4:
version "3.2.4"
resolved "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz"
@ -12360,7 +12373,7 @@ use-debounce@^9.0.4:
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85"
integrity sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==
use-sync-external-store@1.2.0:
use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==