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:
2026-06-04 00:28:09 +02:00
parent 2a7f411834
commit 53efd21a2b
8 changed files with 200 additions and 108 deletions
+112 -74
View File
@@ -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>
);
+8 -8
View File
@@ -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"