feat: enhance Sidebar with toggle functionality and improve layout; update StorageRow and Inventory components for better styling; add new translations for quick product details
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Typography } from "@mui/joy";
|
||||
import InventoryIcon from "@mui/icons-material/Inventory";
|
||||
@@ -6,6 +7,8 @@ import StorageIcon from "@mui/icons-material/Storage";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
|
||||
import TranslateIcon from "@mui/icons-material/Translate";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { useNavigate, useMatchRoute } from "@tanstack/react-router";
|
||||
import Cookies from "js-cookie";
|
||||
import { changeTranslation } from "../utils/uxFncs";
|
||||
@@ -14,6 +17,7 @@ export const Sidebar = () => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const matchRoute = useMatchRoute();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
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!";
|
||||
@@ -21,86 +25,120 @@ export const Sidebar = () => {
|
||||
const variant = (to: string) =>
|
||||
!!matchRoute({ to, fuzzy: false }) ? "soft" : "plain";
|
||||
|
||||
const handleNavigate = (to: string) => {
|
||||
navigate({ to });
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<aside className="flex h-full min-h-screen w-full max-w-70 flex-col gap-8 border-r border-white/80 bg-linear-to-b from-[#f7fbff] via-[#f2f6fb] to-[#eef3f9] px-6 py-8 shadow-[0_20px_60px_rgba(11,107,203,0.08)]">
|
||||
<div className="space-y-2">
|
||||
<Typography
|
||||
level="h2"
|
||||
className="text-[22px] font-semibold text-[#0b6bcb]"
|
||||
>
|
||||
{t("app-title")}
|
||||
</Typography>
|
||||
<Typography
|
||||
level="body-lg"
|
||||
className="text-sm font-medium text-slate-500"
|
||||
>
|
||||
{Cookies.get("app-name") ? Cookies.get("app-name") : ""}
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<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">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="space-y-2">
|
||||
<Typography
|
||||
level="h2"
|
||||
className="text-[22px] font-semibold text-[#0b6bcb]"
|
||||
>
|
||||
{t("app-title")}
|
||||
</Typography>
|
||||
<Typography
|
||||
level="body-lg"
|
||||
className="text-sm font-medium text-slate-500"
|
||||
>
|
||||
{Cookies.get("app-name") ? Cookies.get("app-name") : ""}
|
||||
</Typography>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => navigate({ to: "/app/inventory" })}
|
||||
variant={variant("/app/inventory")}
|
||||
startDecorator={<InventoryIcon />}
|
||||
className={btnClass}
|
||||
variant="soft"
|
||||
size="sm"
|
||||
className="lg:hidden transition-transform duration-200 ease-out"
|
||||
onClick={() => setIsOpen((open) => !open)}
|
||||
startDecorator={
|
||||
<span
|
||||
className={`inline-flex transition-transform duration-200 ease-out ${
|
||||
isOpen ? "rotate-180" : "rotate-0"
|
||||
}`}
|
||||
>
|
||||
{isOpen ? <CloseIcon /> : <MenuIcon />}
|
||||
</span>
|
||||
}
|
||||
sx={{ display: { lg: "none" } }}
|
||||
>
|
||||
{t("inventory")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate({ to: "/app/add-product" })}
|
||||
variant={variant("/app/add-product")}
|
||||
startDecorator={<AddBoxIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("add")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate({ to: "/app/storages" })}
|
||||
variant={variant("/app/storages")}
|
||||
startDecorator={<StorageIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("storages")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate({ to: "/app/app-settings" })}
|
||||
variant={variant("/app/app-settings")}
|
||||
startDecorator={<SettingsIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("settings")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Cookies.remove("token");
|
||||
navigate({ to: "/login" });
|
||||
}}
|
||||
color="danger"
|
||||
startDecorator={<ExitToAppIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("logout")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
changeTranslation();
|
||||
}}
|
||||
color="neutral"
|
||||
startDecorator={<TranslateIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("change-translation")}
|
||||
{isOpen ? t("close") : t("menu")}
|
||||
</Button>
|
||||
</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)]">
|
||||
<img
|
||||
src="/favicon.png"
|
||||
alt="Stockhome"
|
||||
className="h-7 w-7 rounded-lg"
|
||||
/>
|
||||
<span>Stockhome</span>
|
||||
<div
|
||||
className={`flex flex-1 flex-col gap-8 overflow-hidden transition-all duration-200 ease-out lg:overflow-visible lg:transition-none ${
|
||||
isOpen
|
||||
? "max-h-120 opacity-100 pointer-events-auto"
|
||||
: "max-h-0 opacity-0 pointer-events-none"
|
||||
} lg:max-h-none lg:opacity-100 lg:pointer-events-auto`}
|
||||
>
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Button
|
||||
onClick={() => handleNavigate("/app/inventory")}
|
||||
variant={variant("/app/inventory")}
|
||||
startDecorator={<InventoryIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("inventory")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleNavigate("/app/add-product")}
|
||||
variant={variant("/app/add-product")}
|
||||
startDecorator={<AddBoxIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("add")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleNavigate("/app/storages")}
|
||||
variant={variant("/app/storages")}
|
||||
startDecorator={<StorageIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("storages")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => handleNavigate("/app/app-settings")}
|
||||
variant={variant("/app/app-settings")}
|
||||
startDecorator={<SettingsIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("settings")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
Cookies.remove("token");
|
||||
handleNavigate("/login");
|
||||
}}
|
||||
color="danger"
|
||||
startDecorator={<ExitToAppIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("logout")}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
changeTranslation();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
color="neutral"
|
||||
startDecorator={<TranslateIcon />}
|
||||
className={btnClass}
|
||||
>
|
||||
{t("change-translation")}
|
||||
</Button>
|
||||
</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)]">
|
||||
<img
|
||||
src="/favicon.png"
|
||||
alt="Stockhome"
|
||||
className="h-7 w-7 rounded-lg"
|
||||
/>
|
||||
<span>Stockhome</span>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
|
||||
@@ -47,8 +47,8 @@ export const StorageRow = ({ storage }: StorageRowProps) => {
|
||||
(values.description ?? "") !== (storage.description ?? "");
|
||||
|
||||
return (
|
||||
<tr key={storage.uuid} className="align-top">
|
||||
<td>
|
||||
<tr key={storage.uuid} className="border-t border-slate-200 align-top">
|
||||
<td className="px-6 py-5">
|
||||
<form.Field name="name">
|
||||
{(field) => (
|
||||
<Input
|
||||
@@ -57,12 +57,12 @@ export const StorageRow = ({ storage }: StorageRowProps) => {
|
||||
onBlur={field.handleBlur}
|
||||
size="sm"
|
||||
variant="outlined"
|
||||
className="rounded-xl bg-white/90 shadow-[0_8px_18px_rgba(15,23,42,0.06)]"
|
||||
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"
|
||||
/>
|
||||
)}
|
||||
</form.Field>
|
||||
</td>
|
||||
<td>
|
||||
<td className="px-6 py-5">
|
||||
<form.Field name="description">
|
||||
{(field) => (
|
||||
<Input
|
||||
@@ -71,18 +71,18 @@ export const StorageRow = ({ storage }: StorageRowProps) => {
|
||||
onBlur={field.handleBlur}
|
||||
size="sm"
|
||||
variant="outlined"
|
||||
className="rounded-xl bg-white/90 shadow-[0_8px_18px_rgba(15,23,42,0.06)]"
|
||||
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"
|
||||
/>
|
||||
)}
|
||||
</form.Field>
|
||||
</td>
|
||||
<td className="text-sm text-slate-500">
|
||||
<td className="px-6 py-5 text-sm text-slate-500">
|
||||
{formatDate(storage.created_at)}
|
||||
</td>
|
||||
<td className="text-sm text-slate-500">
|
||||
<td className="px-6 py-5 text-sm text-slate-500">
|
||||
{formatDate(storage.updated_at)}
|
||||
</td>
|
||||
<td>
|
||||
<td className="px-6 py-5 text-right">
|
||||
<div className="flex flex-wrap justify-end gap-2">
|
||||
<Button
|
||||
color="primary"
|
||||
|
||||
@@ -97,7 +97,7 @@ export const InventoryPage = () => {
|
||||
|
||||
<Sheet
|
||||
variant="outlined"
|
||||
className="mt-6 flex h-[calc(100vh-260px)] flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white/80 shadow-sm"
|
||||
className="mt-6 flex min-h-0 flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white/80 shadow-sm sm:h-[calc(100vh-260px)]"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-slate-200 px-6 py-4 text-slate-700">
|
||||
<Typography level="body-lg" fontWeight="bold">
|
||||
|
||||
@@ -20,6 +20,7 @@ import { toInputDate } from "../utils/uxFncs";
|
||||
import type { ProductFormValues } from "../misc/interfaces";
|
||||
import type { productDetailsInterface } from "../misc/interfaces";
|
||||
import Cookies from "js-cookie";
|
||||
import ElectricBoltIcon from "@mui/icons-material/ElectricBolt";
|
||||
|
||||
export const ProductQuickView = () => {
|
||||
const uuid = window.location.search.split("uuid=")[1];
|
||||
@@ -99,8 +100,10 @@ export const ProductQuickView = () => {
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<div className="space-y-1">
|
||||
<Typography level="h2" className="text-slate-900">
|
||||
{t("product-details")}
|
||||
<Typography level="h2" fontWeight="1000" className="text-slate-900">
|
||||
<ElectricBoltIcon color="primary" />
|
||||
{t("product-details-quick")}
|
||||
<ElectricBoltIcon color="primary" />
|
||||
</Typography>
|
||||
</div>
|
||||
<Chip
|
||||
|
||||
@@ -40,31 +40,72 @@ export const Storages = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Sheet className="mt-6 rounded-3xl border border-slate-200/70 bg-white/90 p-6 shadow-[0_24px_60px_rgba(12,38,78,0.12)] backdrop-blur">
|
||||
<Sheet
|
||||
variant="outlined"
|
||||
className="mt-6 flex min-h-0 flex-col overflow-hidden rounded-2xl border border-slate-200 bg-white/80 shadow-sm sm:h-[calc(100vh-260px)]"
|
||||
>
|
||||
<AddStorageModal isOpen={modal} setOpen={setModal} />
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center py-16">
|
||||
<CircularProgress size="lg" />
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-hidden rounded-2xl border border-slate-200/80 bg-white">
|
||||
<Table hoverRow className="min-w-240 text-slate-700">
|
||||
<thead className="bg-slate-50/80 text-slate-500">
|
||||
<tr className="text-sm uppercase tracking-wide">
|
||||
<th className="px-5 py-3 text-left">{t("storage-name")}</th>
|
||||
<th className="px-5 py-3 text-left">{t("description")}</th>
|
||||
<th className="px-5 py-3 text-left">{t("created-at")}</th>
|
||||
<th className="px-5 py-3 text-left">{t("updated-at")}</th>
|
||||
<th className="px-5 py-3 text-right"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-slate-200">
|
||||
{storages?.map((storage: Storage) => (
|
||||
<StorageRow key={storage.uuid} storage={storage} />
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
<>
|
||||
<div className="flex items-center justify-between border-b border-slate-200 px-6 py-4 text-slate-700">
|
||||
<Typography level="body-lg" fontWeight="bold">
|
||||
{t("storages")}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="flex-1 overflow-auto">
|
||||
<Table
|
||||
stickyHeader
|
||||
stripe="odd"
|
||||
variant="plain"
|
||||
hoverRow
|
||||
className="min-w-240 text-slate-700"
|
||||
sx={{
|
||||
"--TableCell-headBackground":
|
||||
"var(--joy-palette-background-surface)",
|
||||
"& thead": {
|
||||
position: "sticky",
|
||||
top: 0,
|
||||
zIndex: 3,
|
||||
backgroundColor: "rgb(248 250 252)",
|
||||
},
|
||||
"& thead tr": {
|
||||
backgroundColor: "rgb(248 250 252)",
|
||||
},
|
||||
"& thead th:nth-child(2)": {
|
||||
width: "40%",
|
||||
},
|
||||
"& thead th:nth-child(3)": {
|
||||
width: "14%",
|
||||
},
|
||||
"& thead th": {
|
||||
zIndex: 2,
|
||||
backgroundColor: "rgb(248 250 252)",
|
||||
backgroundImage: "none",
|
||||
},
|
||||
"& tr > *:nth-child(n+4)": { textAlign: "left" },
|
||||
}}
|
||||
>
|
||||
<thead>
|
||||
<tr className="text-slate-600">
|
||||
<th className="px-6 py-4">{t("storage-name")}</th>
|
||||
<th className="px-6 py-4">{t("description")}</th>
|
||||
<th className="px-6 py-4">{t("created-at")}</th>
|
||||
<th className="px-6 py-4">{t("updated-at")}</th>
|
||||
<th className="px-6 py-4 text-right"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{storages?.map((storage: Storage) => (
|
||||
<StorageRow key={storage.uuid} storage={storage} />
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Sheet>
|
||||
</>
|
||||
|
||||
@@ -7,9 +7,9 @@ export const Route = createFileRoute("/app/_hiddenLayout")({
|
||||
|
||||
function AppLayout() {
|
||||
return (
|
||||
<div className="flex min-h-screen w-full bg-[#f7f9fc]">
|
||||
<div className="flex min-h-screen w-full flex-col bg-[#f7f9fc] lg:flex-row">
|
||||
<Sidebar />
|
||||
<main className="flex-1 px-8 py-6">
|
||||
<main className="flex-1 px-4 py-5 sm:px-6 lg:px-8 lg:py-6">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -42,5 +42,10 @@
|
||||
"selected": "ausgewählt",
|
||||
"logout": "Abmelden",
|
||||
"pcs": "Stk.",
|
||||
"change-translation": "English"
|
||||
"change-translation": "English",
|
||||
"new-storage-title": "Neuer Lagerort",
|
||||
"new-storage-content": "Geben Sie hier die Daten für einen neuen Lagerort ein.",
|
||||
"menu": "Menü",
|
||||
"close": "Schließen",
|
||||
"product-details-quick": "Schnelle Produktdetails"
|
||||
}
|
||||
@@ -42,5 +42,10 @@
|
||||
"selected": "selected",
|
||||
"logout": "Logout",
|
||||
"pcs": "pcs.",
|
||||
"change-translation": "Deutsch"
|
||||
"change-translation": "Deutsch",
|
||||
"new-storage-title": "New Storage location",
|
||||
"new-storage-content": "Enter the details for a new storage location here.",
|
||||
"menu": "Menu",
|
||||
"close": "Close",
|
||||
"product-details-quick": "Fast product details"
|
||||
}
|
||||
Reference in New Issue
Block a user