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