add storage management features including update functionality and storage listing page
This commit is contained in:
@@ -35,3 +35,16 @@ export const newStorage = async (name, description) => {
|
|||||||
return { code: "es002" };
|
return { code: "es002" };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateStorage = async (uuid, values) => {
|
||||||
|
const [result] = await pool.query(
|
||||||
|
"UPDATE storage_locations SET name = ?, description = ? WHERE uuid = UUID_TO_BIN(?);",
|
||||||
|
[values.name, values.description, uuid],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.affectedRows > 0) {
|
||||||
|
return { code: "ss003" };
|
||||||
|
} else {
|
||||||
|
return { code: "es003" };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { authenticate } from "../../services/tokenService.js";
|
import { authenticate } from "../../services/tokenService.js";
|
||||||
import { allStorages, newStorage } from "./database/storage.database.js";
|
import {
|
||||||
|
allStorages,
|
||||||
|
newStorage,
|
||||||
|
updateStorage,
|
||||||
|
} from "./database/storage.database.js";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -51,4 +55,29 @@ router.post("/new-storage", authenticate, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post("/update-storage", authenticate, async (req, res) => {
|
||||||
|
const storageUUID = req.query.storageUUID;
|
||||||
|
const values = req.body;
|
||||||
|
|
||||||
|
const result = await updateStorage(storageUUID, values);
|
||||||
|
|
||||||
|
if (result.code === "es003") {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
code: "es003",
|
||||||
|
data: null,
|
||||||
|
message: "unexpected server error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.code === "ss003") {
|
||||||
|
res.status(201).json({
|
||||||
|
success: true,
|
||||||
|
code: "ss003",
|
||||||
|
data: null,
|
||||||
|
message: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Button, Typography } from "@mui/joy";
|
import { Button, Typography } from "@mui/joy";
|
||||||
import InventoryIcon from "@mui/icons-material/Inventory";
|
import InventoryIcon from "@mui/icons-material/Inventory";
|
||||||
import AddBoxIcon from "@mui/icons-material/AddBox";
|
import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||||
|
import StorageIcon from "@mui/icons-material/Storage";
|
||||||
import AccountBoxIcon from "@mui/icons-material/AccountBox";
|
import AccountBoxIcon from "@mui/icons-material/AccountBox";
|
||||||
import { useNavigate, useMatchRoute } from "@tanstack/react-router";
|
import { useNavigate, useMatchRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
@@ -50,6 +51,14 @@ export const Sidebar = () => {
|
|||||||
>
|
>
|
||||||
{t("add")}
|
{t("add")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate({ to: "/app/storages" })}
|
||||||
|
variant={variant("/app/storages")}
|
||||||
|
startDecorator={<StorageIcon />}
|
||||||
|
className={btnClass}
|
||||||
|
>
|
||||||
|
{t("storages")}
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => navigate({ to: "/app/profile" })}
|
onClick={() => navigate({ to: "/app/profile" })}
|
||||||
variant={variant("/app/profile")}
|
variant={variant("/app/profile")}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { useQueryClient, useMutation } from "@tanstack/react-query";
|
||||||
|
import { updateStorage } from "../utils/uxFncs";
|
||||||
|
import { useForm } from "@tanstack/react-form";
|
||||||
|
import { useStore } from "@tanstack/react-store";
|
||||||
|
import { Input, Button } from "@mui/joy";
|
||||||
|
import type { Storage } from "../misc/interfaces";
|
||||||
|
import { formatDate } from "../utils/uxFncs";
|
||||||
|
|
||||||
|
interface StorageRowProps {
|
||||||
|
storage: Storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StorageRow = ({ storage }: StorageRowProps) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const mutation = useMutation({
|
||||||
|
mutationFn: (values: Pick<Storage, "name" | "description">) =>
|
||||||
|
updateStorage(storage.uuid, values),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["storages"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
defaultValues: {
|
||||||
|
name: storage.name,
|
||||||
|
description: storage.description ?? "",
|
||||||
|
},
|
||||||
|
onSubmit: async ({ value }) => {
|
||||||
|
await mutation.mutateAsync(value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const values = useStore(form.baseStore, (state) => state.values);
|
||||||
|
const isDirty =
|
||||||
|
values.name !== storage.name ||
|
||||||
|
(values.description ?? "") !== (storage.description ?? "");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={storage.uuid}>
|
||||||
|
<td>
|
||||||
|
<form.Field name="name">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<form.Field name="description">
|
||||||
|
{(field) => (
|
||||||
|
<Input
|
||||||
|
value={field.state.value}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
</td>
|
||||||
|
<td>{formatDate(storage.created_at)}</td>
|
||||||
|
<td>{formatDate(storage.updated_at)}</td>
|
||||||
|
<td>
|
||||||
|
<Button
|
||||||
|
onClick={form.handleSubmit}
|
||||||
|
disabled={!isDirty || mutation.isPending}
|
||||||
|
>
|
||||||
|
{mutation.isPending ? "..." : "Save"}
|
||||||
|
</Button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -20,3 +20,11 @@ export type ProductFormValues = {
|
|||||||
price: string;
|
price: string;
|
||||||
storage_location_uuid: string;
|
storage_location_uuid: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface Storage {
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
uuid: string;
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { getProducts } from "../utils/uxFncs";
|
import { getProducts } from "../utils/uxFncs";
|
||||||
import { visuallyHidden } from "@mui/utils";
|
import { visuallyHidden } from "@mui/utils";
|
||||||
|
import { formatDate } from "../utils/uxFncs";
|
||||||
|
|
||||||
type Order = "asc" | "desc";
|
type Order = "asc" | "desc";
|
||||||
|
|
||||||
@@ -47,17 +48,6 @@ type ProductRow = {
|
|||||||
refillDate: string;
|
refillDate: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (value?: string | null) => {
|
|
||||||
if (!value) {
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
const date = new Date(value);
|
|
||||||
if (Number.isNaN(date.getTime())) {
|
|
||||||
return "-";
|
|
||||||
}
|
|
||||||
return date.toLocaleDateString("de-DE");
|
|
||||||
};
|
|
||||||
|
|
||||||
const descendingComparator = <T,>(a: T, b: T, orderBy: keyof T) => {
|
const descendingComparator = <T,>(a: T, b: T, orderBy: keyof T) => {
|
||||||
const aValue = (a[orderBy] ?? "") as string | number;
|
const aValue = (a[orderBy] ?? "") as string | number;
|
||||||
const bValue = (b[orderBy] ?? "") as string | number;
|
const bValue = (b[orderBy] ?? "") as string | number;
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { getStorages } from "../utils/uxFncs";
|
||||||
|
import { Sheet, Table, Button } from "@mui/joy";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import type { Storage } from "../misc/interfaces";
|
||||||
|
import { StorageRow } from "../components/StorageRow";
|
||||||
|
|
||||||
|
export const Storages = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const { data: storages, isLoading } = useQuery({
|
||||||
|
queryKey: ["storages"],
|
||||||
|
queryFn: () => getStorages(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet>
|
||||||
|
<Button>+</Button>
|
||||||
|
<Table
|
||||||
|
borderAxis="x"
|
||||||
|
color="neutral"
|
||||||
|
stickyHeader
|
||||||
|
stripe="odd"
|
||||||
|
variant="soft"
|
||||||
|
>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{t("name")}</th>
|
||||||
|
<th>{t("description")}</th>
|
||||||
|
<th>{t("created-at")}</th>
|
||||||
|
<th>{t("updated-at")}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{storages?.map((storage: Storage) => (
|
||||||
|
<StorageRow key={storage.uuid} storage={storage} />
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ import { Route as LoginRouteImport } from './routes/login'
|
|||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as AppHiddenLayoutRouteImport } from './routes/app/_hiddenLayout'
|
import { Route as AppHiddenLayoutRouteImport } from './routes/app/_hiddenLayout'
|
||||||
import { Route as AppHiddenLayoutViewProductRouteImport } from './routes/app/_hiddenLayout/view-product'
|
import { Route as AppHiddenLayoutViewProductRouteImport } from './routes/app/_hiddenLayout/view-product'
|
||||||
|
import { Route as AppHiddenLayoutStoragesRouteImport } from './routes/app/_hiddenLayout/storages'
|
||||||
import { Route as AppHiddenLayoutProfileRouteImport } from './routes/app/_hiddenLayout/profile'
|
import { Route as AppHiddenLayoutProfileRouteImport } from './routes/app/_hiddenLayout/profile'
|
||||||
import { Route as AppHiddenLayoutInventoryRouteImport } from './routes/app/_hiddenLayout/inventory'
|
import { Route as AppHiddenLayoutInventoryRouteImport } from './routes/app/_hiddenLayout/inventory'
|
||||||
import { Route as AppHiddenLayoutAddProductRouteImport } from './routes/app/_hiddenLayout/add-product'
|
import { Route as AppHiddenLayoutAddProductRouteImport } from './routes/app/_hiddenLayout/add-product'
|
||||||
@@ -38,6 +39,11 @@ const AppHiddenLayoutViewProductRoute =
|
|||||||
path: '/view-product',
|
path: '/view-product',
|
||||||
getParentRoute: () => AppHiddenLayoutRoute,
|
getParentRoute: () => AppHiddenLayoutRoute,
|
||||||
} as any)
|
} as any)
|
||||||
|
const AppHiddenLayoutStoragesRoute = AppHiddenLayoutStoragesRouteImport.update({
|
||||||
|
id: '/storages',
|
||||||
|
path: '/storages',
|
||||||
|
getParentRoute: () => AppHiddenLayoutRoute,
|
||||||
|
} as any)
|
||||||
const AppHiddenLayoutProfileRoute = AppHiddenLayoutProfileRouteImport.update({
|
const AppHiddenLayoutProfileRoute = AppHiddenLayoutProfileRouteImport.update({
|
||||||
id: '/profile',
|
id: '/profile',
|
||||||
path: '/profile',
|
path: '/profile',
|
||||||
@@ -63,6 +69,7 @@ export interface FileRoutesByFullPath {
|
|||||||
'/app/add-product': typeof AppHiddenLayoutAddProductRoute
|
'/app/add-product': typeof AppHiddenLayoutAddProductRoute
|
||||||
'/app/inventory': typeof AppHiddenLayoutInventoryRoute
|
'/app/inventory': typeof AppHiddenLayoutInventoryRoute
|
||||||
'/app/profile': typeof AppHiddenLayoutProfileRoute
|
'/app/profile': typeof AppHiddenLayoutProfileRoute
|
||||||
|
'/app/storages': typeof AppHiddenLayoutStoragesRoute
|
||||||
'/app/view-product': typeof AppHiddenLayoutViewProductRoute
|
'/app/view-product': typeof AppHiddenLayoutViewProductRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
@@ -72,6 +79,7 @@ export interface FileRoutesByTo {
|
|||||||
'/app/add-product': typeof AppHiddenLayoutAddProductRoute
|
'/app/add-product': typeof AppHiddenLayoutAddProductRoute
|
||||||
'/app/inventory': typeof AppHiddenLayoutInventoryRoute
|
'/app/inventory': typeof AppHiddenLayoutInventoryRoute
|
||||||
'/app/profile': typeof AppHiddenLayoutProfileRoute
|
'/app/profile': typeof AppHiddenLayoutProfileRoute
|
||||||
|
'/app/storages': typeof AppHiddenLayoutStoragesRoute
|
||||||
'/app/view-product': typeof AppHiddenLayoutViewProductRoute
|
'/app/view-product': typeof AppHiddenLayoutViewProductRoute
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
@@ -82,6 +90,7 @@ export interface FileRoutesById {
|
|||||||
'/app/_hiddenLayout/add-product': typeof AppHiddenLayoutAddProductRoute
|
'/app/_hiddenLayout/add-product': typeof AppHiddenLayoutAddProductRoute
|
||||||
'/app/_hiddenLayout/inventory': typeof AppHiddenLayoutInventoryRoute
|
'/app/_hiddenLayout/inventory': typeof AppHiddenLayoutInventoryRoute
|
||||||
'/app/_hiddenLayout/profile': typeof AppHiddenLayoutProfileRoute
|
'/app/_hiddenLayout/profile': typeof AppHiddenLayoutProfileRoute
|
||||||
|
'/app/_hiddenLayout/storages': typeof AppHiddenLayoutStoragesRoute
|
||||||
'/app/_hiddenLayout/view-product': typeof AppHiddenLayoutViewProductRoute
|
'/app/_hiddenLayout/view-product': typeof AppHiddenLayoutViewProductRoute
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
@@ -93,6 +102,7 @@ export interface FileRouteTypes {
|
|||||||
| '/app/add-product'
|
| '/app/add-product'
|
||||||
| '/app/inventory'
|
| '/app/inventory'
|
||||||
| '/app/profile'
|
| '/app/profile'
|
||||||
|
| '/app/storages'
|
||||||
| '/app/view-product'
|
| '/app/view-product'
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
@@ -102,6 +112,7 @@ export interface FileRouteTypes {
|
|||||||
| '/app/add-product'
|
| '/app/add-product'
|
||||||
| '/app/inventory'
|
| '/app/inventory'
|
||||||
| '/app/profile'
|
| '/app/profile'
|
||||||
|
| '/app/storages'
|
||||||
| '/app/view-product'
|
| '/app/view-product'
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
@@ -111,6 +122,7 @@ export interface FileRouteTypes {
|
|||||||
| '/app/_hiddenLayout/add-product'
|
| '/app/_hiddenLayout/add-product'
|
||||||
| '/app/_hiddenLayout/inventory'
|
| '/app/_hiddenLayout/inventory'
|
||||||
| '/app/_hiddenLayout/profile'
|
| '/app/_hiddenLayout/profile'
|
||||||
|
| '/app/_hiddenLayout/storages'
|
||||||
| '/app/_hiddenLayout/view-product'
|
| '/app/_hiddenLayout/view-product'
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
@@ -150,6 +162,13 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AppHiddenLayoutViewProductRouteImport
|
preLoaderRoute: typeof AppHiddenLayoutViewProductRouteImport
|
||||||
parentRoute: typeof AppHiddenLayoutRoute
|
parentRoute: typeof AppHiddenLayoutRoute
|
||||||
}
|
}
|
||||||
|
'/app/_hiddenLayout/storages': {
|
||||||
|
id: '/app/_hiddenLayout/storages'
|
||||||
|
path: '/storages'
|
||||||
|
fullPath: '/app/storages'
|
||||||
|
preLoaderRoute: typeof AppHiddenLayoutStoragesRouteImport
|
||||||
|
parentRoute: typeof AppHiddenLayoutRoute
|
||||||
|
}
|
||||||
'/app/_hiddenLayout/profile': {
|
'/app/_hiddenLayout/profile': {
|
||||||
id: '/app/_hiddenLayout/profile'
|
id: '/app/_hiddenLayout/profile'
|
||||||
path: '/profile'
|
path: '/profile'
|
||||||
@@ -178,6 +197,7 @@ interface AppHiddenLayoutRouteChildren {
|
|||||||
AppHiddenLayoutAddProductRoute: typeof AppHiddenLayoutAddProductRoute
|
AppHiddenLayoutAddProductRoute: typeof AppHiddenLayoutAddProductRoute
|
||||||
AppHiddenLayoutInventoryRoute: typeof AppHiddenLayoutInventoryRoute
|
AppHiddenLayoutInventoryRoute: typeof AppHiddenLayoutInventoryRoute
|
||||||
AppHiddenLayoutProfileRoute: typeof AppHiddenLayoutProfileRoute
|
AppHiddenLayoutProfileRoute: typeof AppHiddenLayoutProfileRoute
|
||||||
|
AppHiddenLayoutStoragesRoute: typeof AppHiddenLayoutStoragesRoute
|
||||||
AppHiddenLayoutViewProductRoute: typeof AppHiddenLayoutViewProductRoute
|
AppHiddenLayoutViewProductRoute: typeof AppHiddenLayoutViewProductRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,6 +205,7 @@ const AppHiddenLayoutRouteChildren: AppHiddenLayoutRouteChildren = {
|
|||||||
AppHiddenLayoutAddProductRoute: AppHiddenLayoutAddProductRoute,
|
AppHiddenLayoutAddProductRoute: AppHiddenLayoutAddProductRoute,
|
||||||
AppHiddenLayoutInventoryRoute: AppHiddenLayoutInventoryRoute,
|
AppHiddenLayoutInventoryRoute: AppHiddenLayoutInventoryRoute,
|
||||||
AppHiddenLayoutProfileRoute: AppHiddenLayoutProfileRoute,
|
AppHiddenLayoutProfileRoute: AppHiddenLayoutProfileRoute,
|
||||||
|
AppHiddenLayoutStoragesRoute: AppHiddenLayoutStoragesRoute,
|
||||||
AppHiddenLayoutViewProductRoute: AppHiddenLayoutViewProductRoute,
|
AppHiddenLayoutViewProductRoute: AppHiddenLayoutViewProductRoute,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { Storages } from "../../../pages/Storages";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/app/_hiddenLayout/storages")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <Storages />;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { API_BASE } from "../config/api.config";
|
import { API_BASE } from "../config/api.config";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import type { ProductFormValues } from "../misc/interfaces";
|
import type { ProductFormValues, Storage } from "../misc/interfaces";
|
||||||
|
|
||||||
export const getProducts = async () => {
|
export const getProducts = async () => {
|
||||||
const result = await fetch(`${API_BASE}/products/all-products`, {
|
const result = await fetch(`${API_BASE}/products/all-products`, {
|
||||||
@@ -143,3 +143,42 @@ export const createProduct = async (values: ProductFormValues) => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateStorage = async (
|
||||||
|
uuid: string,
|
||||||
|
values: Pick<Storage, "name" | "description">,
|
||||||
|
) => {
|
||||||
|
const result = await fetch(
|
||||||
|
`${API_BASE}/storage/update-storage?storageUUID=${uuid}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify(values),
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${Cookies.get("token") || ""}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await result.json();
|
||||||
|
|
||||||
|
if (response.code === "ep001") {
|
||||||
|
return { success: false, code: response.code };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.code === "sp001") {
|
||||||
|
return response.data;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formatDate = (value?: string | null) => {
|
||||||
|
if (!value) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) {
|
||||||
|
return "-";
|
||||||
|
}
|
||||||
|
return date.toLocaleDateString("de-DE");
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user