refactor: mejorar la navegación de configuración y agregar iconos a las entradas de menú
This commit is contained in:
parent
5ad446be00
commit
685472b3ef
@ -32,7 +32,7 @@ export const SettingsLayout = ({ sections }: SettingsLayoutProps) => {
|
||||
/>
|
||||
|
||||
<div className="grid gap-6 lg:grid-cols-[18rem_minmax(0,1fr)]">
|
||||
<aside className="rounded-xl border border-border bg-card p-4 shadow-sm lg:sticky lg:top-6 lg:self-start">
|
||||
<aside className="min-w-0 lg:sticky lg:top-6 lg:self-start">
|
||||
<SettingsNavigation sections={sections} />
|
||||
</aside>
|
||||
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { i18n } from "@repo/i18next";
|
||||
import { Card, CardContent, CardFooter, CardHeader, Separator } from "@repo/shadcn-ui/components";
|
||||
import { cn } from "@repo/shadcn-ui/lib/utils";
|
||||
import { ChevronRightIcon, SettingsIcon } from "lucide-react";
|
||||
import { Fragment } from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
|
||||
import type { SettingsNavigationSection } from "@/settings/build-settings-navigation";
|
||||
@ -20,43 +23,70 @@ const resolveEntryLabel = (labelKey: string, moduleName: string) => {
|
||||
export const SettingsNavigation = ({ sections }: SettingsNavigationProps) => {
|
||||
const { t } = useAppTranslation();
|
||||
|
||||
if (sections.length === 0) {
|
||||
return (
|
||||
<p className="text-sm leading-6 text-muted-foreground">
|
||||
{t("web.settings.empty.navigation")}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<nav aria-label={t("web.settings.title")} className="space-y-6">
|
||||
{sections.map((section) => (
|
||||
<div className="space-y-2" key={section.id}>
|
||||
<h2 className="text-xs font-semibold uppercase tracking-[0.14em] text-muted-foreground">
|
||||
{t(section.labelKey)}
|
||||
</h2>
|
||||
<Card className="gap-5" size="sm">
|
||||
<CardHeader>
|
||||
<h2 className="px-3 text-lg font-semibold tracking-tight pb-2">
|
||||
{t("web.settings.title")}
|
||||
</h2>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<nav aria-label={t("web.settings.title")} className="space-y-4 ">
|
||||
{sections.length === 0 ? (
|
||||
<p className="text-sm leading-6 text-muted-foreground">
|
||||
{t("web.settings.empty.navigation")}
|
||||
</p>
|
||||
) : (
|
||||
sections.map((section, index) => (
|
||||
<Fragment key={section.id}>
|
||||
{index > 0 && <Separator />}
|
||||
<div className="space-y-2">
|
||||
<h3 className="px-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||
{t(section.labelKey)}
|
||||
</h3>
|
||||
<ul className="space-y-1">
|
||||
{section.entries.map((entry) => {
|
||||
const Icon = entry.icon ?? SettingsIcon;
|
||||
|
||||
<ul className="space-y-1">
|
||||
{section.entries.map((entry) => (
|
||||
<li key={`${entry.moduleName}:${entry.id}`}>
|
||||
<NavLink
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
"block rounded-lg px-3 py-2 text-sm font-medium text-foreground/80 transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
||||
"hover:bg-muted hover:text-foreground",
|
||||
isActive && "bg-muted text-foreground"
|
||||
)
|
||||
}
|
||||
to={entry.path}
|
||||
>
|
||||
{resolveEntryLabel(entry.labelKey, entry.moduleName)}
|
||||
</NavLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
return (
|
||||
<li key={`${entry.moduleName}:${entry.id}`}>
|
||||
<NavLink
|
||||
className={({ isActive }) =>
|
||||
cn(
|
||||
"group flex min-h-12 w-full items-center gap-3 rounded-lg px-3 py-2 text-base transition-colors",
|
||||
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-card",
|
||||
isActive
|
||||
? "bg-primary/5 font-medium text-primary"
|
||||
: "bg-transparent font-normal text-foreground hover:bg-muted/50"
|
||||
)
|
||||
}
|
||||
to={entry.path}
|
||||
>
|
||||
<Icon
|
||||
aria-hidden="true"
|
||||
className="size-5 shrink-0 text-muted-foreground group-aria-[current=page]:text-primary"
|
||||
/>
|
||||
<span className="min-w-0 flex-1 break-words">
|
||||
{resolveEntryLabel(entry.labelKey, entry.moduleName)}
|
||||
</span>
|
||||
<ChevronRightIcon
|
||||
aria-hidden="true"
|
||||
className="size-5 shrink-0 text-muted-foreground group-aria-[current=page]:text-primary"
|
||||
/>
|
||||
</NavLink>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</Fragment>
|
||||
))
|
||||
)}
|
||||
</nav>
|
||||
</CardContent>
|
||||
<CardFooter>
|
||||
<></>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { CalendarClockIcon, CreditCardIcon, LandmarkIcon } from "lucide-react";
|
||||
|
||||
import { CatalogsModuleManifest } from "./manifest";
|
||||
|
||||
test("publica la entry de Settings para payment methods", () => {
|
||||
@ -8,18 +10,21 @@ test("publica la entry de Settings para payment methods", () => {
|
||||
entries: [
|
||||
{
|
||||
id: "payment-methods",
|
||||
icon: CreditCardIcon,
|
||||
sectionId: "general",
|
||||
labelKey: "settings.paymentMethods.title",
|
||||
path: "/settings/general/payment-methods",
|
||||
},
|
||||
{
|
||||
id: "payment-terms",
|
||||
icon: CalendarClockIcon,
|
||||
sectionId: "general",
|
||||
labelKey: "settings.paymentTerms.title",
|
||||
path: "/settings/general/payment-terms",
|
||||
},
|
||||
{
|
||||
id: "tax-regimes",
|
||||
icon: LandmarkIcon,
|
||||
sectionId: "general",
|
||||
labelKey: "settings.taxRegimes.title",
|
||||
path: "/settings/general/tax-regimes",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { IModuleClient } from "@erp/core/client";
|
||||
import { CalendarClockIcon, CreditCardIcon, LandmarkIcon } from "lucide-react";
|
||||
|
||||
import { CatalogsRoutes } from "./catalogs-routes";
|
||||
import { ensureModuleTranslations } from "./i18n";
|
||||
@ -18,18 +19,21 @@ export const CatalogsModuleManifest: IModuleClient = {
|
||||
entries: [
|
||||
{
|
||||
id: "payment-methods",
|
||||
icon: CreditCardIcon,
|
||||
sectionId: "general",
|
||||
labelKey: "settings.paymentMethods.title",
|
||||
path: "/settings/general/payment-methods",
|
||||
},
|
||||
{
|
||||
id: "payment-terms",
|
||||
icon: CalendarClockIcon,
|
||||
sectionId: "general",
|
||||
labelKey: "settings.paymentTerms.title",
|
||||
path: "/settings/general/payment-terms",
|
||||
},
|
||||
{
|
||||
id: "tax-regimes",
|
||||
icon: LandmarkIcon,
|
||||
sectionId: "general",
|
||||
labelKey: "settings.taxRegimes.title",
|
||||
path: "/settings/general/tax-regimes",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { IModuleClient } from "@erp/core/client";
|
||||
import { Building2Icon } from "lucide-react";
|
||||
|
||||
import { CompanyRoutes } from "./company-routes";
|
||||
import { ensureModuleTranslations } from "./i18n";
|
||||
@ -18,6 +19,7 @@ export const CompaniesModuleManifest: IModuleClient = {
|
||||
entries: [
|
||||
{
|
||||
id: "company-details",
|
||||
icon: Building2Icon,
|
||||
sectionId: "company",
|
||||
labelKey: "settings.companyDetails.title",
|
||||
path: "/settings/company/details",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
import type { ReactNode } from "react";
|
||||
import type { RouteObject } from "react-router-dom";
|
||||
|
||||
@ -10,6 +11,7 @@ export type ModuleRouteLayout = "app-sidebar" | "auth" | "app-fullscreen";
|
||||
export interface ModuleSettingsEntryContribution {
|
||||
id: string;
|
||||
sectionId: string;
|
||||
icon?: LucideIcon;
|
||||
labelKey: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
|
||||
import { ShieldCheckIcon } from "lucide-react";
|
||||
|
||||
import { CustomerInvoicesModuleManifest } from "./manifest";
|
||||
|
||||
test("publica la entry de Settings para VeriFactu", () => {
|
||||
@ -8,6 +10,7 @@ test("publica la entry de Settings para VeriFactu", () => {
|
||||
entries: [
|
||||
{
|
||||
id: "verifactu",
|
||||
icon: ShieldCheckIcon,
|
||||
sectionId: "billing",
|
||||
labelKey: "settings.verifactu.title",
|
||||
path: "/settings/billing/verifactu",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { IModuleClient, ModuleClientParams } from "@erp/core/client";
|
||||
import { ShieldCheckIcon } from "lucide-react";
|
||||
|
||||
import { CustomerInvoiceRoutes } from "./customer-invoice-routes";
|
||||
import { ensureModuleTranslations } from "./i18n";
|
||||
@ -21,6 +22,7 @@ export const CustomerInvoicesModuleManifest: IModuleClient = {
|
||||
entries: [
|
||||
{
|
||||
id: "verifactu",
|
||||
icon: ShieldCheckIcon,
|
||||
sectionId: "billing",
|
||||
labelKey: "settings.verifactu.title",
|
||||
path: "/settings/billing/verifactu",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user