feat(v1.4.0): design system semântico, correções de revalidação e melhorias de UX
- Adicionar tokens semânticos de estado (success, warning, info) no globals.css - Migrar ~60+ componentes de cores hardcoded do Tailwind para tokens semânticos - Unificar 3 arrays duplicados de cores de categorias em importação única - Corrigir widgets de boleto/fatura que não atualizavam após pagamento (actions de fatura e antecipação não invalidavam cache do dashboard) - Corrigir scroll em listas Popover+Command (modal prop) - Adicionar link "detalhes" no card de orçamento para página da categoria - Adicionar indicadores de tendência coloridos nos cards de métricas - Estender cores de chart de 6 para 10 - Normalizar dark mode e remover tokens não utilizados Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
30
CHANGELOG.md
30
CHANGELOG.md
@@ -5,6 +5,36 @@ Todas as mudanças notáveis deste projeto serão documentadas neste arquivo.
|
|||||||
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
|
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/),
|
||||||
e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
|
e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
|
||||||
|
|
||||||
|
## [1.4.0] - 2026-02-07
|
||||||
|
|
||||||
|
### Corrigido
|
||||||
|
|
||||||
|
- Widgets de boleto/fatura não atualizavam após pagamento: actions de fatura (`updateInvoicePaymentStatusAction`, `updatePaymentDateAction`) e antecipação de parcelas não invalidavam o cache do dashboard
|
||||||
|
- Substituídos `revalidatePath()` manuais por `revalidateForEntity()` nas actions de fatura e antecipação
|
||||||
|
- Expandido `revalidateConfig.cartoes` para incluir `/contas` e `/lancamentos` (afetados por pagamento de fatura)
|
||||||
|
- Scroll não funcionava em listas Popover+Command (estabelecimento, categorias, filtros): adicionado `modal` ao Popover nos 4 componentes afetados
|
||||||
|
|
||||||
|
### Adicionado
|
||||||
|
|
||||||
|
- Link "detalhes" no card de orçamento para navegar diretamente à página da categoria
|
||||||
|
- Indicadores de tendência coloridos nos cards de métricas do dashboard (receitas, despesas, balanço, previsto) com cores semânticas sutis
|
||||||
|
- Tokens semânticos de estado no design system: `--success`, `--warning`, `--info` (com foregrounds) para light e dark mode
|
||||||
|
- Cores de chart estendidas de 6 para 10 (`--chart-7` a `--chart-10`: teal, violet, cyan, lime)
|
||||||
|
- Variantes `success` e `info` no componente Badge
|
||||||
|
|
||||||
|
### Alterado
|
||||||
|
|
||||||
|
- Migrados ~60+ componentes de cores hardcoded do Tailwind (`green-500`, `red-600`, `amber-500`, `blue-500`, etc.) para tokens semânticos (`success`, `destructive`, `warning`, `info`)
|
||||||
|
- Unificados 3 arrays duplicados de cores de categorias (em `category-report-chart.tsx`, `category-history.ts`, `category-history-widget.tsx`) para importação única de `category-colors.ts`
|
||||||
|
- Month picker migrado de tokens customizados (`--month-picker`) para tokens padrão (`--card`)
|
||||||
|
- Dark mode normalizado: hues consistentes (~70 warm family) em vez de valores dispersos
|
||||||
|
- Token `--accent` ajustado para ser visualmente distinto de `--background`
|
||||||
|
- Token `--card` corrigido para branco limpo (`oklch(100% 0 0)`)
|
||||||
|
|
||||||
|
### Removido
|
||||||
|
|
||||||
|
- Tokens não utilizados: `--dark`, `--dark-foreground`, `--month-picker`, `--month-picker-foreground`
|
||||||
|
|
||||||
## [1.3.1] - 2026-02-06
|
## [1.3.1] - 2026-02-06
|
||||||
|
|
||||||
### Adicionado
|
### Adicionado
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { and, eq, sql } from "drizzle-orm";
|
import { and, eq, sql } from "drizzle-orm";
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
cartoes,
|
cartoes,
|
||||||
@@ -11,6 +10,7 @@ import {
|
|||||||
pagadores,
|
pagadores,
|
||||||
} from "@/db/schema";
|
} from "@/db/schema";
|
||||||
import { buildInvoicePaymentNote } from "@/lib/accounts/constants";
|
import { buildInvoicePaymentNote } from "@/lib/accounts/constants";
|
||||||
|
import { revalidateForEntity } from "@/lib/actions/helpers";
|
||||||
import { getUser } from "@/lib/auth/server";
|
import { getUser } from "@/lib/auth/server";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import {
|
import {
|
||||||
@@ -206,9 +206,7 @@ export async function updateInvoicePaymentStatusAction(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/cartoes/${data.cartaoId}/fatura`);
|
revalidateForEntity("cartoes");
|
||||||
revalidatePath("/cartoes");
|
|
||||||
revalidatePath("/contas");
|
|
||||||
|
|
||||||
return { success: true, message: successMessageByStatus[data.status] };
|
return { success: true, message: successMessageByStatus[data.status] };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -275,9 +273,7 @@ export async function updatePaymentDateAction(
|
|||||||
.where(eq(lancamentos.id, existingPayment.id));
|
.where(eq(lancamentos.id, existingPayment.id));
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/cartoes/${data.cartaoId}/fatura`);
|
revalidateForEntity("cartoes");
|
||||||
revalidatePath("/cartoes");
|
|
||||||
revalidatePath("/contas");
|
|
||||||
|
|
||||||
return { success: true, message: "Data de pagamento atualizada." };
|
return { success: true, message: "Data de pagamento atualizada." };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
|
import { and, asc, desc, eq, inArray, isNull, or } from "drizzle-orm";
|
||||||
import { revalidatePath } from "next/cache";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
antecipacoesParcelas,
|
antecipacoesParcelas,
|
||||||
@@ -9,7 +8,7 @@ import {
|
|||||||
lancamentos,
|
lancamentos,
|
||||||
pagadores,
|
pagadores,
|
||||||
} from "@/db/schema";
|
} from "@/db/schema";
|
||||||
import { handleActionError } from "@/lib/actions/helpers";
|
import { handleActionError, revalidateForEntity } from "@/lib/actions/helpers";
|
||||||
import type { ActionResult } from "@/lib/actions/types";
|
import type { ActionResult } from "@/lib/actions/types";
|
||||||
import { getUser } from "@/lib/auth/server";
|
import { getUser } from "@/lib/auth/server";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
@@ -263,8 +262,7 @@ export async function createInstallmentAnticipationAction(
|
|||||||
.where(inArray(lancamentos.id, data.installmentIds));
|
.where(inArray(lancamentos.id, data.installmentIds));
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath("/lancamentos");
|
revalidateForEntity("lancamentos");
|
||||||
revalidatePath("/dashboard");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -427,8 +425,7 @@ export async function cancelInstallmentAnticipationAction(
|
|||||||
.where(eq(antecipacoesParcelas.id, data.anticipationId));
|
.where(eq(antecipacoesParcelas.id, data.anticipationId));
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath("/lancamentos");
|
revalidateForEntity("lancamentos");
|
||||||
revalidatePath("/dashboard");
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
106
app/globals.css
106
app/globals.css
@@ -10,7 +10,7 @@
|
|||||||
/* Base surfaces - warm cream with subtle orange undertone */
|
/* Base surfaces - warm cream with subtle orange undertone */
|
||||||
--background: oklch(96.563% 0.00504 67.275);
|
--background: oklch(96.563% 0.00504 67.275);
|
||||||
--foreground: oklch(18% 0.02 45);
|
--foreground: oklch(18% 0.02 45);
|
||||||
--card: oklch(100% 0.00011 271.152);
|
--card: oklch(100% 0 0);
|
||||||
--card-foreground: oklch(18% 0.02 45);
|
--card-foreground: oklch(18% 0.02 45);
|
||||||
--popover: oklch(99.5% 0.004 80);
|
--popover: oklch(99.5% 0.004 80);
|
||||||
--popover-foreground: oklch(18% 0.02 45);
|
--popover-foreground: oklch(18% 0.02 45);
|
||||||
@@ -28,9 +28,17 @@
|
|||||||
--muted-foreground: oklch(45% 0.015 60);
|
--muted-foreground: oklch(45% 0.015 60);
|
||||||
|
|
||||||
/* Accent - complementary warm tone */
|
/* Accent - complementary warm tone */
|
||||||
--accent: oklch(96.563% 0.00504 67.275);
|
--accent: oklch(94% 0.01 70);
|
||||||
--accent-foreground: oklch(22% 0.025 45);
|
--accent-foreground: oklch(22% 0.025 45);
|
||||||
|
|
||||||
|
/* Semantic states */
|
||||||
|
--success: oklch(55% 0.17 150);
|
||||||
|
--success-foreground: oklch(98% 0.01 150);
|
||||||
|
--warning: oklch(75.976% 0.16034 71.493);
|
||||||
|
--warning-foreground: oklch(20% 0.04 85);
|
||||||
|
--info: oklch(55% 0.17 250);
|
||||||
|
--info-foreground: oklch(98% 0.01 250);
|
||||||
|
|
||||||
/* Destructive - accessible red */
|
/* Destructive - accessible red */
|
||||||
--destructive: oklch(55% 0.22 27);
|
--destructive: oklch(55% 0.22 27);
|
||||||
--destructive-foreground: oklch(98% 0.005 30);
|
--destructive-foreground: oklch(98% 0.005 30);
|
||||||
@@ -40,22 +48,26 @@
|
|||||||
--input: oklch(82% 0.012 75);
|
--input: oklch(82% 0.012 75);
|
||||||
--ring: oklch(69.18% 0.18855 38.353);
|
--ring: oklch(69.18% 0.18855 38.353);
|
||||||
|
|
||||||
/* Charts - harmonious, distinct, accessible */
|
/* Charts - 10 harmonious, distinct, accessible colors */
|
||||||
--chart-1: var(--color-emerald-400);
|
--chart-1: var(--color-emerald-400);
|
||||||
--chart-2: var(--color-orange-400);
|
--chart-2: var(--color-orange-400);
|
||||||
--chart-3: var(--color-indigo-400);
|
--chart-3: var(--color-indigo-400);
|
||||||
--chart-4: var(--color-amber-400);
|
--chart-4: var(--color-amber-400);
|
||||||
--chart-5: var(--color-pink-400);
|
--chart-5: var(--color-pink-400);
|
||||||
--chart-6: var(--color-stone-400);
|
--chart-6: var(--color-stone-400);
|
||||||
|
--chart-7: var(--color-teal-400);
|
||||||
|
--chart-8: var(--color-violet-400);
|
||||||
|
--chart-9: var(--color-cyan-400);
|
||||||
|
--chart-10: var(--color-lime-400);
|
||||||
|
|
||||||
/* Sidebar - slight elevation from background */
|
/* Sidebar - slight elevation from background */
|
||||||
--sidebar: oklch(100% 0.00011 271.152);
|
--sidebar: oklch(100% 0 0);
|
||||||
--sidebar-foreground: oklch(20% 0.02 45);
|
--sidebar-foreground: oklch(20% 0.02 45);
|
||||||
--sidebar-primary: oklch(25% 0.025 45);
|
--sidebar-primary: oklch(25% 0.025 45);
|
||||||
--sidebar-primary-foreground: oklch(98% 0.008 80);
|
--sidebar-primary-foreground: oklch(98% 0.008 80);
|
||||||
--sidebar-accent: oklch(96.563% 0.00504 67.275);
|
--sidebar-accent: oklch(96.563% 0.00504 67.275);
|
||||||
--sidebar-accent-foreground: oklch(22% 0.025 45);
|
--sidebar-accent-foreground: oklch(22% 0.025 45);
|
||||||
--sidebar-border: oklch(58.814% 0.15852 38.26);
|
--sidebar-border: oklch(69.18% 0.18855 38.353);
|
||||||
--sidebar-ring: oklch(69.18% 0.18855 38.353);
|
--sidebar-ring: oklch(69.18% 0.18855 38.353);
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
@@ -79,46 +91,50 @@
|
|||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
|
|
||||||
/* Special components */
|
/* Special components */
|
||||||
--month-picker: oklch(100% 0.00011 271.152);
|
|
||||||
--month-picker-foreground: oklch(22% 0.015 45);
|
|
||||||
--dark: oklch(22% 0.015 45);
|
|
||||||
--dark-foreground: oklch(94% 0.008 80);
|
|
||||||
--welcome-banner: var(--primary);
|
--welcome-banner: var(--primary);
|
||||||
--welcome-banner-foreground: oklch(98% 0.008 80);
|
--welcome-banner-foreground: var(--primary-foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
/* Base surfaces - true dark with minimal saturation */
|
/* Base surfaces - warm dark with consistent hue family */
|
||||||
--background: oklch(18.674% 0.00002 271.152);
|
--background: oklch(18.5% 0.002 70);
|
||||||
--foreground: oklch(92.189% 0.0186 103.516);
|
--foreground: oklch(92% 0.015 80);
|
||||||
--card: oklch(24.039% 0.00151 16.27);
|
--card: oklch(24% 0.003 70);
|
||||||
--card-foreground: oklch(92.189% 0.0186 103.516);
|
--card-foreground: oklch(92% 0.015 80);
|
||||||
--popover: oklch(24.039% 0.00151 16.27);
|
--popover: oklch(24% 0.003 70);
|
||||||
--popover-foreground: oklch(92.189% 0.0186 103.516);
|
--popover-foreground: oklch(92% 0.015 80);
|
||||||
|
|
||||||
/* Primary - vibrant terracotta stands out on dark */
|
/* Primary - vibrant terracotta stands out on dark */
|
||||||
--primary: oklch(69.18% 0.18855 38.353);
|
--primary: oklch(69.18% 0.18855 38.353);
|
||||||
--primary-foreground: oklch(20.019% 0.00002 271.152);
|
--primary-foreground: oklch(20% 0.002 70);
|
||||||
|
|
||||||
/* Secondary - elevated surface */
|
/* Secondary - elevated surface */
|
||||||
--secondary: oklch(22% 0.004 285);
|
--secondary: oklch(22% 0.004 70);
|
||||||
--secondary-foreground: oklch(92.189% 0.0186 103.516);
|
--secondary-foreground: oklch(92% 0.015 80);
|
||||||
|
|
||||||
/* Muted - subtle surface variant */
|
/* Muted - subtle surface variant */
|
||||||
--muted: oklch(33.674% 0.00531 91.552);
|
--muted: oklch(33.5% 0.005 70);
|
||||||
--muted-foreground: oklch(72.285% 0.00436 286.016);
|
--muted-foreground: oklch(72% 0.004 70);
|
||||||
|
|
||||||
/* Accent - subtle highlight */
|
/* Accent - subtle highlight */
|
||||||
--accent: oklch(26.893% 0.00391 84.539);
|
--accent: oklch(27% 0.004 70);
|
||||||
--accent-foreground: oklch(92.189% 0.0186 103.516);
|
--accent-foreground: oklch(92% 0.015 80);
|
||||||
|
|
||||||
|
/* Semantic states */
|
||||||
|
--success: oklch(65% 0.19 150);
|
||||||
|
--success-foreground: oklch(15% 0.02 150);
|
||||||
|
--warning: oklch(75.976% 0.16034 71.493);
|
||||||
|
--warning-foreground: oklch(15% 0.04 85);
|
||||||
|
--info: oklch(65% 0.17 250);
|
||||||
|
--info-foreground: oklch(15% 0.02 250);
|
||||||
|
|
||||||
/* Destructive - accessible red for dark */
|
/* Destructive - accessible red for dark */
|
||||||
--destructive: oklch(62% 0.2 28);
|
--destructive: oklch(62% 0.2 28);
|
||||||
--destructive-foreground: oklch(98% 0.005 30);
|
--destructive-foreground: oklch(98% 0.005 30);
|
||||||
|
|
||||||
/* Borders and inputs - visible but subtle */
|
/* Borders and inputs - visible but subtle */
|
||||||
--border: oklch(37.332% 0.01493 101.928);
|
--border: oklch(37% 0.01 70);
|
||||||
--input: oklch(32% 0.005 285);
|
--input: oklch(32% 0.005 70);
|
||||||
--ring: oklch(69.18% 0.18855 38.353);
|
--ring: oklch(69.18% 0.18855 38.353);
|
||||||
|
|
||||||
/* Charts - bright and distinct on dark */
|
/* Charts - bright and distinct on dark */
|
||||||
@@ -128,15 +144,19 @@
|
|||||||
--chart-4: var(--color-amber-500);
|
--chart-4: var(--color-amber-500);
|
||||||
--chart-5: var(--color-pink-500);
|
--chart-5: var(--color-pink-500);
|
||||||
--chart-6: var(--color-stone-500);
|
--chart-6: var(--color-stone-500);
|
||||||
|
--chart-7: var(--color-teal-500);
|
||||||
|
--chart-8: var(--color-violet-500);
|
||||||
|
--chart-9: var(--color-cyan-500);
|
||||||
|
--chart-10: var(--color-lime-500);
|
||||||
|
|
||||||
/* Sidebar - slight separation from main */
|
/* Sidebar - slight separation from main */
|
||||||
--sidebar: oklch(24.039% 0.00151 16.27);
|
--sidebar: oklch(24% 0.003 70);
|
||||||
--sidebar-foreground: oklch(92.189% 0.0186 103.516);
|
--sidebar-foreground: oklch(92% 0.015 80);
|
||||||
--sidebar-primary: oklch(69.18% 0.18855 38.353);
|
--sidebar-primary: oklch(69.18% 0.18855 38.353);
|
||||||
--sidebar-primary-foreground: oklch(12.897% 0.00619 87.19);
|
--sidebar-primary-foreground: oklch(13% 0.006 70);
|
||||||
--sidebar-accent: oklch(32.242% 0.00447 67.486);
|
--sidebar-accent: oklch(32% 0.004 70);
|
||||||
--sidebar-accent-foreground: oklch(92.189% 0.0186 103.516);
|
--sidebar-accent-foreground: oklch(92% 0.015 80);
|
||||||
--sidebar-border: oklch(26% 0.004 285);
|
--sidebar-border: oklch(26% 0.004 70);
|
||||||
--sidebar-ring: oklch(69.18% 0.18855 38.353);
|
--sidebar-ring: oklch(69.18% 0.18855 38.353);
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
@@ -161,12 +181,8 @@
|
|||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
|
|
||||||
/* Special components */
|
/* Special components */
|
||||||
--month-picker: oklch(24.039% 0.00151 16.27);
|
--welcome-banner: var(--card);
|
||||||
--month-picker-foreground: oklch(92.189% 0.0186 103.516);
|
--welcome-banner-foreground: var(--card-foreground);
|
||||||
--dark: oklch(92.189% 0.0186 103.516);
|
|
||||||
--dark-foreground: oklch(18.711% 0.00427 84.566);
|
|
||||||
--welcome-banner: oklch(24.039% 0.00151 16.27);
|
|
||||||
--welcome-banner-foreground: oklch(92.189% 0.0186 103.516);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -184,6 +200,12 @@
|
|||||||
--color-muted-foreground: var(--muted-foreground);
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-success: var(--success);
|
||||||
|
--color-success-foreground: var(--success-foreground);
|
||||||
|
--color-warning: var(--warning);
|
||||||
|
--color-warning-foreground: var(--warning-foreground);
|
||||||
|
--color-info: var(--info);
|
||||||
|
--color-info-foreground: var(--info-foreground);
|
||||||
--color-destructive: var(--destructive);
|
--color-destructive: var(--destructive);
|
||||||
--color-destructive-foreground: var(--destructive-foreground);
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
@@ -195,6 +217,10 @@
|
|||||||
--color-chart-4: var(--chart-4);
|
--color-chart-4: var(--chart-4);
|
||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
--color-chart-6: var(--chart-6);
|
--color-chart-6: var(--chart-6);
|
||||||
|
--color-chart-7: var(--chart-7);
|
||||||
|
--color-chart-8: var(--chart-8);
|
||||||
|
--color-chart-9: var(--chart-9);
|
||||||
|
--color-chart-10: var(--chart-10);
|
||||||
--color-sidebar: var(--sidebar);
|
--color-sidebar: var(--sidebar);
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
@@ -217,10 +243,6 @@
|
|||||||
--shadow-2xl: var(--shadow-2xl);
|
--shadow-2xl: var(--shadow-2xl);
|
||||||
--tracking-normal: var(--tracking-normal);
|
--tracking-normal: var(--tracking-normal);
|
||||||
--spacing: var(--spacing);
|
--spacing: var(--spacing);
|
||||||
--color-month-picker: var(--month-picker);
|
|
||||||
--color-month-picker-foreground: var(--month-picker-foreground);
|
|
||||||
--color-dark: var(--dark);
|
|
||||||
--color-dark-foreground: var(--dark-foreground);
|
|
||||||
--color-welcome-banner: var(--welcome-banner);
|
--color-welcome-banner: var(--welcome-banner);
|
||||||
--color-welcome-banner-foreground: var(--welcome-banner-foreground);
|
--color-welcome-banner-foreground: var(--welcome-banner-foreground);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,14 +226,14 @@ export function ApiTokensForm({ tokens }: ApiTokensFormProps) {
|
|||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
<RiCheckLine className="h-4 w-4 text-green-500" />
|
<RiCheckLine className="h-4 w-4 text-success" />
|
||||||
) : (
|
) : (
|
||||||
<RiFileCopyLine className="h-4 w-4" />
|
<RiFileCopyLine className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-md bg-amber-50 dark:bg-amber-950/30 p-3 text-sm text-amber-800 dark:text-amber-200">
|
<div className="rounded-md bg-warning/10 p-3 text-sm text-warning">
|
||||||
<p className="font-medium">Importante:</p>
|
<p className="font-medium">Importante:</p>
|
||||||
<ul className="list-disc list-inside mt-1 space-y-1">
|
<ul className="list-disc list-inside mt-1 space-y-1">
|
||||||
<li>Guarde este token em local seguro</li>
|
<li>Guarde este token em local seguro</li>
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function CompanionTab({ tokens }: CompanionTabProps) {
|
|||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<h2 className="text-lg font-bold">OpenSheets Companion</h2>
|
<h2 className="text-lg font-bold">OpenSheets Companion</h2>
|
||||||
<span className="inline-flex items-center gap-1 rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-700 dark:bg-green-900/30 dark:text-green-400">
|
<span className="inline-flex items-center gap-1 rounded-full bg-success/10 px-2 py-0.5 text-xs font-medium text-success dark:bg-success/10">
|
||||||
<RiAndroidLine className="h-3 w-3" />
|
<RiAndroidLine className="h-3 w-3" />
|
||||||
Android
|
Android
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -149,13 +149,13 @@ export function UpdateEmailForm({
|
|||||||
aria-invalid={!isEmailDifferent}
|
aria-invalid={!isEmailDifferent}
|
||||||
className={
|
className={
|
||||||
!isEmailDifferent
|
!isEmailDifferent
|
||||||
? "border-red-500 focus-visible:ring-red-500"
|
? "border-destructive focus-visible:ring-destructive"
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{!isEmailDifferent && newEmail && (
|
{!isEmailDifferent && newEmail && (
|
||||||
<p
|
<p
|
||||||
className="text-xs text-red-600 dark:text-red-400 flex items-center gap-1"
|
className="text-xs text-destructive flex items-center gap-1"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<RiCloseLine className="h-3.5 w-3.5" />O novo e-mail deve ser
|
<RiCloseLine className="h-3.5 w-3.5" />O novo e-mail deve ser
|
||||||
@@ -188,9 +188,9 @@ export function UpdateEmailForm({
|
|||||||
aria-invalid={emailsMatch === false}
|
aria-invalid={emailsMatch === false}
|
||||||
className={
|
className={
|
||||||
emailsMatch === false
|
emailsMatch === false
|
||||||
? "border-red-500 focus-visible:ring-red-500 pr-10"
|
? "border-destructive focus-visible:ring-destructive pr-10"
|
||||||
: emailsMatch === true
|
: emailsMatch === true
|
||||||
? "border-green-500 focus-visible:ring-green-500 pr-10"
|
? "border-success focus-visible:ring-success pr-10"
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -199,12 +199,12 @@ export function UpdateEmailForm({
|
|||||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||||
{emailsMatch ? (
|
{emailsMatch ? (
|
||||||
<RiCheckLine
|
<RiCheckLine
|
||||||
className="h-5 w-5 text-green-500"
|
className="h-5 w-5 text-success"
|
||||||
aria-label="Os e-mails coincidem"
|
aria-label="Os e-mails coincidem"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RiCloseLine
|
<RiCloseLine
|
||||||
className="h-5 w-5 text-red-500"
|
className="h-5 w-5 text-destructive"
|
||||||
aria-label="Os e-mails não coincidem"
|
aria-label="Os e-mails não coincidem"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -215,7 +215,7 @@ export function UpdateEmailForm({
|
|||||||
{emailsMatch === false && (
|
{emailsMatch === false && (
|
||||||
<p
|
<p
|
||||||
id="confirm-email-help"
|
id="confirm-email-help"
|
||||||
className="text-xs text-red-600 dark:text-red-400 flex items-center gap-1"
|
className="text-xs text-destructive flex items-center gap-1"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<RiCloseLine className="h-3.5 w-3.5" />
|
<RiCloseLine className="h-3.5 w-3.5" />
|
||||||
@@ -225,7 +225,7 @@ export function UpdateEmailForm({
|
|||||||
{emailsMatch === true && (
|
{emailsMatch === true && (
|
||||||
<p
|
<p
|
||||||
id="confirm-email-help"
|
id="confirm-email-help"
|
||||||
className="text-xs text-green-600 dark:text-green-400 flex items-center gap-1"
|
className="text-xs text-success flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<RiCheckLine className="h-3.5 w-3.5" />
|
<RiCheckLine className="h-3.5 w-3.5" />
|
||||||
Os e-mails coincidem
|
Os e-mails coincidem
|
||||||
|
|||||||
@@ -55,9 +55,7 @@ function PasswordRequirement({ met, label }: { met: boolean; label: string }) {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-1.5 text-xs transition-colors",
|
"flex items-center gap-1.5 text-xs transition-colors",
|
||||||
met
|
met ? "text-success" : "text-muted-foreground",
|
||||||
? "text-emerald-600 dark:text-emerald-400"
|
|
||||||
: "text-muted-foreground",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{met ? (
|
{met ? (
|
||||||
@@ -133,14 +131,14 @@ export function UpdatePasswordForm({ authProvider }: UpdatePasswordFormProps) {
|
|||||||
// Se o usuário usa Google OAuth, mostrar aviso
|
// Se o usuário usa Google OAuth, mostrar aviso
|
||||||
if (isGoogleAuth) {
|
if (isGoogleAuth) {
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-900 dark:bg-amber-950/20">
|
<div className="rounded-lg border border-warning/30 bg-warning/10 p-4 dark:border-warning/20 dark:bg-warning/10">
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<RiAlertLine className="h-5 w-5 text-amber-600 dark:text-amber-500 shrink-0 mt-0.5" />
|
<RiAlertLine className="h-5 w-5 text-warning shrink-0 mt-0.5" />
|
||||||
<div>
|
<div>
|
||||||
<h3 className="font-medium text-amber-900 dark:text-amber-400">
|
<h3 className="font-medium text-warning">
|
||||||
Alteração de senha não disponível
|
Alteração de senha não disponível
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mt-1 text-sm text-amber-800 dark:text-amber-500">
|
<p className="mt-1 text-sm text-warning">
|
||||||
Você fez login usando sua conta do Google. A senha é gerenciada
|
Você fez login usando sua conta do Google. A senha é gerenciada
|
||||||
diretamente pelo Google e não pode ser alterada aqui. Para
|
diretamente pelo Google e não pode ser alterada aqui. Para
|
||||||
modificar sua senha, acesse as configurações de segurança da sua
|
modificar sua senha, acesse as configurações de segurança da sua
|
||||||
@@ -285,9 +283,9 @@ export function UpdatePasswordForm({ authProvider }: UpdatePasswordFormProps) {
|
|||||||
aria-invalid={passwordsMatch === false}
|
aria-invalid={passwordsMatch === false}
|
||||||
className={
|
className={
|
||||||
passwordsMatch === false
|
passwordsMatch === false
|
||||||
? "border-red-500 focus-visible:ring-red-500"
|
? "border-destructive focus-visible:ring-destructive"
|
||||||
: passwordsMatch === true
|
: passwordsMatch === true
|
||||||
? "border-green-500 focus-visible:ring-green-500"
|
? "border-success focus-visible:ring-success"
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -312,12 +310,12 @@ export function UpdatePasswordForm({ authProvider }: UpdatePasswordFormProps) {
|
|||||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||||
{passwordsMatch ? (
|
{passwordsMatch ? (
|
||||||
<RiCheckLine
|
<RiCheckLine
|
||||||
className="h-5 w-5 text-green-500"
|
className="h-5 w-5 text-success"
|
||||||
aria-label="As senhas coincidem"
|
aria-label="As senhas coincidem"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<RiCloseLine
|
<RiCloseLine
|
||||||
className="h-5 w-5 text-red-500"
|
className="h-5 w-5 text-destructive"
|
||||||
aria-label="As senhas não coincidem"
|
aria-label="As senhas não coincidem"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -328,7 +326,7 @@ export function UpdatePasswordForm({ authProvider }: UpdatePasswordFormProps) {
|
|||||||
{passwordsMatch === false && (
|
{passwordsMatch === false && (
|
||||||
<p
|
<p
|
||||||
id="confirm-password-help"
|
id="confirm-password-help"
|
||||||
className="text-xs text-red-600 dark:text-red-400 flex items-center gap-1"
|
className="text-xs text-destructive flex items-center gap-1"
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<RiCloseLine className="h-3.5 w-3.5" />
|
<RiCloseLine className="h-3.5 w-3.5" />
|
||||||
@@ -338,7 +336,7 @@ export function UpdatePasswordForm({ authProvider }: UpdatePasswordFormProps) {
|
|||||||
{passwordsMatch === true && (
|
{passwordsMatch === true && (
|
||||||
<p
|
<p
|
||||||
id="confirm-password-help"
|
id="confirm-password-help"
|
||||||
className="text-xs text-green-600 dark:text-green-400 flex items-center gap-1"
|
className="text-xs text-success flex items-center gap-1"
|
||||||
>
|
>
|
||||||
<RiCheckLine className="h-3.5 w-3.5" />
|
<RiCheckLine className="h-3.5 w-3.5" />
|
||||||
As senhas coincidem
|
As senhas coincidem
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export function NoteCard({
|
|||||||
<div
|
<div
|
||||||
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${
|
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${
|
||||||
task.completed
|
task.completed
|
||||||
? "bg-green-600 border-green-600"
|
? "bg-success border-success"
|
||||||
: "border-input"
|
: "border-input"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export function NoteDetailsDialog({
|
|||||||
<div
|
<div
|
||||||
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${
|
className={`mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-sm border ${
|
||||||
task.completed
|
task.completed
|
||||||
? "bg-green-600 border-green-600"
|
? "bg-success border-success"
|
||||||
: "border-input"
|
: "border-input"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ export function NoteDialog({
|
|||||||
className="flex items-center gap-3 px-3 py-2 flex-row mt-1"
|
className="flex items-center gap-3 px-3 py-2 flex-row mt-1"
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
className="data-[state=checked]:bg-green-600 data-[state=checked]:border-green-600"
|
className="data-[state=checked]:bg-success data-[state=checked]:border-success"
|
||||||
checked={task.completed}
|
checked={task.completed}
|
||||||
onCheckedChange={() => handleToggleTask(task.id)}
|
onCheckedChange={() => handleToggleTask(task.id)}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function AuthErrorAlert({ error }: AuthErrorAlertProps) {
|
|||||||
if (!error) return null;
|
if (!error) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert className="mt-2 border border-red-500" variant="destructive">
|
<Alert className="mt-2 border border-destructive" variant="destructive">
|
||||||
<RiTerminalLine className="h-4 w-4" />
|
<RiTerminalLine className="h-4 w-4" />
|
||||||
<AlertDescription>{error}</AlertDescription>
|
<AlertDescription>{error}</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -61,9 +61,7 @@ function PasswordRequirement({ met, label }: { met: boolean; label: string }) {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-1.5 text-xs transition-colors",
|
"flex items-center gap-1.5 text-xs transition-colors",
|
||||||
met
|
met ? "text-success" : "text-muted-foreground",
|
||||||
? "text-emerald-600 dark:text-emerald-400"
|
|
||||||
: "text-muted-foreground",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{met ? (
|
{met ? (
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const LEGEND_ITEMS: Array<{
|
|||||||
{ type: "lancamento", label: "Lançamentos" },
|
{ type: "lancamento", label: "Lançamentos" },
|
||||||
{ type: "boleto", label: "Boleto com vencimento" },
|
{ type: "boleto", label: "Boleto com vencimento" },
|
||||||
{ type: "cartao", label: "Vencimento de cartão" },
|
{ type: "cartao", label: "Vencimento de cartão" },
|
||||||
{ label: "Pagamento fatura", dotColor: "bg-green-600" },
|
{ label: "Pagamento fatura", dotColor: "bg-success" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function CalendarLegend() {
|
export function CalendarLegend() {
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ export const EVENT_TYPE_STYLES: Record<
|
|||||||
> = {
|
> = {
|
||||||
lancamento: {
|
lancamento: {
|
||||||
wrapper:
|
wrapper:
|
||||||
"bg-orange-100 text-orange-600 dark:bg-orange-900/10 dark:text-orange-50 border-l-4 border-orange-500",
|
"bg-warning/10 text-warning dark:bg-warning/5 dark:text-warning border-l-4 border-warning",
|
||||||
dot: "bg-orange-600",
|
dot: "bg-warning",
|
||||||
},
|
},
|
||||||
boleto: {
|
boleto: {
|
||||||
wrapper:
|
wrapper:
|
||||||
"bg-blue-100 text-blue-600 dark:bg-blue-900/10 dark:text-blue-50 border-l-4 border-blue-500",
|
"bg-info/10 text-info dark:bg-info/5 dark:text-info border-l-4 border-info",
|
||||||
dot: "bg-blue-600",
|
dot: "bg-info",
|
||||||
},
|
},
|
||||||
cartao: {
|
cartao: {
|
||||||
wrapper:
|
wrapper:
|
||||||
@@ -87,8 +87,8 @@ const getEventStyle = (event: CalendarEvent) => {
|
|||||||
if (isPagamentoFatura(event)) {
|
if (isPagamentoFatura(event)) {
|
||||||
return {
|
return {
|
||||||
wrapper:
|
wrapper:
|
||||||
"bg-green-100 text-green-600 dark:bg-green-900/10 dark:text-green-50 border-l-4 border-green-500",
|
"bg-success/10 text-success dark:bg-success/5 dark:text-success border-l-4 border-success",
|
||||||
dot: "bg-green-600",
|
dot: "bg-success",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return eventStyles[event.type];
|
return eventStyles[event.type];
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const EventCard = ({
|
|||||||
isPagamentoFatura?: boolean;
|
isPagamentoFatura?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const style = isPagamentoFatura
|
const style = isPagamentoFatura
|
||||||
? { dot: "bg-green-600" }
|
? { dot: "bg-success" }
|
||||||
: EVENT_TYPE_STYLES[type];
|
: EVENT_TYPE_STYLES[type];
|
||||||
return (
|
return (
|
||||||
<Card className="flex flex-row gap-2 p-3 mb-1">
|
<Card className="flex flex-row gap-2 p-3 mb-1">
|
||||||
@@ -61,7 +61,7 @@ const renderLancamento = (
|
|||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span
|
<span
|
||||||
className={`text-sm font-semibold leading-tight ${
|
className={`text-sm font-semibold leading-tight ${
|
||||||
isPagamentoFatura && "text-green-600 dark:text-green-400"
|
isPagamentoFatura && "text-success"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{event.lancamento.name}
|
{event.lancamento.name}
|
||||||
@@ -76,9 +76,7 @@ const renderLancamento = (
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-sm font-semibold whitespace-nowrap",
|
"text-sm font-semibold whitespace-nowrap",
|
||||||
isReceita
|
isReceita ? "text-success" : "text-foreground",
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-foreground",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<MoneyValues
|
<MoneyValues
|
||||||
|
|||||||
@@ -56,11 +56,7 @@ export function StatusSelectContent({ label }: { label: string }) {
|
|||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<DotIcon
|
<DotIcon
|
||||||
color={
|
color={isActive ? "bg-success" : "bg-slate-400 dark:bg-slate-500"}
|
||||||
isActive
|
|
||||||
? "bg-emerald-600 dark:bg-emerald-300"
|
|
||||||
: "bg-slate-400 dark:bg-slate-500"
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RiArrowDownLine, RiArrowUpLine } from "@remixicon/react";
|
import { RiArrowDownSFill, RiArrowUpSFill } from "@remixicon/react";
|
||||||
import type { CategoryType } from "@/lib/categorias/constants";
|
import type { CategoryType } from "@/lib/categorias/constants";
|
||||||
import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers";
|
import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers";
|
||||||
import { cn } from "@/lib/utils/ui";
|
import { cn } from "@/lib/utils/ui";
|
||||||
@@ -40,22 +40,22 @@ export function CategoryDetailHeader({
|
|||||||
const variationColor =
|
const variationColor =
|
||||||
category.type === "receita"
|
category.type === "receita"
|
||||||
? isIncrease
|
? isIncrease
|
||||||
? "text-emerald-600"
|
? "text-success"
|
||||||
: isDecrease
|
: isDecrease
|
||||||
? "text-rose-600"
|
? "text-destructive"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
: isIncrease
|
: isIncrease
|
||||||
? "text-rose-600"
|
? "text-destructive"
|
||||||
: isDecrease
|
: isDecrease
|
||||||
? "text-emerald-600"
|
? "text-success"
|
||||||
: "text-muted-foreground";
|
: "text-muted-foreground";
|
||||||
|
|
||||||
const variationIcon =
|
const variationIcon =
|
||||||
isIncrease || isDecrease ? (
|
isIncrease || isDecrease ? (
|
||||||
isIncrease ? (
|
isIncrease ? (
|
||||||
<RiArrowUpLine className="size-4" aria-hidden />
|
<RiArrowUpSFill className="size-4" aria-hidden />
|
||||||
) : (
|
) : (
|
||||||
<RiArrowDownLine className="size-4" aria-hidden />
|
<RiArrowDownSFill className="size-4" aria-hidden />
|
||||||
)
|
)
|
||||||
) : null;
|
) : null;
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ export function TypeSelectContent({ label }: { label: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<DotIcon
|
<DotIcon color={isReceita ? "bg-success" : "bg-destructive"} />
|
||||||
color={
|
|
||||||
isReceita
|
|
||||||
? "bg-emerald-600 dark:bg-emerald-300"
|
|
||||||
: "bg-rose-600 dark:bg-rose-300"
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ export function StatusSelectContent({ label }: { label: string }) {
|
|||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<DotIcon
|
<DotIcon
|
||||||
color={
|
color={isActive ? "bg-success" : "bg-slate-400 dark:bg-slate-500"}
|
||||||
isActive
|
|
||||||
? "bg-emerald-600 dark:bg-emerald-300"
|
|
||||||
: "bg-slate-400 dark:bg-slate-500"
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export function AccountStatementCard({
|
|||||||
<DetailItem
|
<DetailItem
|
||||||
label="Entradas"
|
label="Entradas"
|
||||||
value={
|
value={
|
||||||
<span className="font-medium text-emerald-600">
|
<span className="font-medium text-success">
|
||||||
{formatCurrency(totalIncomes)}
|
{formatCurrency(totalIncomes)}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -137,7 +137,7 @@ export function AccountStatementCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"font-semibold text-xl",
|
"font-semibold text-xl",
|
||||||
totalIncomes - totalExpenses >= 0
|
totalIncomes - totalExpenses >= 0
|
||||||
? "text-emerald-600"
|
? "text-success"
|
||||||
: "text-destructive",
|
: "text-destructive",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -182,8 +182,7 @@ export function BoletosWidget({ boletos }: BoletosWidgetProps) {
|
|||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-full py-0.5",
|
"rounded-full py-0.5",
|
||||||
boleto.isSettled &&
|
boleto.isSettled && "text-success",
|
||||||
"text-green-600 dark:text-green-400",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{statusLabel}
|
{statusLabel}
|
||||||
@@ -203,7 +202,7 @@ export function BoletosWidget({ boletos }: BoletosWidgetProps) {
|
|||||||
onClick={() => handleOpenModal(boleto.id)}
|
onClick={() => handleOpenModal(boleto.id)}
|
||||||
>
|
>
|
||||||
{boleto.isSettled ? (
|
{boleto.isSettled ? (
|
||||||
<span className="flex items-center gap-1 text-green-600 dark:text-green-400">
|
<span className="flex items-center gap-1 text-success">
|
||||||
<RiCheckboxCircleFill className="size-3" /> Pago
|
<RiCheckboxCircleFill className="size-3" /> Pago
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -248,7 +247,7 @@ export function BoletosWidget({ boletos }: BoletosWidgetProps) {
|
|||||||
>
|
>
|
||||||
{modalState === "success" ? (
|
{modalState === "success" ? (
|
||||||
<div className="flex flex-col items-center gap-4 py-6 text-center">
|
<div className="flex flex-col items-center gap-4 py-6 text-center">
|
||||||
<div className="flex size-16 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-500">
|
<div className="flex size-16 items-center justify-center rounded-full bg-success/10 text-success">
|
||||||
<RiCheckboxCircleLine className="size-8" />
|
<RiCheckboxCircleLine className="size-8" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { WidgetEmptyState } from "@/components/widget-empty-state";
|
import { WidgetEmptyState } from "@/components/widget-empty-state";
|
||||||
import type { CategoryHistoryData } from "@/lib/dashboard/categories/category-history";
|
import type { CategoryHistoryData } from "@/lib/dashboard/categories/category-history";
|
||||||
|
import { CATEGORY_COLORS } from "@/lib/utils/category-colors";
|
||||||
import { getIconComponent } from "@/lib/utils/icons";
|
import { getIconComponent } from "@/lib/utils/icons";
|
||||||
|
|
||||||
type CategoryHistoryWidgetProps = {
|
type CategoryHistoryWidgetProps = {
|
||||||
@@ -36,14 +37,7 @@ type CategoryHistoryWidgetProps = {
|
|||||||
|
|
||||||
const STORAGE_KEY_SELECTED = "dashboard-category-history-selected";
|
const STORAGE_KEY_SELECTED = "dashboard-category-history-selected";
|
||||||
|
|
||||||
// Vibrant colors for categories
|
const CHART_COLORS = CATEGORY_COLORS;
|
||||||
const CHART_COLORS = [
|
|
||||||
"#ef4444", // red-500
|
|
||||||
"#3b82f6", // blue-500
|
|
||||||
"#10b981", // emerald-500
|
|
||||||
"#f59e0b", // amber-500
|
|
||||||
"#8b5cf6", // violet-500
|
|
||||||
];
|
|
||||||
|
|
||||||
export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) {
|
export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) {
|
||||||
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
const [selectedCategories, setSelectedCategories] = useState<string[]>([]);
|
||||||
@@ -260,7 +254,7 @@ export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedCategories.length < 5 && availableCategories.length > 0 && (
|
{selectedCategories.length < 5 && availableCategories.length > 0 && (
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen} modal>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
@@ -295,9 +289,9 @@ export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) {
|
|||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
{IconComponent ? (
|
{IconComponent ? (
|
||||||
<IconComponent className="size-4 text-red-600" />
|
<IconComponent className="size-4 text-destructive" />
|
||||||
) : (
|
) : (
|
||||||
<div className="size-3 rounded-sm bg-red-600" />
|
<div className="size-3 rounded-sm bg-destructive" />
|
||||||
)}
|
)}
|
||||||
<span>{category.name}</span>
|
<span>{category.name}</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
@@ -320,9 +314,9 @@ export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) {
|
|||||||
className="gap-2"
|
className="gap-2"
|
||||||
>
|
>
|
||||||
{IconComponent ? (
|
{IconComponent ? (
|
||||||
<IconComponent className="size-4 text-green-600" />
|
<IconComponent className="size-4 text-success" />
|
||||||
) : (
|
) : (
|
||||||
<div className="size-3 rounded-sm bg-green-600" />
|
<div className="size-3 rounded-sm bg-success" />
|
||||||
)}
|
)}
|
||||||
<span>{category.name}</span>
|
<span>{category.name}</span>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownSFill,
|
||||||
RiArrowUpLine,
|
RiArrowUpSFill,
|
||||||
RiExternalLinkLine,
|
RiExternalLinkLine,
|
||||||
RiListUnordered,
|
RiListUnordered,
|
||||||
RiPieChart2Line,
|
RiPieChart2Line,
|
||||||
@@ -220,14 +220,14 @@ export function ExpensesByCategoryWidgetWithChart({
|
|||||||
<span
|
<span
|
||||||
className={`flex items-center gap-0.5 text-xs ${
|
className={`flex items-center gap-0.5 text-xs ${
|
||||||
hasIncrease
|
hasIncrease
|
||||||
? "text-red-600 dark:text-red-500"
|
? "text-destructive"
|
||||||
: hasDecrease
|
: hasDecrease
|
||||||
? "text-green-600 dark:text-green-500"
|
? "text-success"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{hasIncrease && <RiArrowUpLine className="size-3" />}
|
{hasIncrease && <RiArrowUpSFill className="size-3" />}
|
||||||
{hasDecrease && <RiArrowDownLine className="size-3" />}
|
{hasDecrease && <RiArrowDownSFill className="size-3" />}
|
||||||
{formatPercentage(category.percentageChange)}
|
{formatPercentage(category.percentageChange)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -238,16 +238,12 @@ export function ExpensesByCategoryWidgetWithChart({
|
|||||||
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
||||||
<RiWallet3Line
|
<RiWallet3Line
|
||||||
className={`size-3 ${
|
className={`size-3 ${
|
||||||
budgetExceeded
|
budgetExceeded ? "text-destructive" : "text-info"
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
budgetExceeded
|
budgetExceeded ? "text-destructive" : "text-info"
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{budgetExceeded ? (
|
{budgetExceeded ? (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownSFill,
|
||||||
RiArrowUpLine,
|
RiArrowUpSFill,
|
||||||
RiExternalLinkLine,
|
RiExternalLinkLine,
|
||||||
RiPieChartLine,
|
RiPieChartLine,
|
||||||
RiWallet3Line,
|
RiWallet3Line,
|
||||||
@@ -127,14 +127,14 @@ export function ExpensesByCategoryWidget({
|
|||||||
<span
|
<span
|
||||||
className={`flex items-center gap-0.5 text-xs ${
|
className={`flex items-center gap-0.5 text-xs ${
|
||||||
hasIncrease
|
hasIncrease
|
||||||
? "text-red-600 dark:text-red-500"
|
? "text-destructive"
|
||||||
: hasDecrease
|
: hasDecrease
|
||||||
? "text-green-600 dark:text-green-500"
|
? "text-success"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{hasIncrease && <RiArrowUpLine className="size-3" />}
|
{hasIncrease && <RiArrowUpSFill className="size-3" />}
|
||||||
{hasDecrease && <RiArrowDownLine className="size-3" />}
|
{hasDecrease && <RiArrowDownSFill className="size-3" />}
|
||||||
{formatPercentage(category.percentageChange)}
|
{formatPercentage(category.percentageChange)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -145,17 +145,11 @@ export function ExpensesByCategoryWidget({
|
|||||||
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
||||||
<RiWallet3Line
|
<RiWallet3Line
|
||||||
className={`size-3 ${
|
className={`size-3 ${
|
||||||
budgetExceeded
|
budgetExceeded ? "text-destructive" : "text-info"
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={
|
className={budgetExceeded ? "text-destructive" : "text-info"}
|
||||||
budgetExceeded
|
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{budgetExceeded ? (
|
{budgetExceeded ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownSFill,
|
||||||
RiArrowUpLine,
|
RiArrowUpSFill,
|
||||||
RiExternalLinkLine,
|
RiExternalLinkLine,
|
||||||
RiListUnordered,
|
RiListUnordered,
|
||||||
RiPieChart2Line,
|
RiPieChart2Line,
|
||||||
@@ -220,14 +220,14 @@ export function IncomeByCategoryWidgetWithChart({
|
|||||||
<span
|
<span
|
||||||
className={`flex items-center gap-0.5 text-xs ${
|
className={`flex items-center gap-0.5 text-xs ${
|
||||||
hasIncrease
|
hasIncrease
|
||||||
? "text-green-600 dark:text-green-500"
|
? "text-success"
|
||||||
: hasDecrease
|
: hasDecrease
|
||||||
? "text-red-600 dark:text-red-500"
|
? "text-destructive"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{hasIncrease && <RiArrowUpLine className="size-3" />}
|
{hasIncrease && <RiArrowUpSFill className="size-3" />}
|
||||||
{hasDecrease && <RiArrowDownLine className="size-3" />}
|
{hasDecrease && <RiArrowDownSFill className="size-3" />}
|
||||||
{formatPercentage(category.percentageChange)}
|
{formatPercentage(category.percentageChange)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -240,16 +240,12 @@ export function IncomeByCategoryWidgetWithChart({
|
|||||||
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
||||||
<RiWallet3Line
|
<RiWallet3Line
|
||||||
className={`size-3 ${
|
className={`size-3 ${
|
||||||
budgetExceeded
|
budgetExceeded ? "text-destructive" : "text-info"
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
budgetExceeded
|
budgetExceeded ? "text-destructive" : "text-info"
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{budgetExceeded ? (
|
{budgetExceeded ? (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownSFill,
|
||||||
RiArrowUpLine,
|
RiArrowUpSFill,
|
||||||
RiExternalLinkLine,
|
RiExternalLinkLine,
|
||||||
RiPieChartLine,
|
RiPieChartLine,
|
||||||
RiWallet3Line,
|
RiWallet3Line,
|
||||||
@@ -127,14 +127,14 @@ export function IncomeByCategoryWidget({
|
|||||||
<span
|
<span
|
||||||
className={`flex items-center gap-0.5 text-xs ${
|
className={`flex items-center gap-0.5 text-xs ${
|
||||||
hasIncrease
|
hasIncrease
|
||||||
? "text-green-600 dark:text-green-500"
|
? "text-success"
|
||||||
: hasDecrease
|
: hasDecrease
|
||||||
? "text-red-600 dark:text-red-500"
|
? "text-destructive"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{hasIncrease && <RiArrowUpLine className="size-3" />}
|
{hasIncrease && <RiArrowUpSFill className="size-3" />}
|
||||||
{hasDecrease && <RiArrowDownLine className="size-3" />}
|
{hasDecrease && <RiArrowDownSFill className="size-3" />}
|
||||||
{formatPercentage(category.percentageChange)}
|
{formatPercentage(category.percentageChange)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -147,16 +147,12 @@ export function IncomeByCategoryWidget({
|
|||||||
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
<div className="ml-11 flex items-center gap-1.5 text-xs">
|
||||||
<RiWallet3Line
|
<RiWallet3Line
|
||||||
className={`size-3 ${
|
className={`size-3 ${
|
||||||
budgetExceeded
|
budgetExceeded ? "text-destructive" : "text-info"
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
budgetExceeded
|
budgetExceeded ? "text-destructive" : "text-info"
|
||||||
? "text-red-600"
|
|
||||||
: "text-blue-600 dark:text-blue-400"
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{budgetExceeded ? (
|
{budgetExceeded ? (
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ export function InstallmentGroupCard({
|
|||||||
"flex items-center gap-3 rounded-md border p-2 transition-colors",
|
"flex items-center gap-3 rounded-md border p-2 transition-colors",
|
||||||
isSelected && !isPaid && "border-primary/50 bg-primary/5",
|
isSelected && !isPaid && "border-primary/50 bg-primary/5",
|
||||||
isPaid &&
|
isPaid &&
|
||||||
"border-green-400 bg-green-50 dark:border-green-900 dark:bg-green-950/30",
|
"border-success/40 bg-success/5 dark:border-success/20 dark:bg-success/5",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@@ -194,7 +194,7 @@ export function InstallmentGroupCard({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"text-xs font-medium",
|
"text-xs font-medium",
|
||||||
isPaid &&
|
isPaid &&
|
||||||
"text-green-700 dark:text-green-400 line-through decoration-green-600/50",
|
"text-success line-through decoration-success/50",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Parcela {installment.currentInstallment}/
|
Parcela {installment.currentInstallment}/
|
||||||
@@ -202,7 +202,7 @@ export function InstallmentGroupCard({
|
|||||||
{isPaid && (
|
{isPaid && (
|
||||||
<Badge
|
<Badge
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="ml-1 text-xs border-none border-green-700 text-green-700 dark:text-green-400"
|
className="ml-1 text-xs border-none text-success"
|
||||||
>
|
>
|
||||||
<RiCheckboxCircleFill /> Pago
|
<RiCheckboxCircleFill /> Pago
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -211,9 +211,7 @@ export function InstallmentGroupCard({
|
|||||||
<p
|
<p
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xs mt-1",
|
"text-xs mt-1",
|
||||||
isPaid
|
isPaid ? "text-success" : "text-muted-foreground",
|
||||||
? "text-green-700 dark:text-green-500"
|
|
||||||
: "text-muted-foreground",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Vencimento: {dueDate}
|
Vencimento: {dueDate}
|
||||||
@@ -224,7 +222,7 @@ export function InstallmentGroupCard({
|
|||||||
amount={installment.amount}
|
amount={installment.amount}
|
||||||
className={cn(
|
className={cn(
|
||||||
"shrink-0 text-sm",
|
"shrink-0 text-sm",
|
||||||
isPaid && "text-green-700 dark:text-green-400",
|
isPaid && "text-success",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ export function InvoicesWidget({ invoices }: InvoicesWidgetProps) {
|
|||||||
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
<div className="flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||||
{!isPaid ? <span>{dueInfo.label}</span> : null}
|
{!isPaid ? <span>{dueInfo.label}</span> : null}
|
||||||
{isPaid && paymentInfo ? (
|
{isPaid && paymentInfo ? (
|
||||||
<span className="text-green-600 dark:text-green-400">
|
<span className="text-success">
|
||||||
{paymentInfo.label}
|
{paymentInfo.label}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -378,7 +378,7 @@ export function InvoicesWidget({ invoices }: InvoicesWidgetProps) {
|
|||||||
className="p-0 h-auto disabled:opacity-100"
|
className="p-0 h-auto disabled:opacity-100"
|
||||||
>
|
>
|
||||||
{isPaid ? (
|
{isPaid ? (
|
||||||
<span className="text-green-600 dark:text-green-400 flex items-center gap-1">
|
<span className="text-success flex items-center gap-1">
|
||||||
<RiCheckboxCircleFill className="size-3" /> Pago
|
<RiCheckboxCircleFill className="size-3" /> Pago
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
@@ -421,7 +421,7 @@ export function InvoicesWidget({ invoices }: InvoicesWidgetProps) {
|
|||||||
>
|
>
|
||||||
{modalState === "success" ? (
|
{modalState === "success" ? (
|
||||||
<div className="flex flex-col items-center gap-4 py-6 text-center">
|
<div className="flex flex-col items-center gap-4 py-6 text-center">
|
||||||
<div className="flex size-16 items-center justify-center rounded-full bg-emerald-500/10 text-emerald-500">
|
<div className="flex size-16 items-center justify-center rounded-full bg-success/10 text-success">
|
||||||
<RiCheckboxCircleLine className="size-8" />
|
<RiCheckboxCircleLine className="size-8" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -489,7 +489,7 @@ export function InvoicesWidget({ invoices }: InvoicesWidgetProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
{selectedInvoice.paymentStatus ===
|
{selectedInvoice.paymentStatus ===
|
||||||
INVOICE_PAYMENT_STATUS.PAID && selectedPaymentInfo ? (
|
INVOICE_PAYMENT_STATUS.PAID && selectedPaymentInfo ? (
|
||||||
<p className="text-xs text-emerald-600">
|
<p className="text-xs text-success">
|
||||||
{selectedPaymentInfo.label}
|
{selectedPaymentInfo.label}
|
||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -47,13 +47,13 @@ function CategorySection({
|
|||||||
{/* Status de confirmados e pendentes */}
|
{/* Status de confirmados e pendentes */}
|
||||||
<div className="flex items-center justify-between gap-4 text-sm">
|
<div className="flex items-center justify-between gap-4 text-sm">
|
||||||
<div className="flex items-center gap-1.5 ">
|
<div className="flex items-center gap-1.5 ">
|
||||||
<RiCheckboxCircleLine className="size-3 text-emerald-600" />
|
<RiCheckboxCircleLine className="size-3 text-success" />
|
||||||
<MoneyValues amount={confirmed} />
|
<MoneyValues amount={confirmed} />
|
||||||
<span className="text-xs text-muted-foreground">confirmados</span>
|
<span className="text-xs text-muted-foreground">confirmados</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5 ">
|
<div className="flex items-center gap-1.5 ">
|
||||||
<RiHourglass2Line className="size-3 text-orange-500" />
|
<RiHourglass2Line className="size-3 text-warning" />
|
||||||
<MoneyValues amount={pending} />
|
<MoneyValues amount={pending} />
|
||||||
<span className="text-xs text-muted-foreground">pendentes</span>
|
<span className="text-xs text-muted-foreground">pendentes</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiArrowDownLine, RiStore3Line } from "@remixicon/react";
|
import { RiArrowDownSFill, RiStore3Line } from "@remixicon/react";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
||||||
import MoneyValues from "@/components/money-values";
|
import MoneyValues from "@/components/money-values";
|
||||||
@@ -146,7 +146,7 @@ export function PurchasesByCategoryWidget({
|
|||||||
|
|
||||||
{currentTransactions.length === 0 ? (
|
{currentTransactions.length === 0 ? (
|
||||||
<WidgetEmptyState
|
<WidgetEmptyState
|
||||||
icon={<RiArrowDownLine className="size-6 text-muted-foreground" />}
|
icon={<RiArrowDownSFill className="size-6 text-muted-foreground" />}
|
||||||
title="Nenhuma compra encontrada"
|
title="Nenhuma compra encontrada"
|
||||||
description={
|
description={
|
||||||
selectedCategory
|
selectedCategory
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
RiArrowDownLine,
|
RiArrowDownLine,
|
||||||
|
RiArrowDownSFill,
|
||||||
RiArrowUpLine,
|
RiArrowUpLine,
|
||||||
RiCurrencyLine,
|
RiArrowUpSFill,
|
||||||
|
RiCashLine,
|
||||||
RiIncreaseDecreaseLine,
|
RiIncreaseDecreaseLine,
|
||||||
RiSubtractLine,
|
RiSubtractLine,
|
||||||
} from "@remixicon/react";
|
} from "@remixicon/react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardAction,
|
CardAction,
|
||||||
@@ -25,15 +26,30 @@ type Trend = "up" | "down" | "flat";
|
|||||||
const TREND_THRESHOLD = 0.005;
|
const TREND_THRESHOLD = 0.005;
|
||||||
|
|
||||||
const CARDS = [
|
const CARDS = [
|
||||||
{ label: "Receitas", key: "receitas", icon: RiArrowUpLine },
|
{
|
||||||
{ label: "Despesas", key: "despesas", icon: RiArrowDownLine },
|
label: "Receitas",
|
||||||
{ label: "Balanço", key: "balanco", icon: RiIncreaseDecreaseLine },
|
key: "receitas",
|
||||||
{ label: "Previsto", key: "previsto", icon: RiCurrencyLine },
|
icon: RiArrowUpLine,
|
||||||
|
invertTrend: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Despesas",
|
||||||
|
key: "despesas",
|
||||||
|
icon: RiArrowDownLine,
|
||||||
|
invertTrend: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Balanço",
|
||||||
|
key: "balanco",
|
||||||
|
icon: RiIncreaseDecreaseLine,
|
||||||
|
invertTrend: false,
|
||||||
|
},
|
||||||
|
{ label: "Previsto", key: "previsto", icon: RiCashLine, invertTrend: false },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const TREND_ICONS = {
|
const TREND_ICONS = {
|
||||||
up: RiArrowUpLine,
|
up: RiArrowUpSFill,
|
||||||
down: RiArrowDownLine,
|
down: RiArrowDownSFill,
|
||||||
flat: RiSubtractLine,
|
flat: RiSubtractLine,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@@ -58,13 +74,22 @@ const getPercentChange = (current: number, previous: number): string => {
|
|||||||
: "—";
|
: "—";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTrendColor = (trend: Trend, invertTrend: boolean): string => {
|
||||||
|
if (trend === "flat") return "";
|
||||||
|
const isPositive = invertTrend ? trend === "down" : trend === "up";
|
||||||
|
return isPositive
|
||||||
|
? "text-success border-success"
|
||||||
|
: "text-destructive border-destructive";
|
||||||
|
};
|
||||||
|
|
||||||
export function SectionCards({ metrics }: SectionCardsProps) {
|
export function SectionCards({ metrics }: SectionCardsProps) {
|
||||||
return (
|
return (
|
||||||
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-3 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
<div className="*:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card dark:*:data-[slot=card]:bg-card grid grid-cols-1 gap-3 @xl/main:grid-cols-2 @5xl/main:grid-cols-4">
|
||||||
{CARDS.map(({ label, key, icon: Icon }) => {
|
{CARDS.map(({ label, key, icon: Icon, invertTrend }) => {
|
||||||
const metric = metrics[key];
|
const metric = metrics[key];
|
||||||
const trend = getTrend(metric.current, metric.previous);
|
const trend = getTrend(metric.current, metric.previous);
|
||||||
const TrendIcon = TREND_ICONS[trend];
|
const TrendIcon = TREND_ICONS[trend];
|
||||||
|
const trendColor = getTrendColor(trend, invertTrend);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card key={label} className="@container/card gap-2">
|
<Card key={label} className="@container/card gap-2">
|
||||||
@@ -75,10 +100,10 @@ export function SectionCards({ metrics }: SectionCardsProps) {
|
|||||||
</CardTitle>
|
</CardTitle>
|
||||||
<MoneyValues className="text-2xl" amount={metric.current} />
|
<MoneyValues className="text-2xl" amount={metric.current} />
|
||||||
<CardAction>
|
<CardAction>
|
||||||
<Badge variant="outline">
|
<div className={`flex items-center text-xs ${trendColor}`}>
|
||||||
<TrendIcon />
|
<TrendIcon size={16} />
|
||||||
{getPercentChange(metric.current, metric.previous)}
|
{getPercentChange(metric.current, metric.previous)}
|
||||||
</Badge>
|
</div>
|
||||||
</CardAction>
|
</CardAction>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardFooter className="flex-col items-start gap-2 text-sm">
|
<CardFooter className="flex-col items-start gap-2 text-sm">
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const getCardStatusDotColor = (status: string | null) => {
|
|||||||
if (!status) return "bg-gray-400";
|
if (!status) return "bg-gray-400";
|
||||||
const normalizedStatus = status.toLowerCase();
|
const normalizedStatus = status.toLowerCase();
|
||||||
if (normalizedStatus === "ativo" || normalizedStatus === "active") {
|
if (normalizedStatus === "ativo" || normalizedStatus === "active") {
|
||||||
return "bg-green-500";
|
return "bg-success";
|
||||||
}
|
}
|
||||||
return "bg-gray-400";
|
return "bg-gray-400";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const feedbackCategories = [
|
|||||||
title: "Reportar Bug",
|
title: "Reportar Bug",
|
||||||
icon: RiBugLine,
|
icon: RiBugLine,
|
||||||
description: "Encontrou algo que não está funcionando?",
|
description: "Encontrou algo que não está funcionando?",
|
||||||
color: "text-red-500 dark:text-red-400",
|
color: "text-destructive",
|
||||||
url: GITHUB_ISSUES_URL,
|
url: GITHUB_ISSUES_URL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -43,7 +43,7 @@ const feedbackCategories = [
|
|||||||
title: "Sugerir Feature",
|
title: "Sugerir Feature",
|
||||||
icon: RiLightbulbLine,
|
icon: RiLightbulbLine,
|
||||||
description: "Tem uma ideia para melhorar o app?",
|
description: "Tem uma ideia para melhorar o app?",
|
||||||
color: "text-yellow-500 dark:text-yellow-400",
|
color: "text-warning",
|
||||||
url: `${GITHUB_DISCUSSIONS_BASE}?category=ideias`,
|
url: `${GITHUB_DISCUSSIONS_BASE}?category=ideias`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -51,7 +51,7 @@ const feedbackCategories = [
|
|||||||
title: "Dúvidas/Suporte",
|
title: "Dúvidas/Suporte",
|
||||||
icon: RiQuestionLine,
|
icon: RiQuestionLine,
|
||||||
description: "Precisa de ajuda com alguma coisa?",
|
description: "Precisa de ajuda com alguma coisa?",
|
||||||
color: "text-blue-500 dark:text-blue-400",
|
color: "text-info",
|
||||||
url: `${GITHUB_DISCUSSIONS_BASE}?category=q-a`,
|
url: `${GITHUB_DISCUSSIONS_BASE}?category=q-a`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -365,7 +365,7 @@ export function AnticipateInstallmentsDialog({
|
|||||||
{Number(formState.discount) > 0 && (
|
{Number(formState.discount) > 0 && (
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<dt className="text-muted-foreground">Desconto</dt>
|
<dt className="text-muted-foreground">Desconto</dt>
|
||||||
<dd className="font-medium tabular-nums text-green-600">
|
<dd className="font-medium tabular-nums text-success">
|
||||||
-{" "}
|
-{" "}
|
||||||
<MoneyValues
|
<MoneyValues
|
||||||
amount={Number(formState.discount)}
|
amount={Number(formState.discount)}
|
||||||
|
|||||||
@@ -49,9 +49,9 @@ export function CategoriaSelectContent({
|
|||||||
|
|
||||||
export function TransactionTypeSelectContent({ label }: { label: string }) {
|
export function TransactionTypeSelectContent({ label }: { label: string }) {
|
||||||
const colorMap: Record<string, string> = {
|
const colorMap: Record<string, string> = {
|
||||||
Receita: "bg-emerald-600 dark:bg-emerald-400",
|
Receita: "bg-success",
|
||||||
Despesa: "bg-red-600 dark:bg-red-400",
|
Despesa: "bg-destructive",
|
||||||
Transferência: "bg-blue-600 dark:bg-blue-400",
|
Transferência: "bg-info",
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export function AnticipationCard({
|
|||||||
{Number(anticipation.discount) > 0 && (
|
{Number(anticipation.discount) > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<dt className="text-muted-foreground">Desconto</dt>
|
<dt className="text-muted-foreground">Desconto</dt>
|
||||||
<dd className="mt-1 font-medium tabular-nums text-green-600">
|
<dd className="mt-1 font-medium tabular-nums text-success">
|
||||||
- <MoneyValues amount={Number(anticipation.discount)} />
|
- <MoneyValues amount={Number(anticipation.discount)} />
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function EstabelecimentoInput({
|
|||||||
}, [estabelecimentos, searchValue]);
|
}, [estabelecimentos, searchValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen} modal>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input
|
<Input
|
||||||
@@ -112,7 +112,7 @@ export function EstabelecimentoInput({
|
|||||||
className={cn(
|
className={cn(
|
||||||
"size-4 shrink-0",
|
"size-4 shrink-0",
|
||||||
value === item
|
value === item
|
||||||
? "opacity-100 text-green-500"
|
? "opacity-100 text-success"
|
||||||
: "opacity-5",
|
: "opacity-5",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function InstallmentTimeline({
|
|||||||
{/* Linha de conexão */}
|
{/* Linha de conexão */}
|
||||||
<div className="absolute left-0 right-0 top-6 h-0.5 bg-border">
|
<div className="absolute left-0 right-0 top-6 h-0.5 bg-border">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-green-600 transition-all duration-300"
|
className="h-full bg-success transition-all duration-300"
|
||||||
style={{
|
style={{
|
||||||
width: `${
|
width: `${
|
||||||
((currentInstallment - 1) / (totalInstallments - 1)) * 100
|
((currentInstallment - 1) / (totalInstallments - 1)) * 100
|
||||||
@@ -41,7 +41,7 @@ export function InstallmentTimeline({
|
|||||||
|
|
||||||
{/* Ponto 1: Data de Compra */}
|
{/* Ponto 1: Data de Compra */}
|
||||||
<div className="relative z-10 flex flex-col items-center gap-2">
|
<div className="relative z-10 flex flex-col items-center gap-2">
|
||||||
<div className="flex size-4 items-center justify-center rounded-full border-2 border-green-600 bg-green-600 shadow-sm">
|
<div className="flex size-4 items-center justify-center rounded-full border-2 border-success bg-success shadow-sm">
|
||||||
<RiCheckLine className="size-5 text-white" />
|
<RiCheckLine className="size-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
@@ -57,7 +57,7 @@ export function InstallmentTimeline({
|
|||||||
{/* Ponto 2: Parcela Atual */}
|
{/* Ponto 2: Parcela Atual */}
|
||||||
<div className="relative z-10 flex flex-col items-center gap-2">
|
<div className="relative z-10 flex flex-col items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`flex size-4 items-center justify-center rounded-full border-2 shadow-sm border-orange-600 bg-orange-600`}
|
className={`flex size-4 items-center justify-center rounded-full border-2 shadow-sm border-warning bg-warning`}
|
||||||
>
|
>
|
||||||
<RiArrowDownFill className="size-5 text-white" />
|
<RiArrowDownFill className="size-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@ export function InstallmentTimeline({
|
|||||||
{/* Ponto 3: Última Parcela */}
|
{/* Ponto 3: Última Parcela */}
|
||||||
<div className="relative z-10 flex flex-col items-center gap-2">
|
<div className="relative z-10 flex flex-col items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`flex size-4 items-center justify-center rounded-full border-2 shadow-sm border-green-600 bg-green-600`}
|
className={`flex size-4 items-center justify-center rounded-full border-2 shadow-sm border-success bg-success`}
|
||||||
>
|
>
|
||||||
<RiCheckLine className="size-5 text-white" />
|
<RiCheckLine className="size-5 text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -388,7 +388,11 @@ export function LancamentosFilters({
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium">Categoria</label>
|
<label className="text-sm font-medium">Categoria</label>
|
||||||
<Popover open={categoriaOpen} onOpenChange={setCategoriaOpen}>
|
<Popover
|
||||||
|
open={categoriaOpen}
|
||||||
|
onOpenChange={setCategoriaOpen}
|
||||||
|
modal
|
||||||
|
>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -324,10 +324,8 @@ const buildColumns = ({
|
|||||||
showPositiveSign={isReceita}
|
showPositiveSign={isReceita}
|
||||||
className={cn(
|
className={cn(
|
||||||
"whitespace-nowrap",
|
"whitespace-nowrap",
|
||||||
isReceita
|
isReceita ? "text-success" : "text-foreground",
|
||||||
? "text-green-600 dark:text-green-400"
|
isTransfer && "text-info",
|
||||||
: "text-foreground",
|
|
||||||
isTransfer && "text-blue-700 dark:text-blue-500",
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -545,7 +543,7 @@ const buildColumns = ({
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
) : (
|
) : (
|
||||||
<Icon className={cn("size-4", settled && "text-green-600")} />
|
<Icon className={cn("size-4", settled && "text-success")} />
|
||||||
)}
|
)}
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
{settled ? "Desfazer pagamento" : "Marcar como pago"}
|
{settled ? "Desfazer pagamento" : "Marcar como pago"}
|
||||||
@@ -631,7 +629,7 @@ const buildColumns = ({
|
|||||||
|
|
||||||
{row.original.isAnticipated && (
|
{row.original.isAnticipated && (
|
||||||
<DropdownMenuItem disabled>
|
<DropdownMenuItem disabled>
|
||||||
<RiCheckLine className="size-4 text-green-500" />
|
<RiCheckLine className="size-4 text-success" />
|
||||||
Parcela Antecipada
|
Parcela Antecipada
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
@@ -799,7 +797,7 @@ export function LancamentosTable({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<RiAddCircleLine className="size-4 text-green-500" />
|
<RiAddCircleLine className="size-4 text-success" />
|
||||||
Nova Receita
|
Nova Receita
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
@@ -807,7 +805,7 @@ export function LancamentosTable({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full sm:w-auto"
|
className="w-full sm:w-auto"
|
||||||
>
|
>
|
||||||
<RiAddCircleLine className="size-4 text-red-500" />
|
<RiAddCircleLine className="size-4 text-destructive" />
|
||||||
Nova Despesa
|
Nova Despesa
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export default function MonthNavigation() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="sticky top-0 z-30 w-full flex-row bg-month-picker text-month-picker-foreground p-4">
|
<Card className="sticky top-0 z-30 w-full flex-row bg-card text-card-foreground p-4">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<NavigationButton
|
<NavigationButton
|
||||||
direction="left"
|
direction="left"
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export default function NavigationButton({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="text-month-picker-foreground transition-all duration-200 cursor-pointer rounded-lg p-1 hover:bg-month-picker-foreground/10 focus:outline-hidden focus:ring-2 focus:ring-month-picker-foreground/30 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent"
|
className="text-card-foreground transition-all duration-200 cursor-pointer rounded-lg p-1 hover:bg-card-foreground/10 focus:outline-hidden focus:ring-2 focus:ring-card-foreground/30 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
aria-label={`Navegar para o mês ${
|
aria-label={`Navegar para o mês ${
|
||||||
direction === "left" ? "anterior" : "seguinte"
|
direction === "left" ? "anterior" : "seguinte"
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiDeleteBin5Line, RiPencilLine } from "@remixicon/react";
|
import {
|
||||||
|
RiDeleteBin5Line,
|
||||||
|
RiFileList2Line,
|
||||||
|
RiPencilLine,
|
||||||
|
} from "@remixicon/react";
|
||||||
|
import Link from "next/link";
|
||||||
import { CategoryIconBadge } from "@/components/categorias/category-icon-badge";
|
import { CategoryIconBadge } from "@/components/categorias/category-icon-badge";
|
||||||
import MoneyValues from "@/components/money-values";
|
import MoneyValues from "@/components/money-values";
|
||||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||||
@@ -78,11 +83,11 @@ export function BudgetCard({
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
{exceeded ? (
|
{exceeded ? (
|
||||||
<div className="text-xs text-red-500">
|
<div className="text-xs text-destructive">
|
||||||
Excedeu em <MoneyValues amount={difference} />
|
Excedeu em <MoneyValues amount={difference} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-xs text-green-600">
|
<div className="text-xs text-success">
|
||||||
Restam <MoneyValues amount={Math.max(limit - spent, 0)} />{" "}
|
Restam <MoneyValues amount={Math.max(limit - spent, 0)} />{" "}
|
||||||
disponíveis.
|
disponíveis.
|
||||||
</div>
|
</div>
|
||||||
@@ -98,6 +103,14 @@ export function BudgetCard({
|
|||||||
>
|
>
|
||||||
<RiPencilLine className="size-4" aria-hidden /> editar
|
<RiPencilLine className="size-4" aria-hidden /> editar
|
||||||
</button>
|
</button>
|
||||||
|
{budget.category && (
|
||||||
|
<Link
|
||||||
|
href={`/categorias/${budget.category.id}`}
|
||||||
|
className="flex items-center gap-1 text-primary font-medium transition-opacity hover:opacity-80"
|
||||||
|
>
|
||||||
|
<RiFileList2Line className="size-4" aria-hidden /> detalhes
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onRemove(budget)}
|
onClick={() => onRemove(budget)}
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export function PagadorInfoCard({
|
|||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-xs font-medium text-amber-600">
|
<span className="text-xs font-medium text-warning">
|
||||||
Acesso somente leitura
|
Acesso somente leitura
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -247,7 +247,7 @@ export function PagadorInfoCard({
|
|||||||
<InfoItem
|
<InfoItem
|
||||||
label="Aviso"
|
label="Aviso"
|
||||||
value={
|
value={
|
||||||
<span className="text-[13px] text-amber-700">
|
<span className="text-[13px] text-warning">
|
||||||
Cadastre um e-mail para permitir o envio automático.
|
Cadastre um e-mail para permitir o envio automático.
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
@@ -397,7 +397,7 @@ export function PagadorInfoCard({
|
|||||||
<div className="grid gap-2 sm:grid-cols-2">
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">Pagos</p>
|
<p className="text-xs text-muted-foreground">Pagos</p>
|
||||||
<p className="text-sm font-semibold text-green-600">
|
<p className="text-sm font-semibold text-success">
|
||||||
{formatCurrency(summary.boletoStats.paidAmount)}{" "}
|
{formatCurrency(summary.boletoStats.paidAmount)}{" "}
|
||||||
<span className="text-xs font-normal">
|
<span className="text-xs font-normal">
|
||||||
({summary.boletoStats.paidCount})
|
({summary.boletoStats.paidCount})
|
||||||
@@ -408,7 +408,7 @@ export function PagadorInfoCard({
|
|||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
Pendentes
|
Pendentes
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm font-semibold text-amber-600">
|
<p className="text-sm font-semibold text-warning">
|
||||||
{formatCurrency(summary.boletoStats.pendingAmount)}{" "}
|
{formatCurrency(summary.boletoStats.pendingAmount)}{" "}
|
||||||
<span className="text-xs font-normal">
|
<span className="text-xs font-normal">
|
||||||
({summary.boletoStats.pendingCount})
|
({summary.boletoStats.pendingCount})
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ function StatusRow({ label, amount, count, percent, tone }: StatusRowProps) {
|
|||||||
amount={amount}
|
amount={amount}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xl font-semibold",
|
"text-xl font-semibold",
|
||||||
tone === "success" ? "text-emerald-600" : "text-amber-600",
|
tone === "success" ? "text-success" : "text-warning",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||||
@@ -95,7 +95,7 @@ function StatusRow({ label, amount, count, percent, tone }: StatusRowProps) {
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-full rounded-full",
|
"h-full rounded-full",
|
||||||
tone === "success" ? "bg-emerald-500" : "bg-amber-500",
|
tone === "success" ? "bg-success" : "bg-warning",
|
||||||
)}
|
)}
|
||||||
style={{ width: `${clampedPercent}%` }}
|
style={{ width: `${clampedPercent}%` }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function PagadorCard({ pagador, onEdit, onRemove }: PagadorCardProps) {
|
|||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
{isReadOnly ? (
|
{isReadOnly ? (
|
||||||
<Badge variant="outline" className="text-xs text-amber-600">
|
<Badge variant="outline" className="text-xs text-warning">
|
||||||
Somente leitura
|
Somente leitura
|
||||||
</Badge>
|
</Badge>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ export function StatusSelectContent({ label }: { label: string }) {
|
|||||||
return (
|
return (
|
||||||
<span className="flex items-center gap-2">
|
<span className="flex items-center gap-2">
|
||||||
<DotIcon
|
<DotIcon
|
||||||
color={
|
color={isActive ? "bg-success" : "bg-slate-400 dark:bg-slate-500"}
|
||||||
isActive
|
|
||||||
? "bg-emerald-600 dark:bg-emerald-300"
|
|
||||||
: "bg-slate-400 dark:bg-slate-500"
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -43,11 +43,11 @@ export function CardInvoiceStatus({ data }: CardInvoiceStatusProps) {
|
|||||||
const getStatusColor = (status: string | null) => {
|
const getStatusColor = (status: string | null) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "pago":
|
case "pago":
|
||||||
return "bg-green-500";
|
return "bg-success";
|
||||||
case "pendente":
|
case "pendente":
|
||||||
return "bg-yellow-500";
|
return "bg-warning";
|
||||||
case "atrasado":
|
case "atrasado":
|
||||||
return "bg-red-500";
|
return "bg-destructive";
|
||||||
default:
|
default:
|
||||||
return "bg-muted";
|
return "bg-muted";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export function CardUsageChart({ data, limit, card }: CardUsageChartProps) {
|
|||||||
label={{
|
label={{
|
||||||
value: "Limite",
|
value: "Limite",
|
||||||
position: "right",
|
position: "right",
|
||||||
className: "text-xs fill-red-500",
|
className: "text-xs fill-destructive",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -58,9 +58,9 @@ export function CardsOverview({ data }: CardsOverviewProps) {
|
|||||||
}).format(value);
|
}).format(value);
|
||||||
|
|
||||||
const getUsageColor = (percent: number) => {
|
const getUsageColor = (percent: number) => {
|
||||||
if (percent < 50) return "bg-green-500";
|
if (percent < 50) return "bg-success";
|
||||||
if (percent < 80) return "bg-yellow-500";
|
if (percent < 80) return "bg-warning";
|
||||||
return "bg-red-500";
|
return "bg-destructive";
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildUrl = (cardId: string) => {
|
const buildUrl = (cardId: string) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { RiArrowDownLine, RiArrowUpLine } from "@remixicon/react";
|
import { RiArrowDownSFill, RiArrowUpSFill } from "@remixicon/react";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
@@ -41,12 +41,12 @@ export function CategoryCell({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center gap-0.5 text-xs",
|
"flex items-center gap-0.5 text-xs",
|
||||||
isIncrease && "text-red-600 dark:text-red-400",
|
isIncrease && "text-destructive",
|
||||||
isDecrease && "text-green-600 dark:text-green-400",
|
isDecrease && "text-success",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isIncrease && <RiArrowUpLine className="h-3 w-3" />}
|
{isIncrease && <RiArrowUpSFill className="h-3 w-3" />}
|
||||||
{isDecrease && <RiArrowDownLine className="h-3 w-3" />}
|
{isDecrease && <RiArrowDownSFill className="h-3 w-3" />}
|
||||||
<span>{formatPercentageChange(percentageChange)}</span>
|
<span>{formatPercentageChange(percentageChange)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -63,8 +63,8 @@ export function CategoryCell({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"font-medium",
|
"font-medium",
|
||||||
isIncrease && "text-red-500",
|
isIncrease && "text-destructive",
|
||||||
isDecrease && "text-green-500",
|
isDecrease && "text-success",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
Diferença:{" "}
|
Diferença:{" "}
|
||||||
|
|||||||
@@ -15,23 +15,13 @@ import { EmptyState } from "@/components/empty-state";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers";
|
import { currencyFormatter } from "@/lib/lancamentos/formatting-helpers";
|
||||||
import type { CategoryChartData } from "@/lib/relatorios/fetch-category-chart-data";
|
import type { CategoryChartData } from "@/lib/relatorios/fetch-category-chart-data";
|
||||||
|
import { CATEGORY_COLORS } from "@/lib/utils/category-colors";
|
||||||
|
|
||||||
interface CategoryReportChartProps {
|
interface CategoryReportChartProps {
|
||||||
data: CategoryChartData;
|
data: CategoryChartData;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CHART_COLORS = [
|
const CHART_COLORS = CATEGORY_COLORS;
|
||||||
"#ef4444", // red-500
|
|
||||||
"#3b82f6", // blue-500
|
|
||||||
"#10b981", // emerald-500
|
|
||||||
"#f59e0b", // amber-500
|
|
||||||
"#8b5cf6", // violet-500
|
|
||||||
"#ec4899", // pink-500
|
|
||||||
"#14b8a6", // teal-500
|
|
||||||
"#f97316", // orange-500
|
|
||||||
"#06b6d4", // cyan-500
|
|
||||||
"#84cc16", // lime-500
|
|
||||||
];
|
|
||||||
|
|
||||||
const MAX_CATEGORIES_IN_CHART = 15;
|
const MAX_CATEGORIES_IN_CHART = 15;
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export function CategoryReportFilters({
|
|||||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{/* Category Multi-Select */}
|
{/* Category Multi-Select */}
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen} modal>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
|
|||||||
@@ -87,8 +87,8 @@ export function CategoryTable({
|
|||||||
<DotIcon
|
<DotIcon
|
||||||
color={
|
color={
|
||||||
category.type === "receita"
|
category.type === "receita"
|
||||||
? "bg-green-600"
|
? "bg-success"
|
||||||
: "bg-red-600"
|
: "bg-destructive"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -36,16 +36,16 @@ export function TypeBadge({ type, className }: TypeBadgeProps) {
|
|||||||
const label = TYPE_LABELS[type] || type;
|
const label = TYPE_LABELS[type] || type;
|
||||||
|
|
||||||
const colorClass = isTransferencia
|
const colorClass = isTransferencia
|
||||||
? "text-blue-700 dark:text-blue-400"
|
? "text-info"
|
||||||
: isReceita || isSaldoInicial
|
: isReceita || isSaldoInicial
|
||||||
? "text-green-700 dark:text-green-400"
|
? "text-success"
|
||||||
: "text-red-700 dark:text-red-400";
|
: "text-destructive";
|
||||||
|
|
||||||
const dotColor = isTransferencia
|
const dotColor = isTransferencia
|
||||||
? "bg-blue-700 dark:bg-blue-400"
|
? "bg-info"
|
||||||
: isReceita || isSaldoInicial
|
: isReceita || isSaldoInicial
|
||||||
? "bg-green-600 dark:bg-green-400"
|
? "bg-success"
|
||||||
: "bg-red-600 dark:bg-red-400";
|
: "bg-destructive";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge
|
<Badge
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ const badgeVariants = cva(
|
|||||||
outline:
|
outline:
|
||||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
success:
|
success:
|
||||||
"border-transparent bg-green-500 text-white [a&]:hover:bg-green-600 dark:bg-green-600 dark:[a&]:hover:bg-green-700",
|
"border-transparent bg-success text-success-foreground [a&]:hover:bg-success/90 dark:bg-success dark:[a&]:hover:bg-success/90",
|
||||||
info: "border-transparent bg-blue-500 text-white [a&]:hover:bg-blue-600 dark:bg-blue-600 dark:[a&]:hover:bg-blue-700",
|
info: "border-transparent bg-info text-info-foreground [a&]:hover:bg-info/90 dark:bg-info dark:[a&]:hover:bg-info/90",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export function handleActionError(error: unknown): ActionResult {
|
|||||||
* Configuration for revalidation after mutations
|
* Configuration for revalidation after mutations
|
||||||
*/
|
*/
|
||||||
export const revalidateConfig = {
|
export const revalidateConfig = {
|
||||||
cartoes: ["/cartoes"],
|
cartoes: ["/cartoes", "/contas", "/lancamentos"],
|
||||||
contas: ["/contas", "/lancamentos"],
|
contas: ["/contas", "/lancamentos"],
|
||||||
categorias: ["/categorias"],
|
categorias: ["/categorias"],
|
||||||
orcamentos: ["/orcamentos"],
|
orcamentos: ["/orcamentos"],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ACCOUNT_AUTO_INVOICE_NOTE_PREFIX } from "@/lib/accounts/constants";
|
|||||||
import { toNumber } from "@/lib/dashboard/common";
|
import { toNumber } from "@/lib/dashboard/common";
|
||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
|
import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
|
||||||
|
import { CATEGORY_COLORS } from "@/lib/utils/category-colors";
|
||||||
|
|
||||||
export type CategoryOption = {
|
export type CategoryOption = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -32,13 +33,7 @@ export type CategoryHistoryData = {
|
|||||||
allCategories: CategoryOption[];
|
allCategories: CategoryOption[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHART_COLORS = [
|
const CHART_COLORS = CATEGORY_COLORS;
|
||||||
"#ef4444", // red-500
|
|
||||||
"#3b82f6", // blue-500
|
|
||||||
"#10b981", // emerald-500
|
|
||||||
"#f59e0b", // amber-500
|
|
||||||
"#8b5cf6", // violet-500
|
|
||||||
];
|
|
||||||
|
|
||||||
export async function fetchAllCategories(
|
export async function fetchAllCategories(
|
||||||
userId: string,
|
userId: string,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "opensheets",
|
"name": "opensheets",
|
||||||
"version": "1.3.1",
|
"version": "1.4.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
|
|||||||
Reference in New Issue
Block a user