feat: implement dark mode and add custom color palette (theme)
This commit is contained in:
@@ -2,6 +2,8 @@ import { createRouter, RouterProvider } from "@tanstack/react-router";
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
import { routeTree } from "./routeTree.gen";
|
import { routeTree } from "./routeTree.gen";
|
||||||
import { NotFound } from "./components/NotFound.tsx";
|
import { NotFound } from "./components/NotFound.tsx";
|
||||||
|
import { CssVarsProvider } from "@mui/joy";
|
||||||
|
import { theme } from "./theme.ts";
|
||||||
|
|
||||||
const router = createRouter({ routeTree, defaultNotFoundComponent: NotFound });
|
const router = createRouter({ routeTree, defaultNotFoundComponent: NotFound });
|
||||||
|
|
||||||
@@ -12,7 +14,11 @@ declare module "@tanstack/react-router" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return <RouterProvider router={router} />;
|
return (
|
||||||
|
<CssVarsProvider theme={theme} defaultMode={"system"}>
|
||||||
|
<RouterProvider router={router} />
|
||||||
|
</CssVarsProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -68,14 +68,34 @@ export const LoginCard = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen w-full items-center justify-center bg-[radial-gradient(1200px_circle_at_20%_10%,#e6f2ff_0%,#f7f9fc_40%,#f1f5fb_100%)] px-6 py-10">
|
<div
|
||||||
|
className="flex min-h-screen w-full items-center justify-center px-6 py-10"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"radial-gradient(1200px circle at 20% 10%, var(--joy-palette-primary-100) 0%, var(--joy-palette-background-body) 40%, var(--joy-palette-background-level1) 100%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="mx-auto flex w-full max-w-4xl items-center justify-center">
|
<div className="mx-auto flex w-full max-w-4xl items-center justify-center">
|
||||||
<div className="w-full max-w-md rounded-3xl border border-white/70 bg-white/80 p-8 shadow-[0_24px_60px_rgba(12,38,78,0.18)] backdrop-blur">
|
<div
|
||||||
|
className="w-full max-w-md rounded-3xl p-8 backdrop-blur"
|
||||||
|
style={{
|
||||||
|
border: "1px solid var(--joy-palette-divider)",
|
||||||
|
backgroundColor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow:
|
||||||
|
"0 24px 60px color-mix(in srgb, var(--joy-palette-primary-800) 18%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="mb-8 space-y-2">
|
<div className="mb-8 space-y-2">
|
||||||
<p className="text-xs font-semibold uppercase tracking-[0.3em] text-[#0b6bcb]">
|
<p
|
||||||
|
className="text-xs font-semibold uppercase tracking-[0.3em]"
|
||||||
|
style={{ color: "var(--joy-palette-primary-solidBg)" }}
|
||||||
|
>
|
||||||
Stockhome
|
Stockhome
|
||||||
</p>
|
</p>
|
||||||
<h1 className="text-3xl font-semibold text-slate-900">
|
<h1
|
||||||
|
className="text-3xl font-semibold"
|
||||||
|
style={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("login")}
|
{t("login")}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,7 +121,11 @@ export const LoginCard = () => {
|
|||||||
placeholder={t("username")}
|
placeholder={t("username")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -114,7 +138,11 @@ export const LoginCard = () => {
|
|||||||
placeholder={t("password")}
|
placeholder={t("password")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -122,7 +150,13 @@ export const LoginCard = () => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="w-full rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
className="w-full rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("login")}
|
{t("login")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ export const NotFound = () => {
|
|||||||
<Typography color={"primary"} level={"h1"}>
|
<Typography color={"primary"} level={"h1"}>
|
||||||
{t("not-found-header")}
|
{t("not-found-header")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography className={"text-slate-900"} level={"body-lg"}>
|
<Typography
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
level={"body-lg"}
|
||||||
|
>
|
||||||
{t("not-found-body")}
|
{t("not-found-body")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,20 +7,23 @@ import StorageIcon from "@mui/icons-material/Storage";
|
|||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
|
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
|
||||||
import TranslateIcon from "@mui/icons-material/Translate";
|
import TranslateIcon from "@mui/icons-material/Translate";
|
||||||
|
import Brightness4Icon from "@mui/icons-material/Brightness4";
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import CloseIcon from "@mui/icons-material/Close";
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
import { useMatchRoute, useNavigate } from "@tanstack/react-router";
|
import { useMatchRoute, useNavigate } from "@tanstack/react-router";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { changeTranslation } from "../utils/uxFncs";
|
import { changeTranslation } from "../utils/uxFncs";
|
||||||
|
import { useColorScheme } from "@mui/joy/styles";
|
||||||
|
|
||||||
export const Sidebar = () => {
|
export const Sidebar = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const matchRoute = useMatchRoute();
|
const matchRoute = useMatchRoute();
|
||||||
|
const { mode, setMode } = useColorScheme();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const btnClass =
|
const btnClass =
|
||||||
"h-11 w-full justify-start! rounded-2xl px-4 text-left text-sm font-semibold text-slate-700 transition hover:bg-white/80 hover:text-[#0b6bcb] [&_.MuiButton-startDecorator]:mr-3! [&_.MuiButton-startDecorator]:ml-0!";
|
"h-11 w-full justify-start! rounded-2xl px-4 text-left text-sm font-semibold transition [&_.MuiButton-startDecorator]:mr-3! [&_.MuiButton-startDecorator]:ml-0!";
|
||||||
|
|
||||||
const variant = (to: string) =>
|
const variant = (to: string) =>
|
||||||
!!matchRoute({ to, fuzzy: false }) ? "soft" : "plain";
|
!!matchRoute({ to, fuzzy: false }) ? "soft" : "plain";
|
||||||
@@ -31,18 +34,30 @@ export const Sidebar = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="flex w-full flex-col gap-6 border-b border-white/80 bg-linear-to-b from-[#f7fbff] via-[#f2f6fb] to-[#eef3f9] px-4 py-5 shadow-[0_20px_60px_rgba(11,107,203,0.08)] sm:px-5 lg:h-full lg:min-h-screen lg:max-w-70 lg:border-b-0 lg:border-r lg:px-6 lg:py-8">
|
<aside
|
||||||
|
className="flex w-full flex-col gap-6 px-4 py-5 sm:px-5 lg:h-full lg:min-h-screen lg:max-w-70 lg:border-b-0 lg:border-r lg:px-6 lg:py-8"
|
||||||
|
style={{
|
||||||
|
borderBottom: "1px solid var(--joy-palette-divider)",
|
||||||
|
borderRightColor: "var(--joy-palette-divider)",
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom, var(--joy-palette-primary-50), var(--joy-palette-background-level1), var(--joy-palette-background-level2))",
|
||||||
|
boxShadow:
|
||||||
|
"0 20px 60px color-mix(in srgb, var(--joy-palette-primary-solidBg) 8%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Typography
|
<Typography
|
||||||
level="h2"
|
level="h2"
|
||||||
className="text-[22px] font-semibold text-[#0b6bcb]"
|
className="text-[22px] font-semibold"
|
||||||
|
sx={{ color: "var(--joy-palette-primary-solidBg)" }}
|
||||||
>
|
>
|
||||||
{t("app-title")}
|
{t("app-title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
level="body-lg"
|
level="body-lg"
|
||||||
className="text-sm font-medium text-slate-500"
|
className="text-sm font-medium"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
>
|
>
|
||||||
{Cookies.get("app-name") ? Cookies.get("app-name") : ""}
|
{Cookies.get("app-name") ? Cookies.get("app-name") : ""}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -83,6 +98,13 @@ export const Sidebar = () => {
|
|||||||
variant={variant("/app/inventory")}
|
variant={variant("/app/inventory")}
|
||||||
startDecorator={<InventoryIcon />}
|
startDecorator={<InventoryIcon />}
|
||||||
className={btnClass}
|
className={btnClass}
|
||||||
|
sx={{
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
color: "var(--joy-palette-primary-solidBg)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("inventory")}
|
{t("inventory")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -91,6 +113,13 @@ export const Sidebar = () => {
|
|||||||
variant={variant("/app/add-product")}
|
variant={variant("/app/add-product")}
|
||||||
startDecorator={<AddBoxIcon />}
|
startDecorator={<AddBoxIcon />}
|
||||||
className={btnClass}
|
className={btnClass}
|
||||||
|
sx={{
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
color: "var(--joy-palette-primary-solidBg)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("add")}
|
{t("add")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -99,6 +128,13 @@ export const Sidebar = () => {
|
|||||||
variant={variant("/app/storages")}
|
variant={variant("/app/storages")}
|
||||||
startDecorator={<StorageIcon />}
|
startDecorator={<StorageIcon />}
|
||||||
className={btnClass}
|
className={btnClass}
|
||||||
|
sx={{
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
color: "var(--joy-palette-primary-solidBg)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("storages")}
|
{t("storages")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -107,6 +143,13 @@ export const Sidebar = () => {
|
|||||||
variant={variant("/app/app-settings")}
|
variant={variant("/app/app-settings")}
|
||||||
startDecorator={<SettingsIcon />}
|
startDecorator={<SettingsIcon />}
|
||||||
className={btnClass}
|
className={btnClass}
|
||||||
|
sx={{
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
"&:hover": {
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
color: "var(--joy-palette-primary-solidBg)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("settings")}
|
{t("settings")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -132,9 +175,25 @@ export const Sidebar = () => {
|
|||||||
>
|
>
|
||||||
{t("change-translation")}
|
{t("change-translation")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => setMode(mode === "dark" ? "light" : "dark")}
|
||||||
|
color="neutral"
|
||||||
|
startDecorator={<Brightness4Icon />}
|
||||||
|
className={btnClass}
|
||||||
|
>
|
||||||
|
{mode === "dark" ? "Light Mode" : "Dark Mode"}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-3 rounded-2xl border border-white/70 bg-white/80 px-4 py-3 text-xs font-semibold uppercase tracking-[0.2em] text-[#0b6bcb] shadow-[0_12px_30px_rgba(12,38,78,0.12)]">
|
<div
|
||||||
|
className="flex items-center gap-3 rounded-2xl px-4 py-3 text-xs font-semibold uppercase tracking-[0.2em]"
|
||||||
|
style={{
|
||||||
|
border: "1px solid var(--joy-palette-divider)",
|
||||||
|
backgroundColor: "var(--joy-palette-background-surface)",
|
||||||
|
color: "var(--joy-palette-primary-solidBg)",
|
||||||
|
boxShadow: "0 12px 30px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="/favicon.png"
|
src="/favicon.png"
|
||||||
alt="Stockhome"
|
alt="Stockhome"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const StorageRow = ({ storage, onError }: StorageRowProps) => {
|
|||||||
(values.description ?? "") !== (storage.description ?? "");
|
(values.description ?? "") !== (storage.description ?? "");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr key={storage.uuid} className="border-t border-slate-200 align-top">
|
<tr key={storage.uuid} className="align-top">
|
||||||
<td className="px-6 py-5">
|
<td className="px-6 py-5">
|
||||||
<form.Field name="name">
|
<form.Field name="name">
|
||||||
{(field) => (
|
{(field) => (
|
||||||
@@ -60,7 +60,12 @@ export const StorageRow = ({ storage, onError }: StorageRowProps) => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-xl bg-slate-50/80 text-slate-700 shadow-none ring-1 ring-inset ring-slate-200 focus-within:ring-2 focus-within:ring-slate-300"
|
className="rounded-xl shadow-none"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-level1)",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
"--Input-focusedHighlight": "var(--joy-palette-neutral-300)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -74,15 +79,26 @@ export const StorageRow = ({ storage, onError }: StorageRowProps) => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-xl bg-slate-50/80 text-slate-700 shadow-none ring-1 ring-inset ring-slate-200 focus-within:ring-2 focus-within:ring-slate-300"
|
className="rounded-xl shadow-none"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-level1)",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
"--Input-focusedHighlight": "var(--joy-palette-neutral-300)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-5 text-sm text-slate-500">
|
<td
|
||||||
|
className="px-6 py-5 text-sm"
|
||||||
|
style={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{formatDate(storage.created_at)}
|
{formatDate(storage.created_at)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-5 text-sm text-slate-500">
|
<td
|
||||||
|
className="px-6 py-5 text-sm"
|
||||||
|
style={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{formatDate(storage.updated_at)}
|
{formatDate(storage.updated_at)}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-6 py-5 text-right">
|
<td className="px-6 py-5 text-right">
|
||||||
|
|||||||
@@ -51,11 +51,20 @@ export const AddStorageModal = (props: AddStorageModalProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal open={props.isOpen} onClose={() => props.setOpen(false)}>
|
<Modal open={props.isOpen} onClose={() => props.setOpen(false)}>
|
||||||
<ModalDialog className="rounded-3xl border border-white/70 bg-white/90 p-6 shadow-[0_30px_70px_rgba(12,38,78,0.2)] backdrop-blur">
|
<ModalDialog
|
||||||
<DialogTitle className="text-slate-900">
|
className="rounded-3xl p-6 backdrop-blur"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
boxShadow:
|
||||||
|
"0 30px 70px color-mix(in srgb, var(--joy-palette-primary-800) 20%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ color: "var(--joy-palette-text-primary)" }}>
|
||||||
{t("new-storage-title")}
|
{t("new-storage-title")}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent className="text-slate-500">
|
<DialogContent sx={{ color: "var(--joy-palette-text-tertiary)" }}>
|
||||||
{t("new-storage-content")}
|
{t("new-storage-content")}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<form
|
<form
|
||||||
@@ -73,7 +82,11 @@ export const AddStorageModal = (props: AddStorageModalProps) => {
|
|||||||
placeholder={t("storage-name")}
|
placeholder={t("storage-name")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -85,14 +98,24 @@ export const AddStorageModal = (props: AddStorageModalProps) => {
|
|||||||
placeholder={t("description")}
|
placeholder={t("description")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("submit")}
|
{t("submit")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { Button, DialogTitle, Input, Modal, ModalDialog, Stack, } from "@mui/joy";
|
||||||
Button,
|
|
||||||
DialogTitle,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalDialog,
|
|
||||||
Stack,
|
|
||||||
} from "@mui/joy";
|
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { useMutation } from "@tanstack/react-query";
|
import { useMutation } from "@tanstack/react-query";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -66,7 +59,6 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Sets the success alert in the settings page via component props
|
|
||||||
props.alert({
|
props.alert({
|
||||||
isAlert: true,
|
isAlert: true,
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -74,7 +66,6 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
text: t("SU005"),
|
text: t("SU005"),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sets the success alert locally (in the modal itself)
|
|
||||||
setAlert({
|
setAlert({
|
||||||
isAlert: true,
|
isAlert: true,
|
||||||
type: "success",
|
type: "success",
|
||||||
@@ -88,8 +79,17 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal open={props.isOpen} onClose={() => props.setOpen(false)}>
|
<Modal open={props.isOpen} onClose={() => props.setOpen(false)}>
|
||||||
<ModalDialog className="rounded-3xl border border-white/70 bg-white/90 p-6 shadow-[0_30px_70px_rgba(12,38,78,0.2)] backdrop-blur">
|
<ModalDialog
|
||||||
<DialogTitle className="text-slate-900">
|
className="rounded-3xl p-6 backdrop-blur"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
boxShadow:
|
||||||
|
"0 30px 70px color-mix(in srgb, var(--joy-palette-primary-800) 20%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogTitle sx={{ color: "var(--joy-palette-text-primary)" }}>
|
||||||
{t("new-password-title")}
|
{t("new-password-title")}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<form
|
<form
|
||||||
@@ -108,7 +108,11 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
placeholder={t("current-password")}
|
placeholder={t("current-password")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -121,7 +125,11 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
placeholder={t("new-password")}
|
placeholder={t("new-password")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -134,14 +142,24 @@ export const ChangePasswordModal = (props: ChangePasswordProps) => {
|
|||||||
placeholder={t("new-password-rep")}
|
placeholder={t("new-password-rep")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("change")}
|
{t("change")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -90,10 +90,16 @@ export const AddProduct = () => {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="h2" className="text-slate-900">
|
<Typography
|
||||||
|
level="h2"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("add-product")}
|
{t("add-product")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography level="body-lg" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-lg"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("add-product-subtitle")}
|
{t("add-product-subtitle")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,7 +112,16 @@ export const AddProduct = () => {
|
|||||||
</Chip>
|
</Chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Box className="mt-6 rounded-3xl border border-white/70 bg-white/80 p-6 shadow-[0_24px_60px_rgba(12,38,78,0.12)] backdrop-blur">
|
<Box
|
||||||
|
className="mt-6 rounded-3xl p-6 backdrop-blur"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
boxShadow:
|
||||||
|
"0 24px 60px color-mix(in srgb, var(--joy-palette-primary-800) 12%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<form
|
<form
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
@@ -117,7 +132,10 @@ export const AddProduct = () => {
|
|||||||
<div className="grid gap-5 lg:grid-cols-[1.2fr_1fr]">
|
<div className="grid gap-5 lg:grid-cols-[1.2fr_1fr]">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("product-name")}
|
{t("product-name")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="name">
|
<form.Field name="name">
|
||||||
@@ -130,13 +148,20 @@ export const AddProduct = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("description")}
|
{t("description")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="description">
|
<form.Field name="description">
|
||||||
@@ -148,14 +173,21 @@ export const AddProduct = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("expiry-date")}
|
{t("expiry-date")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="expiry_date">
|
<form.Field name="expiry_date">
|
||||||
@@ -167,13 +199,19 @@ export const AddProduct = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("bottling-date")}
|
{t("bottling-date")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="bottling_date">
|
<form.Field name="bottling_date">
|
||||||
@@ -185,7 +223,10 @@ export const AddProduct = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -193,14 +234,28 @@ export const AddProduct = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-2xl border border-white/70 bg-linear-to-br from-[#f7fbff] via-[#f2f6fb] to-[#eef3f9] p-5 shadow-[0_16px_40px_rgba(12,38,78,0.08)]">
|
<div
|
||||||
<Typography level="title-md" className="text-slate-900">
|
className="rounded-2xl p-5"
|
||||||
|
style={{
|
||||||
|
border: "1px solid var(--joy-palette-divider)",
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom right, var(--joy-palette-primary-50), var(--joy-palette-background-level1), var(--joy-palette-background-level2))",
|
||||||
|
boxShadow: "0 16px 40px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("inventory")}
|
{t("inventory")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider className="my-3" />
|
<Divider className="my-3" />
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("amount")}
|
{t("amount")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="amount">
|
<form.Field name="amount">
|
||||||
@@ -220,13 +275,19 @@ export const AddProduct = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
className="rounded-2xl bg-white/80"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("price")}
|
{t("price")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="price">
|
<form.Field name="price">
|
||||||
@@ -238,16 +299,25 @@ export const AddProduct = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{Cookies.get("currency")}
|
{Cookies.get("currency")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("storage-place")}
|
{t("storage-place")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="storage_location_uuid">
|
<form.Field name="storage_location_uuid">
|
||||||
@@ -260,7 +330,10 @@ export const AddProduct = () => {
|
|||||||
}
|
}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{storages?.map((storage) => (
|
{storages?.map((storage) => (
|
||||||
<Option key={storage.uuid} value={storage.uuid}>
|
<Option key={storage.uuid} value={storage.uuid}>
|
||||||
@@ -276,7 +349,10 @@ export const AddProduct = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("product-details")}
|
{t("product-details")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className="grow"></div>
|
<div className="grow"></div>
|
||||||
@@ -284,7 +360,13 @@ export const AddProduct = () => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("save")}
|
{t("save")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -124,9 +124,20 @@ export const InventoryPage = () => {
|
|||||||
|
|
||||||
<Sheet
|
<Sheet
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="mt-6 flex min-h-0 w-full max-w-full flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white/80 shadow-sm sm:h-[calc(100vh-260px)]"
|
className="mt-6 flex min-h-0 w-full max-w-full flex-col overflow-hidden rounded-2xl shadow-sm sm:h-[calc(100vh-260px)]"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between border-b border-slate-200 px-6 py-4 text-slate-700">
|
<div
|
||||||
|
className="flex items-center justify-between px-6 py-4"
|
||||||
|
style={{
|
||||||
|
borderBottom: "1px solid var(--joy-palette-divider)",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography level="body-lg" fontWeight="bold">
|
<Typography level="body-lg" fontWeight="bold">
|
||||||
{t("inventory")}
|
{t("inventory")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -147,24 +158,26 @@ export const InventoryPage = () => {
|
|||||||
stripe="odd"
|
stripe="odd"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
hoverRow
|
hoverRow
|
||||||
className="w-full text-slate-700"
|
className="w-full"
|
||||||
sx={{
|
sx={{
|
||||||
tableLayout: "fixed",
|
tableLayout: "fixed",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
"--TableCell-headBackground":
|
"--TableCell-headBackground":
|
||||||
"var(--joy-palette-background-surface)",
|
"var(--joy-palette-background-level1)",
|
||||||
"& thead": {
|
"& thead": {
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 3,
|
zIndex: 3,
|
||||||
backgroundColor: "rgb(248 250 252)",
|
backgroundColor: "var(--joy-palette-background-level1)",
|
||||||
},
|
},
|
||||||
"& thead tr": {
|
"& thead tr": {
|
||||||
backgroundColor: "rgb(248 250 252)",
|
backgroundColor: "var(--joy-palette-background-level1)",
|
||||||
},
|
},
|
||||||
"& thead th": {
|
"& thead th": {
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
backgroundColor: "rgb(248 250 252)",
|
backgroundColor: "var(--joy-palette-background-level1)",
|
||||||
backgroundImage: "none",
|
backgroundImage: "none",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
},
|
},
|
||||||
"& thead th:nth-child(1)": {
|
"& thead th:nth-child(1)": {
|
||||||
width: "44px",
|
width: "44px",
|
||||||
@@ -198,10 +211,13 @@ export const InventoryPage = () => {
|
|||||||
minWidth: "100px",
|
minWidth: "100px",
|
||||||
},
|
},
|
||||||
"& tr > *:nth-child(n+4)": { textAlign: "left" },
|
"& tr > *:nth-child(n+4)": { textAlign: "left" },
|
||||||
|
"& tbody tr": {
|
||||||
|
borderTop: "1px solid var(--joy-palette-divider)",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="text-slate-600">
|
<tr>
|
||||||
<th className="px-2 py-4">
|
<th className="px-2 py-4">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={rows.length > 0 && selected.length === rows.length}
|
checked={rows.length > 0 && selected.length === rows.length}
|
||||||
@@ -229,7 +245,6 @@ export const InventoryPage = () => {
|
|||||||
return (
|
return (
|
||||||
<tr
|
<tr
|
||||||
key={row.id}
|
key={row.id}
|
||||||
className="border-t border-slate-200"
|
|
||||||
onClick={(event) => handleClick(event, row.id)}
|
onClick={(event) => handleClick(event, row.id)}
|
||||||
role="checkbox"
|
role="checkbox"
|
||||||
aria-checked={isItemSelected}
|
aria-checked={isItemSelected}
|
||||||
@@ -254,13 +269,15 @@ export const InventoryPage = () => {
|
|||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<Typography
|
<Typography
|
||||||
level="title-md"
|
level="title-md"
|
||||||
className="text-slate-900 truncate"
|
className="truncate"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
>
|
>
|
||||||
{row.name}
|
{row.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
level="body-sm"
|
level="body-sm"
|
||||||
className="text-slate-500 truncate"
|
className="truncate"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
>
|
>
|
||||||
{row.description}
|
{row.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -273,7 +290,8 @@ export const InventoryPage = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
level="body-sm"
|
level="body-sm"
|
||||||
className="truncate text-slate-400"
|
className="truncate"
|
||||||
|
sx={{ color: "var(--joy-palette-neutral-400)" }}
|
||||||
>
|
>
|
||||||
{Cookies.get("currency")}
|
{Cookies.get("currency")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -294,7 +312,8 @@ export const InventoryPage = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
level="body-sm"
|
level="body-sm"
|
||||||
className="truncate text-slate-500"
|
className="truncate"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
>
|
>
|
||||||
{row.locationDetail}
|
{row.locationDetail}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { getStorages } from "../utils/api/storages.ts";
|
import { getStorages } from "../utils/api/storages.ts";
|
||||||
import {
|
import { Box, Button, Chip, CircularProgress, Divider, Input, Option, Select, Typography, } from "@mui/joy";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Chip,
|
|
||||||
CircularProgress,
|
|
||||||
Divider,
|
|
||||||
Input,
|
|
||||||
Option,
|
|
||||||
Select,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/joy";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { getProductDetails, mutateProduct } from "../utils/api/products.ts";
|
import { getProductDetails, mutateProduct } from "../utils/api/products.ts";
|
||||||
import { toInputDate } from "../utils/uxFncs";
|
import { toInputDate } from "../utils/uxFncs";
|
||||||
import type {
|
import type { AlertInterface, productDetailsInterface, ProductFormValues, Storage, } from "../misc/interfaces";
|
||||||
AlertInterface,
|
|
||||||
productDetailsInterface,
|
|
||||||
ProductFormValues,
|
|
||||||
Storage,
|
|
||||||
} from "../misc/interfaces";
|
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import ElectricBoltIcon from "@mui/icons-material/ElectricBolt";
|
import ElectricBoltIcon from "@mui/icons-material/ElectricBolt";
|
||||||
@@ -140,7 +125,11 @@ export const ProductQuickView = () => {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="h2" fontWeight="1000" className="text-slate-900">
|
<Typography
|
||||||
|
level="h2"
|
||||||
|
fontWeight="1000"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
<ElectricBoltIcon color="primary" />
|
<ElectricBoltIcon color="primary" />
|
||||||
{t("product-details-quick")}
|
{t("product-details-quick")}
|
||||||
<ElectricBoltIcon color="primary" />
|
<ElectricBoltIcon color="primary" />
|
||||||
@@ -157,7 +146,16 @@ export const ProductQuickView = () => {
|
|||||||
{productDetailsLoading && <CircularProgress size="sm" />}
|
{productDetailsLoading && <CircularProgress size="sm" />}
|
||||||
</div>
|
</div>
|
||||||
{isSuccess && (
|
{isSuccess && (
|
||||||
<Box className="mt-6 rounded-3xl border border-white/70 bg-white/80 p-6 shadow-[0_24px_60px_rgba(12,38,78,0.12)] backdrop-blur">
|
<Box
|
||||||
|
className="mt-6 rounded-3xl p-6 backdrop-blur"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
boxShadow:
|
||||||
|
"0 24px 60px color-mix(in srgb, var(--joy-palette-primary-800) 12%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<form
|
<form
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
@@ -168,7 +166,10 @@ export const ProductQuickView = () => {
|
|||||||
<div className="gap-5 lg:grid-cols-[1.2fr_1fr]">
|
<div className="gap-5 lg:grid-cols-[1.2fr_1fr]">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("product-name")}
|
{t("product-name")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="name">
|
<form.Field name="name">
|
||||||
@@ -180,13 +181,20 @@ export const ProductQuickView = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("description")}
|
{t("description")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="description">
|
<form.Field name="description">
|
||||||
@@ -198,14 +206,21 @@ export const ProductQuickView = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("expiry-date")}
|
{t("expiry-date")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="expiry_date">
|
<form.Field name="expiry_date">
|
||||||
@@ -217,13 +232,19 @@ export const ProductQuickView = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("bottling-date")}
|
{t("bottling-date")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="bottling_date">
|
<form.Field name="bottling_date">
|
||||||
@@ -235,7 +256,10 @@ export const ProductQuickView = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -243,14 +267,28 @@ export const ProductQuickView = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-2xl border border-white/70 bg-linear-to-br from-[#f7fbff] via-[#f2f6fb] to-[#eef3f9] p-5 shadow-[0_16px_40px_rgba(12,38,78,0.08)]">
|
<div
|
||||||
<Typography level="title-lg" className="text-slate-900">
|
className="rounded-2xl p-5"
|
||||||
|
style={{
|
||||||
|
border: "1px solid var(--joy-palette-divider)",
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom right, var(--joy-palette-primary-50), var(--joy-palette-background-level1), var(--joy-palette-background-level2))",
|
||||||
|
boxShadow: "0 16px 40px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
level="title-lg"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("inventory")}
|
{t("inventory")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider className="my-3" />
|
<Divider className="my-3" />
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("amount")}
|
{t("amount")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="amount">
|
<form.Field name="amount">
|
||||||
@@ -270,13 +308,19 @@ export const ProductQuickView = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
className="rounded-2xl bg-white/80"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("price")}
|
{t("price")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="price">
|
<form.Field name="price">
|
||||||
@@ -288,16 +332,25 @@ export const ProductQuickView = () => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{Cookies.get("currency")}
|
{Cookies.get("currency")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("storage-place")}
|
{t("storage-place")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="storage_location_uuid">
|
<form.Field name="storage_location_uuid">
|
||||||
@@ -309,7 +362,10 @@ export const ProductQuickView = () => {
|
|||||||
}
|
}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{storages?.map((storage) => (
|
{storages?.map((storage) => (
|
||||||
<Option key={storage.uuid} value={storage.uuid}>
|
<Option key={storage.uuid} value={storage.uuid}>
|
||||||
@@ -325,14 +381,23 @@ export const ProductQuickView = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("product-details")}
|
{t("product-details")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("save")}
|
{t("save")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -91,10 +91,16 @@ export const Settings = () => {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="h2" className="text-slate-900">
|
<Typography
|
||||||
|
level="h2"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("settings")}
|
{t("settings")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography level="body-lg" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-lg"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("settings-sub")}
|
{t("settings-sub")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,7 +114,16 @@ export const Settings = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Sheet className="mt-6 rounded-3xl border border-white/70 bg-white/80 p-6 shadow-[0_24px_60px_rgba(12,38,78,0.12)] backdrop-blur">
|
<Sheet
|
||||||
|
className="mt-6 rounded-3xl p-6 backdrop-blur"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
boxShadow:
|
||||||
|
"0 24px 60px rgba(var(--joy-palette-primary-900, 12 38 78) / 0.12)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{alert.isAlert && (
|
{alert.isAlert && (
|
||||||
<MyAlert type={alert.type} header={alert.header} text={alert.text} />
|
<MyAlert type={alert.type} header={alert.header} text={alert.text} />
|
||||||
)}
|
)}
|
||||||
@@ -124,13 +139,19 @@ export const Settings = () => {
|
|||||||
form.handleSubmit();
|
form.handleSubmit();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="grid gap-6 lg:grid-cols-[1.1fr_0.9fr]">
|
<div className="grid gap-6 lg:grid-cols-[1.1fr\_0.9fr]">
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("app-name")}
|
{t("app-name")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("app-name-sub")}
|
{t("app-name-sub")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="app-name">
|
<form.Field name="app-name">
|
||||||
@@ -141,16 +162,26 @@ export const Settings = () => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
placeholder="Stockhome"
|
placeholder="Stockhome"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("currency")}
|
{t("currency")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("currency-sub")}
|
{t("currency-sub")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="currency">
|
<form.Field name="currency">
|
||||||
@@ -161,18 +192,36 @@ export const Settings = () => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
placeholder="EUR"
|
placeholder="EUR"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-2xl border border-white/70 bg-linear-to-br from-[#f7fbff] via-[#f2f6fb] to-[#eef3f9] p-5 shadow-[0_16px_40px_rgba(12,38,78,0.08)]">
|
<div
|
||||||
<Typography level="title-lg" className="text-slate-900">
|
className="rounded-2xl p-5"
|
||||||
|
style={{
|
||||||
|
border: "1px solid var(--joy-palette-divider)",
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom right, var(--joy-palette-primary-50), var(--joy-palette-background-level1), var(--joy-palette-background-level2))",
|
||||||
|
boxShadow: "0 16px 40px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
level="title-lg"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("quick-tips")}
|
{t("quick-tips")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider className="my-3" />
|
<Divider className="my-3" />
|
||||||
<div className="space-y-3 text-md text-slate-600">
|
<div
|
||||||
|
className="space-y-3 text-md"
|
||||||
|
style={{ color: "var(--joy-palette-text-secondary)" }}
|
||||||
|
>
|
||||||
<p>{t("quick-tips-1")}</p>
|
<p>{t("quick-tips-1")}</p>
|
||||||
<p>{t("quick-tips-2")}</p>
|
<p>{t("quick-tips-2")}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -183,15 +232,23 @@ export const Settings = () => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
size="lg"
|
size="lg"
|
||||||
color="primary"
|
color="primary"
|
||||||
className="rounded-2xl text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("save")}
|
{t("save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setModal(true)}
|
onClick={() => setModal(true)}
|
||||||
size="lg"
|
size="lg"
|
||||||
color="warning"
|
color="primary"
|
||||||
className="rounded-2xl text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("change-password")}
|
{t("change-password")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -50,10 +50,16 @@ export const Storages = () => {
|
|||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="min-w-65 space-y-2">
|
<div className="min-w-65 space-y-2">
|
||||||
<Typography level="h2" className="text-slate-900">
|
<Typography
|
||||||
|
level="h2"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("storages")}
|
{t("storages")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography level="body-lg" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-lg"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("storage-delete-info")}
|
{t("storage-delete-info")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +80,12 @@ export const Storages = () => {
|
|||||||
|
|
||||||
<Sheet
|
<Sheet
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="mt-6 flex min-h-0 w-full max-w-full flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white/80 shadow-sm sm:h-[calc(100vh-260px)]"
|
className="mt-6 flex min-h-0 w-full max-w-full flex-col overflow-hidden rounded-2xl shadow-sm sm:h-[calc(100vh-260px)]"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<AddStorageModal isOpen={modal} setOpen={setModal} />
|
<AddStorageModal isOpen={modal} setOpen={setModal} />
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -83,7 +94,13 @@ export const Storages = () => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between border-b border-slate-200 px-6 py-4 text-slate-700">
|
<div
|
||||||
|
className="flex items-center justify-between px-6 py-4"
|
||||||
|
style={{
|
||||||
|
borderBottom: "1px solid var(--joy-palette-divider)",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Typography level="body-lg" fontWeight="bold">
|
<Typography level="body-lg" fontWeight="bold">
|
||||||
{t("storages")}
|
{t("storages")}
|
||||||
</Typography>
|
</Typography>
|
||||||
@@ -94,24 +111,26 @@ export const Storages = () => {
|
|||||||
stripe="odd"
|
stripe="odd"
|
||||||
variant="plain"
|
variant="plain"
|
||||||
hoverRow
|
hoverRow
|
||||||
className="w-full text-slate-700"
|
className="w-full"
|
||||||
sx={{
|
sx={{
|
||||||
tableLayout: "fixed",
|
tableLayout: "fixed",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
"--TableCell-headBackground":
|
"--TableCell-headBackground":
|
||||||
"var(--joy-palette-background-surface)",
|
"var(--joy-palette-background-level1)",
|
||||||
"& thead": {
|
"& thead": {
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 3,
|
zIndex: 3,
|
||||||
backgroundColor: "rgb(248 250 252)",
|
backgroundColor: "var(--joy-palette-background-level1)",
|
||||||
},
|
},
|
||||||
"& thead tr": {
|
"& thead tr": {
|
||||||
backgroundColor: "rgb(248 250 252)",
|
backgroundColor: "var(--joy-palette-background-level1)",
|
||||||
},
|
},
|
||||||
"& thead th": {
|
"& thead th": {
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
backgroundColor: "rgb(248 250 252)",
|
backgroundColor: "var(--joy-palette-background-level1)",
|
||||||
backgroundImage: "none",
|
backgroundImage: "none",
|
||||||
|
color: "var(--joy-palette-text-secondary)",
|
||||||
},
|
},
|
||||||
"& thead th:nth-child(1)": {
|
"& thead th:nth-child(1)": {
|
||||||
width: "26%",
|
width: "26%",
|
||||||
@@ -134,10 +153,13 @@ export const Storages = () => {
|
|||||||
minWidth: "80px",
|
minWidth: "80px",
|
||||||
},
|
},
|
||||||
"& tr > *:nth-child(n+3)": { textAlign: "left" },
|
"& tr > *:nth-child(n+3)": { textAlign: "left" },
|
||||||
|
"& tbody tr": {
|
||||||
|
borderTop: "1px solid var(--joy-palette-divider)",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="text-slate-600">
|
<tr>
|
||||||
<th className="px-3 py-4">{t("storage-name")}</th>
|
<th className="px-3 py-4">{t("storage-name")}</th>
|
||||||
<th className="px-3 py-4">{t("description")}</th>
|
<th className="px-3 py-4">{t("description")}</th>
|
||||||
<th className="px-3 py-4">{t("created-at")}</th>
|
<th className="px-3 py-4">{t("created-at")}</th>
|
||||||
|
|||||||
@@ -1,27 +1,12 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { getStorages } from "../utils/api/storages.ts";
|
import { getStorages } from "../utils/api/storages.ts";
|
||||||
import {
|
import { Box, Button, Chip, CircularProgress, Divider, Input, Option, Select, Typography, } from "@mui/joy";
|
||||||
Box,
|
|
||||||
Button,
|
|
||||||
Chip,
|
|
||||||
CircularProgress,
|
|
||||||
Divider,
|
|
||||||
Input,
|
|
||||||
Option,
|
|
||||||
Select,
|
|
||||||
Typography,
|
|
||||||
} from "@mui/joy";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "@tanstack/react-form";
|
import { useForm } from "@tanstack/react-form";
|
||||||
import { getProductDetails, mutateProduct } from "../utils/api/products.ts";
|
import { getProductDetails, mutateProduct } from "../utils/api/products.ts";
|
||||||
import { toInputDate } from "../utils/uxFncs";
|
import { toInputDate } from "../utils/uxFncs";
|
||||||
import type {
|
import type { AlertInterface, productDetailsInterface, ProductFormValues, Storage, } from "../misc/interfaces";
|
||||||
AlertInterface,
|
|
||||||
productDetailsInterface,
|
|
||||||
ProductFormValues,
|
|
||||||
Storage,
|
|
||||||
} from "../misc/interfaces";
|
|
||||||
import type { ApiError } from "../utils/api/apiError";
|
import type { ApiError } from "../utils/api/apiError";
|
||||||
import QrCodeIcon from "@mui/icons-material/QrCode";
|
import QrCodeIcon from "@mui/icons-material/QrCode";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
@@ -160,7 +145,10 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-wrap items-center gap-3">
|
<div className="flex flex-wrap items-center gap-3">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="h2" className="text-slate-900">
|
<Typography
|
||||||
|
level="h2"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("product-details")}
|
{t("product-details")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
@@ -175,7 +163,16 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
{productDetailsLoading && <CircularProgress size="sm" />}
|
{productDetailsLoading && <CircularProgress size="sm" />}
|
||||||
</div>
|
</div>
|
||||||
{isSuccess && (
|
{isSuccess && (
|
||||||
<Box className="mt-6 rounded-3xl border border-white/70 bg-white/80 p-6 shadow-[0_24px_60px_rgba(12,38,78,0.12)] backdrop-blur">
|
<Box
|
||||||
|
className="mt-6 rounded-3xl p-6 backdrop-blur"
|
||||||
|
sx={{
|
||||||
|
border: "1px solid",
|
||||||
|
borderColor: "divider",
|
||||||
|
bgcolor: "background.surface",
|
||||||
|
boxShadow:
|
||||||
|
"0 24px 60px color-mix(in srgb, var(--joy-palette-primary-800) 12%, transparent)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
<form
|
<form
|
||||||
className="space-y-6"
|
className="space-y-6"
|
||||||
onSubmit={(e) => {
|
onSubmit={(e) => {
|
||||||
@@ -186,7 +183,10 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
<div className="grid gap-5 lg:grid-cols-[1.2fr_1fr]">
|
<div className="grid gap-5 lg:grid-cols-[1.2fr_1fr]">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("product-name")}
|
{t("product-name")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="name">
|
<form.Field name="name">
|
||||||
@@ -198,13 +198,20 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("description")}
|
{t("description")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="description">
|
<form.Field name="description">
|
||||||
@@ -216,14 +223,21 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90 shadow-[0_10px_24px_rgba(15,23,42,0.08)]"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
boxShadow: "0 10px 24px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 md:grid-cols-2">
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("expiry-date")}
|
{t("expiry-date")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="expiry_date">
|
<form.Field name="expiry_date">
|
||||||
@@ -235,13 +249,19 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("bottling-date")}
|
{t("bottling-date")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="bottling_date">
|
<form.Field name="bottling_date">
|
||||||
@@ -253,7 +273,10 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
@@ -261,14 +284,28 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-2xl border border-white/70 bg-linear-to-br from-[#f7fbff] via-[#f2f6fb] to-[#eef3f9] p-5 shadow-[0_16px_40px_rgba(12,38,78,0.08)]">
|
<div
|
||||||
<Typography level="title-lg" className="text-slate-900">
|
className="rounded-2xl p-5"
|
||||||
|
style={{
|
||||||
|
border: "1px solid var(--joy-palette-divider)",
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom right, var(--joy-palette-primary-50), var(--joy-palette-background-level1), var(--joy-palette-background-level2))",
|
||||||
|
boxShadow: "0 16px 40px var(--joy-palette-divider)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
level="title-lg"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("inventory")}
|
{t("inventory")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider className="my-3" />
|
<Divider className="my-3" />
|
||||||
<div className="grid gap-4">
|
<div className="grid gap-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("amount")}
|
{t("amount")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="amount">
|
<form.Field name="amount">
|
||||||
@@ -288,13 +325,19 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
className="rounded-2xl bg-white/80"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("price")}
|
{t("price")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="price">
|
<form.Field name="price">
|
||||||
@@ -306,16 +349,25 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
onBlur={field.handleBlur}
|
onBlur={field.handleBlur}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</form.Field>
|
</form.Field>
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{Cookies.get("currency")}
|
{Cookies.get("currency")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Typography level="title-md" className="text-slate-900">
|
<Typography
|
||||||
|
level="title-md"
|
||||||
|
sx={{ color: "var(--joy-palette-text-primary)" }}
|
||||||
|
>
|
||||||
{t("storage-place")}
|
{t("storage-place")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form.Field name="storage_location_uuid">
|
<form.Field name="storage_location_uuid">
|
||||||
@@ -327,7 +379,10 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
}
|
}
|
||||||
size="lg"
|
size="lg"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="rounded-2xl bg-white/90"
|
className="rounded-2xl"
|
||||||
|
sx={{
|
||||||
|
bgcolor: "var(--joy-palette-background-surface)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{storages?.map((storage) => (
|
{storages?.map((storage) => (
|
||||||
<Option key={storage.uuid} value={storage.uuid}>
|
<Option key={storage.uuid} value={storage.uuid}>
|
||||||
@@ -344,7 +399,10 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<Typography level="body-sm" className="text-slate-500">
|
<Typography
|
||||||
|
level="body-sm"
|
||||||
|
sx={{ color: "var(--joy-palette-text-tertiary)" }}
|
||||||
|
>
|
||||||
{t("product-details")}
|
{t("product-details")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className="grow"></div>
|
<div className="grow"></div>
|
||||||
@@ -353,7 +411,13 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
loading={isPending}
|
loading={isPending}
|
||||||
onClick={() => downloadQRcode()}
|
onClick={() => downloadQRcode()}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("download-qr-code")}
|
{t("download-qr-code")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -361,7 +425,13 @@ export const ViewProduct = (props: ViewProductProps) => {
|
|||||||
type="submit"
|
type="submit"
|
||||||
loading={isPending}
|
loading={isPending}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-2xl bg-[#0b6bcb] text-white shadow-[0_16px_36px_rgba(11,107,203,0.35)] transition hover:-translate-y-0.5 hover:bg-[#095aa7]"
|
color="primary"
|
||||||
|
variant="solid"
|
||||||
|
className="rounded-2xl transition hover:-translate-y-0.5"
|
||||||
|
sx={{
|
||||||
|
boxShadow:
|
||||||
|
"0 16px 36px color-mix(in srgb, var(--joy-palette-primary-solidBg) 35%, transparent)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{t("save")}
|
{t("save")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Outlet, createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||||
import { Sidebar } from "../../components/Sidebar";
|
import { Sidebar } from "../../components/Sidebar";
|
||||||
|
|
||||||
export const Route = createFileRoute("/app/_hiddenLayout")({
|
export const Route = createFileRoute("/app/_hiddenLayout")({
|
||||||
@@ -7,7 +7,10 @@ export const Route = createFileRoute("/app/_hiddenLayout")({
|
|||||||
|
|
||||||
function AppLayout() {
|
function AppLayout() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-screen w-full flex-col bg-[#f7f9fc] lg:flex-row">
|
<div
|
||||||
|
className="flex min-h-screen w-full flex-col lg:flex-row"
|
||||||
|
style={{ backgroundColor: "var(--joy-palette-background-body)" }}
|
||||||
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<main className="flex-1 px-4 py-5 sm:px-6 lg:px-8 lg:py-6">
|
<main className="flex-1 px-4 py-5 sm:px-6 lg:px-8 lg:py-6">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
import { extendTheme } from "@mui/joy/styles";
|
||||||
|
|
||||||
|
// I generated this theme with AI (Claude Opus 4.6)
|
||||||
|
|
||||||
|
export const theme = extendTheme({
|
||||||
|
cssVarPrefix: "joy",
|
||||||
|
colorSchemes: {
|
||||||
|
light: {
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
50: "#eef7ff",
|
||||||
|
100: "#dcedff",
|
||||||
|
200: "#b3d9ff",
|
||||||
|
300: "#7fbdff",
|
||||||
|
400: "#469cf5",
|
||||||
|
500: "#0b6bcb",
|
||||||
|
600: "#0958a8",
|
||||||
|
700: "#08478a",
|
||||||
|
800: "#0a3a6e",
|
||||||
|
900: "#0c2f59",
|
||||||
|
solidBg: "#0b6bcb",
|
||||||
|
solidHoverBg: "#095aa7",
|
||||||
|
solidActiveBg: "#08478a",
|
||||||
|
outlinedBorder: "#7fbdff",
|
||||||
|
outlinedColor: "#08478a",
|
||||||
|
softBg: "#dcedff", // Color in sidebar for selected view
|
||||||
|
softColor: "#08478a",
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
50: "#ecfdf5",
|
||||||
|
100: "#d1faee",
|
||||||
|
200: "#a6f4dc",
|
||||||
|
300: "#6ee7c9",
|
||||||
|
400: "#34d0ac",
|
||||||
|
500: "#0d9488",
|
||||||
|
600: "#0b7c73",
|
||||||
|
700: "#0a655e",
|
||||||
|
800: "#0b514c",
|
||||||
|
900: "#0a423f",
|
||||||
|
solidBg: "#0d9488",
|
||||||
|
solidHoverBg: "#0b7c73",
|
||||||
|
softBg: "#d1faee",
|
||||||
|
softColor: "#0a655e",
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
50: "#fffaeb",
|
||||||
|
100: "#fef0c7",
|
||||||
|
200: "#fedf89",
|
||||||
|
300: "#fdc74a",
|
||||||
|
400: "#fbaf24",
|
||||||
|
500: "#f59e0b",
|
||||||
|
600: "#d67e06",
|
||||||
|
700: "#b25f08",
|
||||||
|
800: "#8f4a0e",
|
||||||
|
900: "#763d10",
|
||||||
|
solidBg: "#f59e0b",
|
||||||
|
solidHoverBg: "#d67e06",
|
||||||
|
softBg: "#fef0c7",
|
||||||
|
softColor: "#8f4a0e",
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
50: "#fff1f2",
|
||||||
|
100: "#ffe1e4",
|
||||||
|
200: "#ffc7cd",
|
||||||
|
300: "#ff9ba7",
|
||||||
|
400: "#fb6b7d",
|
||||||
|
500: "#e11d48",
|
||||||
|
600: "#be123c",
|
||||||
|
700: "#9b1038",
|
||||||
|
800: "#7f1230",
|
||||||
|
900: "#6a122b",
|
||||||
|
solidBg: "#e11d48",
|
||||||
|
solidHoverBg: "#be123c",
|
||||||
|
softBg: "#ffe1e4",
|
||||||
|
softColor: "#9b1038",
|
||||||
|
},
|
||||||
|
neutral: {
|
||||||
|
50: "#f8fafc",
|
||||||
|
100: "#f1f5f9",
|
||||||
|
200: "#e2e8f0",
|
||||||
|
300: "#cbd5e1",
|
||||||
|
400: "#94a3b8",
|
||||||
|
500: "#64748b",
|
||||||
|
600: "#475569",
|
||||||
|
700: "#334155",
|
||||||
|
800: "#1e293b",
|
||||||
|
900: "#0f172a",
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
body: "#f7f9fc",
|
||||||
|
surface: "#ffffff",
|
||||||
|
level1: "#f1f5f9",
|
||||||
|
level2: "#e2e8f0",
|
||||||
|
level3: "#cbd5e1",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: "#0f172a",
|
||||||
|
secondary: "#475569",
|
||||||
|
tertiary: "#64748b",
|
||||||
|
},
|
||||||
|
divider: "rgba(15, 23, 42, 0.08)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dark: {
|
||||||
|
palette: {
|
||||||
|
primary: {
|
||||||
|
50: "#0c2f59",
|
||||||
|
100: "#0a3a6e",
|
||||||
|
200: "#08478a",
|
||||||
|
300: "#0958a8",
|
||||||
|
400: "#0b6bcb",
|
||||||
|
500: "#3b8ce0",
|
||||||
|
600: "#6ba9ea",
|
||||||
|
700: "#9cc6f2",
|
||||||
|
800: "#c7defa",
|
||||||
|
900: "#eef7ff",
|
||||||
|
solidBg: "#3b8ce0",
|
||||||
|
solidHoverBg: "#6ba9ea",
|
||||||
|
solidActiveBg: "#0958a8",
|
||||||
|
outlinedBorder: "#0958a8",
|
||||||
|
outlinedColor: "#9cc6f2",
|
||||||
|
softBg: "rgba(59, 140, 224, 0.16)", // Color in sidebar for selected view
|
||||||
|
softColor: "#9cc6f2",
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
50: "#0a423f",
|
||||||
|
100: "#0b514c",
|
||||||
|
200: "#0a655e",
|
||||||
|
300: "#0b7c73",
|
||||||
|
400: "#0d9488",
|
||||||
|
500: "#2dc8b1",
|
||||||
|
600: "#6ee7c9",
|
||||||
|
700: "#a6f4dc",
|
||||||
|
800: "#d1faee",
|
||||||
|
900: "#ecfdf5",
|
||||||
|
solidBg: "#0d9488",
|
||||||
|
solidHoverBg: "#2dc8b1",
|
||||||
|
softBg: "rgba(13, 148, 136, 0.18)",
|
||||||
|
softColor: "#6ee7c9",
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
50: "#763d10",
|
||||||
|
100: "#8f4a0e",
|
||||||
|
200: "#b25f08",
|
||||||
|
300: "#d67e06",
|
||||||
|
400: "#f59e0b",
|
||||||
|
500: "#fbaf24",
|
||||||
|
600: "#fdc74a",
|
||||||
|
700: "#fedf89",
|
||||||
|
800: "#fef0c7",
|
||||||
|
900: "#fffaeb",
|
||||||
|
solidBg: "#f59e0b",
|
||||||
|
solidHoverBg: "#fbaf24",
|
||||||
|
softBg: "rgba(245, 158, 11, 0.16)",
|
||||||
|
softColor: "#fdc74a",
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
50: "#6a122b",
|
||||||
|
100: "#7f1230",
|
||||||
|
200: "#9b1038",
|
||||||
|
300: "#be123c",
|
||||||
|
400: "#e11d48",
|
||||||
|
500: "#f04463",
|
||||||
|
600: "#fb6b7d",
|
||||||
|
700: "#ff9ba7",
|
||||||
|
800: "#ffc7cd",
|
||||||
|
900: "#fff1f2",
|
||||||
|
solidBg: "#e11d48",
|
||||||
|
solidHoverBg: "#f04463",
|
||||||
|
softBg: "rgba(225, 29, 72, 0.18)",
|
||||||
|
softColor: "#fb6b7d",
|
||||||
|
},
|
||||||
|
neutral: {
|
||||||
|
50: "#0b1220",
|
||||||
|
100: "#0f172a",
|
||||||
|
200: "#1e293b",
|
||||||
|
300: "#334155",
|
||||||
|
400: "#475569",
|
||||||
|
500: "#64748b",
|
||||||
|
600: "#94a3b8",
|
||||||
|
700: "#cbd5e1",
|
||||||
|
800: "#e2e8f0",
|
||||||
|
900: "#f1f5f9",
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
body: "#0b1220",
|
||||||
|
surface: "#111a2e",
|
||||||
|
level1: "#16213a",
|
||||||
|
level2: "#1e293b",
|
||||||
|
level3: "#334155",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: "#f1f5f9",
|
||||||
|
secondary: "#cbd5e1",
|
||||||
|
tertiary: "#94a3b8",
|
||||||
|
},
|
||||||
|
divider: "rgba(226, 232, 240, 0.08)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
body: "'Inter', 'Segoe UI', system-ui, sans-serif",
|
||||||
|
display: "'Inter', 'Segoe UI', system-ui, sans-serif",
|
||||||
|
},
|
||||||
|
radius: {
|
||||||
|
xs: "6px",
|
||||||
|
sm: "10px",
|
||||||
|
md: "14px",
|
||||||
|
lg: "20px",
|
||||||
|
xl: "28px",
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
JoyInput: {
|
||||||
|
styleOverrides: {
|
||||||
|
input: ({ theme }) => ({
|
||||||
|
color: theme.vars.palette.text.primary,
|
||||||
|
"&::placeholder": {
|
||||||
|
color: theme.vars.palette.text.tertiary,
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JoySelect: {
|
||||||
|
styleOverrides: {
|
||||||
|
button: ({ theme }) => ({
|
||||||
|
color: theme.vars.palette.text.primary,
|
||||||
|
}),
|
||||||
|
listbox: ({ theme }) => ({
|
||||||
|
color: theme.vars.palette.text.primary,
|
||||||
|
backgroundColor: theme.vars.palette.background.surface,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JoySheet: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: ({ theme }) => ({
|
||||||
|
backgroundColor: theme.vars.palette.background.surface,
|
||||||
|
borderColor: theme.vars.palette.divider,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JoyOption: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: ({ theme }) => ({
|
||||||
|
color: theme.vars.palette.text.primary,
|
||||||
|
backgroundColor: theme.vars.palette.background.surface,
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: theme.vars.palette.background.level1,
|
||||||
|
},
|
||||||
|
'&[aria-selected="true"]': {
|
||||||
|
backgroundColor: theme.vars.palette.background.level2,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JoyButton: {
|
||||||
|
styleOverrides: {
|
||||||
|
root: {
|
||||||
|
transition: "all 0.15s ease",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user