mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 08:08:48 +02:00
Add nav config module, budget-cycle helper, me/recent API helpers
navigation.ts becomes the single source of truth for sidebar and breadcrumbs (Inicio entry added; '/' is exact-match). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -130,6 +130,8 @@ export async function logout() {
|
||||
await fetch("/api/auth/logout", { method: "POST", credentials: "same-origin" });
|
||||
}
|
||||
|
||||
export const getMe = () => api.get<{ username: string }>("/auth/me");
|
||||
|
||||
// ─── Types ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export type Account = Schema<'AccountRead'>;
|
||||
@@ -240,6 +242,11 @@ export const upsertBalanceOverride = (
|
||||
export const deleteBalanceOverride = (year: number, month: number) =>
|
||||
api.delete(`/budget/balance-override/${year}/${month}`);
|
||||
|
||||
// --- Transactions ---
|
||||
|
||||
export const getRecentTransactions = (limit = 5) =>
|
||||
api.get<Transaction[]>("/transactions/recent", { params: { limit } });
|
||||
|
||||
// --- Installment Plans (Tasa Cero) ---
|
||||
|
||||
export type InstallmentPlan = Schema<'InstallmentPlanRead'>;
|
||||
|
||||
@@ -18,6 +18,29 @@ export function formatShortDate(dateStr: string): string {
|
||||
});
|
||||
}
|
||||
|
||||
/** Budget month M = the credit-card cycle ENDING on the 18th of M
|
||||
* (matches get_cycle_range in the backend). After the 18th we are already
|
||||
* in next month's cycle — including the Dec 19 → January-next-year wrap. */
|
||||
export function currentBudgetCycle(today = new Date()): {
|
||||
year: number;
|
||||
month: number;
|
||||
start: Date;
|
||||
end: Date;
|
||||
} {
|
||||
let year = today.getFullYear();
|
||||
let month = today.getMonth() + 1;
|
||||
if (today.getDate() > 18) {
|
||||
month += 1;
|
||||
if (month === 13) {
|
||||
month = 1;
|
||||
year += 1;
|
||||
}
|
||||
}
|
||||
const end = new Date(year, month - 1, 18);
|
||||
const start = new Date(year, month - 2, 18);
|
||||
return { year, month, start, end };
|
||||
}
|
||||
|
||||
/** "hace 3 días" / "hace 2 horas" / "hace un momento" */
|
||||
export function formatRelativeAge(iso: string): string {
|
||||
const ms = Date.now() - new Date(iso).getTime();
|
||||
|
||||
71
frontend/src/lib/navigation.ts
Normal file
71
frontend/src/lib/navigation.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import {
|
||||
BarChart3,
|
||||
Calculator,
|
||||
Droplets,
|
||||
Home,
|
||||
Landmark,
|
||||
Layers,
|
||||
PiggyBank,
|
||||
RefreshCw,
|
||||
Sparkles,
|
||||
Telescope,
|
||||
TrendingUp,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
/** Single source of truth for the sidebar nav and header breadcrumbs. */
|
||||
|
||||
export interface NavItem {
|
||||
to: string;
|
||||
icon: LucideIcon;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface NavSection {
|
||||
label: string;
|
||||
items: NavItem[];
|
||||
}
|
||||
|
||||
export const navSections: NavSection[] = [
|
||||
{
|
||||
label: "General",
|
||||
items: [
|
||||
{ to: "/", icon: Home, label: "Inicio" },
|
||||
{ to: "/asistente", icon: Sparkles, label: "Asistente" },
|
||||
{ to: "/sync", icon: RefreshCw, label: "Sincronización" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Finanzas",
|
||||
items: [
|
||||
{ to: "/budget", icon: Calculator, label: "Presupuesto" },
|
||||
{ to: "/salarios", icon: Landmark, label: "Salarios" },
|
||||
{ to: "/financiamientos", icon: Layers, label: "Financiamientos" },
|
||||
{ to: "/pensions", icon: PiggyBank, label: "Pensiones" },
|
||||
{ to: "/proyecciones", icon: TrendingUp, label: "Proyecciones" },
|
||||
{ to: "/planificador", icon: Telescope, label: "Planificador" },
|
||||
{ to: "/analytics", icon: BarChart3, label: "Analytics" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Servicios",
|
||||
items: [
|
||||
{ to: "/servicios-municipales", icon: Droplets, label: "Municipalidad" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** "/" must match exactly — prefix-matching it would mark Inicio active everywhere. */
|
||||
export function isNavActive(to: string, pathname: string): boolean {
|
||||
if (to === "/") return pathname === "/";
|
||||
return pathname === to || pathname.startsWith(`${to}/`);
|
||||
}
|
||||
|
||||
export function titleFor(pathname: string): string {
|
||||
for (const section of navSections) {
|
||||
for (const item of section.items) {
|
||||
if (isNavActive(item.to, pathname)) return item.label;
|
||||
}
|
||||
}
|
||||
return "WealthySmart";
|
||||
}
|
||||
Reference in New Issue
Block a user