forked from git.gladyson/openmonetis
Remove unused font file and update font index; initialize database extensions with improved error handling; add EstabelecimentoLogo component for dynamic logo generation.
This commit is contained in:
@@ -2,10 +2,13 @@
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
||||
import { RiDeleteBin5Line, RiEyeLine, RiPencilLine } from "@remixicon/react";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import {
|
||||
RiCheckLine,
|
||||
RiDeleteBin5Line,
|
||||
RiEyeLine,
|
||||
RiPencilLine,
|
||||
} from "@remixicon/react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import type { Note } from "./types";
|
||||
|
||||
const DATE_FORMATTER = new Intl.DateTimeFormat("pt-BR", {
|
||||
@@ -88,7 +91,7 @@ export function NoteCard({ note, onEdit, onDetails, onRemove }: NoteCardProps) {
|
||||
}`}
|
||||
>
|
||||
{task.completed && (
|
||||
<CheckIcon className="h-3 w-3 text-background" />
|
||||
<RiCheckLine className="h-3 w-3 text-background" />
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
|
||||
@@ -11,9 +11,8 @@ import {
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
import { RiCheckLine } from "@remixicon/react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import type { Note } from "./types";
|
||||
|
||||
const DATE_FORMATTER = new Intl.DateTimeFormat("pt-BR", {
|
||||
@@ -84,7 +83,7 @@ export function NoteDetailsDialog({
|
||||
}`}
|
||||
>
|
||||
{task.completed && (
|
||||
<CheckIcon className="h-4 w-4 text-primary-foreground" />
|
||||
<RiCheckLine className="h-4 w-4 text-primary-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<span
|
||||
|
||||
@@ -20,7 +20,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useControlledState } from "@/hooks/use-controlled-state";
|
||||
import { useFormState } from "@/hooks/use-form-state";
|
||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import { RiAddLine, RiDeleteBinLine } from "@remixicon/react";
|
||||
import {
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
@@ -388,7 +388,7 @@ export function NoteDialog({
|
||||
disabled={isPending || !normalize(newTaskText)}
|
||||
className="shrink-0"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
<RiAddLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
@@ -434,7 +434,7 @@ export function NoteDialog({
|
||||
className="h-8 w-8 p-0 shrink-0 text-muted-foreground hover:text-destructive"
|
||||
aria-label={`Remover tarefa "${task.text}"`}
|
||||
>
|
||||
<Trash2Icon className="h-4 w-4" />
|
||||
<RiDeleteBinLine className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { toggleLancamentoSettlementAction } from "@/app/(dashboard)/lancamentos/actions";
|
||||
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
||||
import MoneyValues from "@/components/money-values";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
@@ -171,9 +172,7 @@ export function BoletosWidget({ boletos }: BoletosWidgetProps) {
|
||||
className="flex items-center justify-between border-b border-dashed last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3 py-2">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted">
|
||||
<RiBarcodeFill className="size-5" />
|
||||
</div>
|
||||
<EstabelecimentoLogo name={boleto.name} size={38} />
|
||||
|
||||
<div className="min-w-0">
|
||||
<span className="block truncate text-sm font-medium text-foreground">
|
||||
|
||||
@@ -22,8 +22,11 @@ import {
|
||||
import { WidgetEmptyState } from "@/components/widget-empty-state";
|
||||
import type { CategoryHistoryData } from "@/lib/dashboard/categories/category-history";
|
||||
import { getIconComponent } from "@/lib/utils/icons";
|
||||
import { RiBarChartBoxLine, RiCloseLine } from "@remixicon/react";
|
||||
import { ChevronDownIcon } from "lucide-react";
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiBarChartBoxLine,
|
||||
RiCloseLine,
|
||||
} from "@remixicon/react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
||||
|
||||
@@ -265,7 +268,7 @@ export function CategoryHistoryWidget({ data }: CategoryHistoryWidgetProps) {
|
||||
className="w-full justify-between hover:scale-none"
|
||||
>
|
||||
Selecionar categorias
|
||||
<ChevronDownIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<RiArrowDownSLine className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
|
||||
@@ -134,9 +134,9 @@ export function InstallmentExpensesWidget({
|
||||
return (
|
||||
<li
|
||||
key={expense.id}
|
||||
className="flex items-center gap-3 border-b border-dashed pb-2 last:border-b-0 last:pb-0"
|
||||
className="flex items-center gap-3 border-b border-dashed pb-3 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="min-w-0 flex-1 ">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
@@ -169,17 +169,17 @@ export function InstallmentExpensesWidget({
|
||||
</div>
|
||||
<MoneyValues amount={expense.amount} className="shrink-0" />
|
||||
</div>
|
||||
<Progress value={progress} className="h-2" />
|
||||
|
||||
<p className="text-xs text-muted-foreground mt-1">
|
||||
Restantes {remainingInstallments}
|
||||
{endDate && ` - Termina em ${endDate}`}
|
||||
{" - Restante "}
|
||||
<p className="text-xs text-muted-foreground ">
|
||||
{endDate && `Termina em ${endDate}`}
|
||||
{` - Restante (${remainingInstallments}) `}
|
||||
<MoneyValues
|
||||
amount={remainingAmount}
|
||||
className="inline-block font-medium"
|
||||
/>
|
||||
</p>
|
||||
|
||||
<Progress value={progress} className="h-2 mt-1" />
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
|
||||
@@ -369,7 +369,7 @@ export function InvoicesWidget({ invoices }: InvoicesWidgetProps) {
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 flex-col items-end">
|
||||
<MoneyValues amount={invoice.totalAmount} />
|
||||
<MoneyValues amount={Math.abs(invoice.totalAmount)} />
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -504,7 +504,7 @@ export function InvoicesWidget({ invoices }: InvoicesWidgetProps) {
|
||||
Valor da fatura
|
||||
</span>
|
||||
<MoneyValues
|
||||
amount={selectedInvoice.totalAmount}
|
||||
amount={Math.abs(selectedInvoice.totalAmount)}
|
||||
className="text-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
||||
import MoneyValues from "@/components/money-values";
|
||||
import {
|
||||
Select,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
import { CATEGORY_TYPE_LABEL } from "@/lib/categorias/constants";
|
||||
import type { PurchasesByCategoryData } from "@/lib/dashboard/purchases-by-category";
|
||||
import { RiArrowDownLine, RiStore3Line } from "@remixicon/react";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { WidgetEmptyState } from "../widget-empty-state";
|
||||
|
||||
@@ -19,30 +19,6 @@ type PurchasesByCategoryWidgetProps = {
|
||||
data: PurchasesByCategoryData;
|
||||
};
|
||||
|
||||
const resolveLogoPath = (logo: string | null) => {
|
||||
if (!logo) {
|
||||
return null;
|
||||
}
|
||||
if (/^(https?:\/\/|data:)/.test(logo)) {
|
||||
return logo;
|
||||
}
|
||||
return logo.startsWith("/") ? logo : `/logos/${logo}`;
|
||||
};
|
||||
|
||||
const buildInitials = (value: string) => {
|
||||
const parts = value.trim().split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) {
|
||||
return "LC";
|
||||
}
|
||||
if (parts.length === 1) {
|
||||
const firstPart = parts[0];
|
||||
return firstPart ? firstPart.slice(0, 2).toUpperCase() : "LC";
|
||||
}
|
||||
const firstChar = parts[0]?.[0] ?? "";
|
||||
const secondChar = parts[1]?.[0] ?? "";
|
||||
return `${firstChar}${secondChar}`.toUpperCase() || "LC";
|
||||
};
|
||||
|
||||
const formatTransactionDate = (date: Date) => {
|
||||
const formatter = new Intl.DateTimeFormat("pt-BR", {
|
||||
weekday: "short",
|
||||
@@ -180,30 +156,13 @@ export function PurchasesByCategoryWidget({
|
||||
) : (
|
||||
<ul className="flex flex-col">
|
||||
{currentTransactions.map((transaction) => {
|
||||
const logo = resolveLogoPath(transaction.logo);
|
||||
const initials = buildInitials(transaction.name);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={transaction.id}
|
||||
className="flex items-center justify-between gap-3 border-b border-dashed py-2 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted">
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={`Logo de ${transaction.name}`}
|
||||
width={48}
|
||||
height={48}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-sm font-semibold uppercase text-muted-foreground">
|
||||
{initials}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<EstabelecimentoLogo name={transaction.name} size={38} />
|
||||
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
|
||||
@@ -1,37 +1,13 @@
|
||||
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
||||
import MoneyValues from "@/components/money-values";
|
||||
import type { RecentTransactionsData } from "@/lib/dashboard/recent-transactions";
|
||||
import { RiExchangeLine } from "@remixicon/react";
|
||||
import Image from "next/image";
|
||||
import { WidgetEmptyState } from "../widget-empty-state";
|
||||
|
||||
type RecentTransactionsWidgetProps = {
|
||||
data: RecentTransactionsData;
|
||||
};
|
||||
|
||||
const resolveLogoPath = (logo: string | null) => {
|
||||
if (!logo) {
|
||||
return null;
|
||||
}
|
||||
if (/^(https?:\/\/|data:)/.test(logo)) {
|
||||
return logo;
|
||||
}
|
||||
return logo.startsWith("/") ? logo : `/logos/${logo}`;
|
||||
};
|
||||
|
||||
const buildInitials = (value: string) => {
|
||||
const parts = value.trim().split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) {
|
||||
return "LC";
|
||||
}
|
||||
if (parts.length === 1) {
|
||||
const firstPart = parts[0];
|
||||
return firstPart ? firstPart.slice(0, 2).toUpperCase() : "LC";
|
||||
}
|
||||
const firstChar = parts[0]?.[0] ?? "";
|
||||
const secondChar = parts[1]?.[0] ?? "";
|
||||
return `${firstChar}${secondChar}`.toUpperCase() || "LC";
|
||||
};
|
||||
|
||||
const formatTransactionDate = (date: Date) => {
|
||||
const formatter = new Intl.DateTimeFormat("pt-BR", {
|
||||
weekday: "short",
|
||||
@@ -59,32 +35,13 @@ export function RecentTransactionsWidget({
|
||||
) : (
|
||||
<ul className="flex flex-col">
|
||||
{data.transactions.map((transaction) => {
|
||||
const logo = resolveLogoPath(
|
||||
transaction.cardLogo ?? transaction.accountLogo
|
||||
);
|
||||
const initials = buildInitials(transaction.name);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={transaction.id}
|
||||
className="flex items-center justify-between gap-3 border-b border-dashed py-2 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg">
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={`Logo de ${transaction.name}`}
|
||||
width={48}
|
||||
height={48}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-sm font-semibold uppercase text-muted-foreground">
|
||||
{initials}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<EstabelecimentoLogo name={transaction.name} size={38} />
|
||||
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
||||
import MoneyValues from "@/components/money-values";
|
||||
import { CardContent } from "@/components/ui/card";
|
||||
import type { RecurringExpensesData } from "@/lib/dashboard/expenses/recurring-expenses";
|
||||
@@ -38,9 +39,7 @@ export function RecurringExpensesWidget({
|
||||
key={expense.id}
|
||||
className="flex items-start gap-3 border-b border-dashed pb-2 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted">
|
||||
<RiRefreshLine className="size-5 text-foreground" />
|
||||
</div>
|
||||
<EstabelecimentoLogo name={expense.name} size={38} />
|
||||
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
|
||||
@@ -1,37 +1,13 @@
|
||||
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
||||
import MoneyValues from "@/components/money-values";
|
||||
import type { TopEstablishmentsData } from "@/lib/dashboard/top-establishments";
|
||||
import { RiStore2Line } from "@remixicon/react";
|
||||
import Image from "next/image";
|
||||
import { WidgetEmptyState } from "../widget-empty-state";
|
||||
|
||||
type TopEstablishmentsWidgetProps = {
|
||||
data: TopEstablishmentsData;
|
||||
};
|
||||
|
||||
const resolveLogoPath = (logo: string | null) => {
|
||||
if (!logo) {
|
||||
return null;
|
||||
}
|
||||
if (/^(https?:\/\/|data:)/.test(logo)) {
|
||||
return logo;
|
||||
}
|
||||
return logo.startsWith("/") ? logo : `/logos/${logo}`;
|
||||
};
|
||||
|
||||
const buildInitials = (value: string) => {
|
||||
const parts = value.trim().split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) {
|
||||
return "LC";
|
||||
}
|
||||
if (parts.length === 1) {
|
||||
const firstPart = parts[0];
|
||||
return firstPart ? firstPart.slice(0, 2).toUpperCase() : "LC";
|
||||
}
|
||||
const firstChar = parts[0]?.[0] ?? "";
|
||||
const secondChar = parts[1]?.[0] ?? "";
|
||||
return `${firstChar}${secondChar}`.toUpperCase() || "LC";
|
||||
};
|
||||
|
||||
const formatOccurrencesLabel = (occurrences: number) => {
|
||||
if (occurrences === 1) {
|
||||
return "1 lançamento";
|
||||
@@ -53,30 +29,13 @@ export function TopEstablishmentsWidget({
|
||||
) : (
|
||||
<ul className="flex flex-col">
|
||||
{data.establishments.map((establishment) => {
|
||||
const logo = resolveLogoPath(establishment.logo);
|
||||
const initials = buildInitials(establishment.name);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={establishment.id}
|
||||
className="flex items-center justify-between gap-3 border-b border-dashed py-2 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted">
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={`Logo de ${establishment.name}`}
|
||||
width={48}
|
||||
height={48}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-sm font-semibold uppercase text-muted-foreground">
|
||||
{initials}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<EstabelecimentoLogo name={establishment.name} size={38} />
|
||||
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { EstabelecimentoLogo } from "@/components/lancamentos/shared/estabelecimento-logo";
|
||||
import MoneyValues from "@/components/money-values";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import type { TopExpense, TopExpensesData } from "@/lib/dashboard/expenses/top-expenses";
|
||||
import type {
|
||||
TopExpense,
|
||||
TopExpensesData,
|
||||
} from "@/lib/dashboard/expenses/top-expenses";
|
||||
import { RiArrowUpDoubleLine } from "@remixicon/react";
|
||||
import Image from "next/image";
|
||||
import { useMemo, useState } from "react";
|
||||
import { WidgetEmptyState } from "../widget-empty-state";
|
||||
|
||||
@@ -13,30 +16,6 @@ type TopExpensesWidgetProps = {
|
||||
cardOnlyExpenses: TopExpensesData;
|
||||
};
|
||||
|
||||
const resolveLogoPath = (logo: string | null) => {
|
||||
if (!logo) {
|
||||
return null;
|
||||
}
|
||||
if (/^(https?:\/\/|data:)/.test(logo)) {
|
||||
return logo;
|
||||
}
|
||||
return logo.startsWith("/") ? logo : `/logos/${logo}`;
|
||||
};
|
||||
|
||||
const buildInitials = (value: string) => {
|
||||
const parts = value.trim().split(/\s+/).filter(Boolean);
|
||||
if (parts.length === 0) {
|
||||
return "LC";
|
||||
}
|
||||
if (parts.length === 1) {
|
||||
const firstPart = parts[0];
|
||||
return firstPart ? firstPart.slice(0, 2).toUpperCase() : "LC";
|
||||
}
|
||||
const firstChar = parts[0]?.[0] ?? "";
|
||||
const secondChar = parts[1]?.[0] ?? "";
|
||||
return `${firstChar}${secondChar}`.toUpperCase() || "LC";
|
||||
};
|
||||
|
||||
const formatTransactionDate = (date: Date) => {
|
||||
const formatter = new Intl.DateTimeFormat("pt-BR", {
|
||||
weekday: "short",
|
||||
@@ -129,30 +108,13 @@ export function TopExpensesWidget({
|
||||
) : (
|
||||
<ul className="flex flex-col">
|
||||
{data.expenses.map((expense) => {
|
||||
const logo = resolveLogoPath(expense.logo);
|
||||
const initials = buildInitials(expense.name);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={expense.id}
|
||||
className="flex items-center justify-between gap-3 border-b border-dashed py-2 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||
<div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted">
|
||||
{logo ? (
|
||||
<Image
|
||||
src={logo}
|
||||
alt={`Logo de ${expense.name}`}
|
||||
width={48}
|
||||
height={48}
|
||||
className="h-full w-full object-contain"
|
||||
/>
|
||||
) : (
|
||||
<span className="text-sm font-semibold uppercase text-muted-foreground">
|
||||
{initials}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<EstabelecimentoLogo name={expense.name} size={38} />
|
||||
|
||||
<div className="min-w-0">
|
||||
<p className="truncate text-sm font-medium text-foreground">
|
||||
|
||||
@@ -306,10 +306,17 @@ export function LancamentoDialog({
|
||||
]
|
||||
);
|
||||
|
||||
const title = mode === "create" ? "Novo lançamento" : "Editar lançamento";
|
||||
const isCopyMode = mode === "create" && Boolean(lancamento);
|
||||
const title = mode === "create"
|
||||
? isCopyMode
|
||||
? "Copiar lançamento"
|
||||
: "Novo lançamento"
|
||||
: "Editar lançamento";
|
||||
const description =
|
||||
mode === "create"
|
||||
? "Informe os dados abaixo para registrar um novo lançamento."
|
||||
? isCopyMode
|
||||
? "Os dados do lançamento foram copiados. Revise e ajuste conforme necessário antes de salvar."
|
||||
: "Informe os dados abaixo para registrar um novo lançamento."
|
||||
: "Atualize as informações do lançamento selecionado.";
|
||||
const submitLabel = mode === "create" ? "Salvar lançamento" : "Atualizar";
|
||||
|
||||
|
||||
@@ -69,6 +69,9 @@ export function LancamentosPage({
|
||||
useState<LancamentoItem | null>(null);
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [copyOpen, setCopyOpen] = useState(false);
|
||||
const [lancamentoToCopy, setLancamentoToCopy] =
|
||||
useState<LancamentoItem | null>(null);
|
||||
const [massAddOpen, setMassAddOpen] = useState(false);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [lancamentoToDelete, setLancamentoToDelete] =
|
||||
@@ -288,6 +291,11 @@ export function LancamentosPage({
|
||||
setEditOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleCopy = useCallback((item: LancamentoItem) => {
|
||||
setLancamentoToCopy(item);
|
||||
setCopyOpen(true);
|
||||
}, []);
|
||||
|
||||
const handleConfirmDelete = useCallback((item: LancamentoItem) => {
|
||||
if (item.seriesId) {
|
||||
setPendingDeleteData(item);
|
||||
@@ -323,6 +331,7 @@ export function LancamentosPage({
|
||||
onCreate={allowCreate ? handleCreate : undefined}
|
||||
onMassAdd={allowCreate ? handleMassAdd : undefined}
|
||||
onEdit={handleEdit}
|
||||
onCopy={handleCopy}
|
||||
onConfirmDelete={handleConfirmDelete}
|
||||
onBulkDelete={handleMultipleBulkDelete}
|
||||
onViewDetails={handleViewDetails}
|
||||
@@ -352,6 +361,26 @@ export function LancamentosPage({
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<LancamentoDialog
|
||||
mode="create"
|
||||
open={copyOpen && !!lancamentoToCopy}
|
||||
onOpenChange={(open) => {
|
||||
setCopyOpen(open);
|
||||
if (!open) {
|
||||
setLancamentoToCopy(null);
|
||||
}
|
||||
}}
|
||||
pagadorOptions={pagadorOptions}
|
||||
splitPagadorOptions={splitPagadorOptions}
|
||||
defaultPagadorId={defaultPagadorId}
|
||||
contaOptions={contaOptions}
|
||||
cartaoOptions={cartaoOptions}
|
||||
categoriaOptions={categoriaOptions}
|
||||
estabelecimentos={estabelecimentos}
|
||||
lancamento={lancamentoToCopy ?? undefined}
|
||||
defaultPeriod={selectedPeriod}
|
||||
/>
|
||||
|
||||
<LancamentoDialog
|
||||
mode="update"
|
||||
open={editOpen && !!selectedLancamento}
|
||||
|
||||
72
components/lancamentos/shared/estabelecimento-logo.tsx
Normal file
72
components/lancamentos/shared/estabelecimento-logo.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
|
||||
interface EstabelecimentoLogoProps {
|
||||
name: string;
|
||||
size?: number;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const COLOR_PALETTE = [
|
||||
"bg-purple-400 dark:bg-purple-600",
|
||||
"bg-pink-400 dark:bg-pink-600",
|
||||
"bg-red-400 dark:bg-red-600",
|
||||
"bg-orange-400 dark:bg-orange-600",
|
||||
"bg-indigo-400 dark:bg-indigo-600",
|
||||
"bg-violet-400 dark:bg-violet-600",
|
||||
"bg-fuchsia-400 dark:bg-fuchsia-600",
|
||||
"bg-rose-400 dark:bg-rose-600",
|
||||
"bg-amber-400 dark:bg-amber-600",
|
||||
"bg-emerald-400 dark:bg-emerald-600",
|
||||
];
|
||||
|
||||
function getInitials(name: string): string {
|
||||
if (!name || !name.trim()) return "?";
|
||||
|
||||
const words = name.trim().split(/\s+/);
|
||||
|
||||
if (words.length === 1) {
|
||||
return words[0]?.[0]?.toUpperCase() || "?";
|
||||
}
|
||||
|
||||
const firstInitial = words[0]?.[0]?.toUpperCase() || "";
|
||||
const secondInitial = words[1]?.[0]?.toUpperCase() || "";
|
||||
|
||||
return `${firstInitial}${secondInitial}`;
|
||||
}
|
||||
|
||||
function generateColorFromName(name: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < name.length; i++) {
|
||||
hash = name.charCodeAt(i) + ((hash << 5) - hash);
|
||||
}
|
||||
const index = Math.abs(hash) % COLOR_PALETTE.length;
|
||||
return COLOR_PALETTE[index] || "bg-gray-400";
|
||||
}
|
||||
|
||||
export function EstabelecimentoLogo({
|
||||
name,
|
||||
size = 32,
|
||||
className,
|
||||
}: EstabelecimentoLogoProps) {
|
||||
const initials = getInitials(name);
|
||||
const colorClass = generateColorFromName(name);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center rounded-md text-white font-medium shrink-0 ",
|
||||
colorClass,
|
||||
className
|
||||
)}
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
fontSize: size * 0.4,
|
||||
}}
|
||||
>
|
||||
{initials}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
4
components/lancamentos/shared/index.ts
Normal file
4
components/lancamentos/shared/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { AnticipationCard } from "./anticipation-card";
|
||||
export { EstabelecimentoInput } from "./estabelecimento-input";
|
||||
export { InstallmentTimeline } from "./installment-timeline";
|
||||
export { EstabelecimentoLogo } from "./estabelecimento-logo";
|
||||
@@ -1,15 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Command,
|
||||
@@ -39,15 +29,25 @@ import {
|
||||
LANCAMENTO_TRANSACTION_TYPES,
|
||||
} from "@/lib/lancamentos/constants";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useTransition,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import {
|
||||
TransactionTypeSelectContent,
|
||||
ConditionSelectContent,
|
||||
PaymentMethodSelectContent,
|
||||
CategoriaSelectContent,
|
||||
PagadorSelectContent,
|
||||
ConditionSelectContent,
|
||||
ContaCartaoSelectContent,
|
||||
PagadorSelectContent,
|
||||
PaymentMethodSelectContent,
|
||||
TransactionTypeSelectContent,
|
||||
} from "../select-items";
|
||||
|
||||
import { RiCheckLine, RiExpandUpDownLine } from "@remixicon/react";
|
||||
import type { ContaCartaoFilterOption, LancamentoFilterOption } from "../types";
|
||||
|
||||
const FILTER_EMPTY_VALUE = "__all";
|
||||
@@ -337,7 +337,7 @@ export function LancamentosFilters({
|
||||
"Categoria"
|
||||
)}
|
||||
</span>
|
||||
<ChevronsUpDownIcon className="ml-2 size-4 shrink-0 opacity-50" />
|
||||
<RiExpandUpDownLine className="ml-2 size-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" className="w-[220px] p-0">
|
||||
@@ -355,7 +355,7 @@ export function LancamentosFilters({
|
||||
>
|
||||
Todas
|
||||
{categoriaValue === FILTER_EMPTY_VALUE ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
<RiCheckLine className="ml-auto size-4" />
|
||||
) : null}
|
||||
</CommandItem>
|
||||
{categoriaOptions.map((option) => (
|
||||
@@ -372,7 +372,7 @@ export function LancamentosFilters({
|
||||
icon={option.icon}
|
||||
/>
|
||||
{categoriaValue === option.slug ? (
|
||||
<CheckIcon className="ml-auto size-4" />
|
||||
<RiCheckLine className="ml-auto size-4" />
|
||||
) : null}
|
||||
</CommandItem>
|
||||
))}
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
RiCheckLine,
|
||||
RiDeleteBin5Line,
|
||||
RiEyeLine,
|
||||
RiFileCopyLine,
|
||||
RiGroupLine,
|
||||
RiHistoryLine,
|
||||
RiMoreFill,
|
||||
@@ -72,6 +73,7 @@ import {
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useMemo, useState } from "react";
|
||||
import { EstabelecimentoLogo } from "../shared/estabelecimento-logo";
|
||||
import type {
|
||||
ContaCartaoFilterOption,
|
||||
LancamentoFilterOption,
|
||||
@@ -90,6 +92,7 @@ const resolveLogoSrc = (logo: string | null) => {
|
||||
|
||||
type BuildColumnsArgs = {
|
||||
onEdit?: (item: LancamentoItem) => void;
|
||||
onCopy?: (item: LancamentoItem) => void;
|
||||
onConfirmDelete?: (item: LancamentoItem) => void;
|
||||
onViewDetails?: (item: LancamentoItem) => void;
|
||||
onToggleSettlement?: (item: LancamentoItem) => void;
|
||||
@@ -101,6 +104,7 @@ type BuildColumnsArgs = {
|
||||
|
||||
const buildColumns = ({
|
||||
onEdit,
|
||||
onCopy,
|
||||
onConfirmDelete,
|
||||
onViewDetails,
|
||||
onToggleSettlement,
|
||||
@@ -111,6 +115,7 @@ const buildColumns = ({
|
||||
}: BuildColumnsArgs): ColumnDef<LancamentoItem>[] => {
|
||||
const noop = () => undefined;
|
||||
const handleEdit = onEdit ?? noop;
|
||||
const handleCopy = onCopy ?? noop;
|
||||
const handleConfirmDelete = onConfirmDelete ?? noop;
|
||||
const handleViewDetails = onViewDetails ?? noop;
|
||||
const handleToggleSettlement = onToggleSettlement ?? noop;
|
||||
@@ -180,6 +185,7 @@ const buildColumns = ({
|
||||
|
||||
return (
|
||||
<span className="flex items-center gap-2">
|
||||
<EstabelecimentoLogo name={name} size={28} />
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="line-clamp-2 max-w-[180px] font-bold truncate">
|
||||
@@ -522,6 +528,12 @@ const buildColumns = ({
|
||||
<RiPencilLine className="size-4" />
|
||||
Editar
|
||||
</DropdownMenuItem>
|
||||
{row.original.categoriaName !== "Pagamentos" && (
|
||||
<DropdownMenuItem onSelect={() => handleCopy(row.original)}>
|
||||
<RiFileCopyLine className="size-4" />
|
||||
Copiar
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem
|
||||
variant="destructive"
|
||||
onSelect={() => handleConfirmDelete(row.original)}
|
||||
@@ -583,6 +595,7 @@ type LancamentosTableProps = {
|
||||
onCreate?: () => void;
|
||||
onMassAdd?: () => void;
|
||||
onEdit?: (item: LancamentoItem) => void;
|
||||
onCopy?: (item: LancamentoItem) => void;
|
||||
onConfirmDelete?: (item: LancamentoItem) => void;
|
||||
onBulkDelete?: (items: LancamentoItem[]) => void;
|
||||
onViewDetails?: (item: LancamentoItem) => void;
|
||||
@@ -602,6 +615,7 @@ export function LancamentosTable({
|
||||
onCreate,
|
||||
onMassAdd,
|
||||
onEdit,
|
||||
onCopy,
|
||||
onConfirmDelete,
|
||||
onBulkDelete,
|
||||
onViewDetails,
|
||||
@@ -625,6 +639,7 @@ export function LancamentosTable({
|
||||
() =>
|
||||
buildColumns({
|
||||
onEdit,
|
||||
onCopy,
|
||||
onConfirmDelete,
|
||||
onViewDetails,
|
||||
onToggleSettlement,
|
||||
@@ -635,6 +650,7 @@ export function LancamentosTable({
|
||||
}),
|
||||
[
|
||||
onEdit,
|
||||
onCopy,
|
||||
onConfirmDelete,
|
||||
onViewDetails,
|
||||
onToggleSettlement,
|
||||
|
||||
@@ -16,6 +16,10 @@ import { PAGADOR_ROLE_ADMIN } from "@/lib/pagadores/constants";
|
||||
import { getAvatarSrc } from "@/lib/pagadores/utils";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import {
|
||||
RiBankCard2Line,
|
||||
RiBillLine,
|
||||
RiExchangeDollarLine,
|
||||
RiFileList3Line,
|
||||
RiMailLine,
|
||||
RiMailSendLine,
|
||||
RiUser3Line,
|
||||
@@ -272,74 +276,149 @@ export function PagadorInfoCard({
|
||||
setConfirmOpen(open);
|
||||
}}
|
||||
>
|
||||
<DialogContent className="max-w-lg">
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Confirmar envio do resumo</DialogTitle>
|
||||
<DialogDescription>
|
||||
O resumo de{" "}
|
||||
Resumo de{" "}
|
||||
<span className="font-semibold text-foreground">
|
||||
{summary.periodLabel}
|
||||
</span>{" "}
|
||||
será enviado para{" "}
|
||||
para{" "}
|
||||
<span className="font-medium text-foreground">
|
||||
{pagador.email ?? "—"}
|
||||
{pagador.email}
|
||||
</span>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-3 rounded-lg border border-dashed border-border/70 bg-muted/30 p-4 text-sm text-muted-foreground">
|
||||
<div>
|
||||
<span className="text-xs font-semibold uppercase text-muted-foreground/70">
|
||||
Totais do mês
|
||||
</span>
|
||||
<p className="text-foreground">
|
||||
{formatCurrency(summary.totalExpenses)} em despesas
|
||||
registradas
|
||||
</p>
|
||||
<p className="text-xs">
|
||||
Cartões: {formatCurrency(summary.paymentSplits.card)} -
|
||||
Boletos: {formatCurrency(summary.paymentSplits.boleto)} -
|
||||
Pix/Débito/Dinheiro:{" "}
|
||||
{formatCurrency(summary.paymentSplits.instant)}
|
||||
</p>
|
||||
<div className="space-y-4">
|
||||
{/* Total Geral */}
|
||||
<div className="rounded-lg border bg-muted/30 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex size-10 items-center justify-center rounded-full bg-primary/10">
|
||||
<RiExchangeDollarLine className="size-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
Total de Despesas
|
||||
</p>
|
||||
<p className="text-2xl font-bold text-foreground">
|
||||
{formatCurrency(summary.totalExpenses)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{summary.lancamentoCount} lançamentos
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span className="text-xs font-semibold uppercase text-muted-foreground/70">
|
||||
Principais cartões
|
||||
</span>
|
||||
<p>
|
||||
{summary.cardUsage.length
|
||||
? summary.cardUsage
|
||||
.map(
|
||||
(item) =>
|
||||
`${item.name}: ${formatCurrency(item.amount)}`
|
||||
)
|
||||
.join(" - ")
|
||||
: "Sem lançamentos com cartão no período."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-border/60 p-3">
|
||||
<div className="space-y-1">
|
||||
<span className="text-xs font-semibold uppercase text-muted-foreground/70">
|
||||
Boletos
|
||||
</span>
|
||||
<p>
|
||||
Pagos: {formatCurrency(summary.boletoStats.paidAmount)} (
|
||||
{summary.boletoStats.paidCount})
|
||||
{/* Grid de Formas de Pagamento */}
|
||||
<div className="grid gap-3 sm:grid-cols-3">
|
||||
{/* Cartões */}
|
||||
<div className="rounded-lg border bg-background p-3">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-2">
|
||||
<RiBankCard2Line className="size-4" />
|
||||
<span className="text-xs font-semibold uppercase">
|
||||
Cartões
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-lg font-bold text-foreground">
|
||||
{formatCurrency(summary.paymentSplits.card)}
|
||||
</p>
|
||||
<p>
|
||||
Pendentes:{" "}
|
||||
{formatCurrency(summary.boletoStats.pendingAmount)} (
|
||||
{summary.boletoStats.pendingCount})
|
||||
</div>
|
||||
|
||||
{/* Boletos */}
|
||||
<div className="rounded-lg border bg-background p-3">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-2">
|
||||
<RiBillLine className="size-4" />
|
||||
<span className="text-xs font-semibold uppercase">
|
||||
Boletos
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-lg font-bold text-foreground">
|
||||
{formatCurrency(summary.paymentSplits.boleto)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Instantâneo */}
|
||||
<div className="rounded-lg border bg-background p-3">
|
||||
<div className="flex items-center gap-2 text-muted-foreground mb-2">
|
||||
<RiExchangeDollarLine className="size-4" />
|
||||
<span className="text-xs font-semibold uppercase">
|
||||
Pix/Débito
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-lg font-bold text-foreground">
|
||||
{formatCurrency(summary.paymentSplits.instant)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between text-xs text-muted-foreground">
|
||||
<span>Inclui {summary.lancamentoCount} lançamentos.</span>
|
||||
<span>Último envio: {lastMailLabel}</span>
|
||||
{/* Detalhes Adicionais */}
|
||||
<div className="space-y-3">
|
||||
{/* Cartões Utilizados */}
|
||||
{summary.cardUsage.length > 0 && (
|
||||
<div className="rounded-lg border bg-muted/20 p-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<RiBankCard2Line className="size-4 text-muted-foreground" />
|
||||
<span className="text-xs font-semibold uppercase text-muted-foreground">
|
||||
Cartões Utilizados
|
||||
</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{summary.cardUsage.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between text-sm"
|
||||
>
|
||||
<span className="text-foreground">{card.name}</span>
|
||||
<span className="font-medium text-foreground">
|
||||
{formatCurrency(card.amount)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Status de Boletos */}
|
||||
{(summary.boletoStats.paidCount > 0 ||
|
||||
summary.boletoStats.pendingCount > 0) && (
|
||||
<div className="rounded-lg border bg-muted/20 p-3">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<RiBillLine className="size-4 text-muted-foreground" />
|
||||
<span className="text-xs font-semibold uppercase text-muted-foreground">
|
||||
Status de Boletos
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">Pagos</p>
|
||||
<p className="text-sm font-semibold text-green-600">
|
||||
{formatCurrency(summary.boletoStats.paidAmount)}{" "}
|
||||
<span className="text-xs font-normal">
|
||||
({summary.boletoStats.paidCount})
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Pendentes
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-amber-600">
|
||||
{formatCurrency(summary.boletoStats.pendingAmount)}{" "}
|
||||
<span className="text-xs font-normal">
|
||||
({summary.boletoStats.pendingCount})
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { RiArrowRightSLine, RiMore2Line } from "@remixicon/react";
|
||||
|
||||
function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
|
||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />
|
||||
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
|
||||
}
|
||||
|
||||
function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||
@@ -13,12 +13,12 @@ function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
|
||||
<ol
|
||||
data-slot="breadcrumb-list"
|
||||
className={cn(
|
||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
|
||||
"text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm wrap-break-word sm:gap-2.5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||
@@ -28,7 +28,7 @@ function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
|
||||
className={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbLink({
|
||||
@@ -36,9 +36,9 @@ function BreadcrumbLink({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"a"> & {
|
||||
asChild?: boolean
|
||||
asChild?: boolean;
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "a"
|
||||
const Comp = asChild ? Slot : "a";
|
||||
|
||||
return (
|
||||
<Comp
|
||||
@@ -46,7 +46,7 @@ function BreadcrumbLink({
|
||||
className={cn("hover:text-foreground transition-colors", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||
@@ -59,7 +59,7 @@ function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
|
||||
className={cn("text-foreground font-normal", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbSeparator({
|
||||
@@ -75,9 +75,9 @@ function BreadcrumbSeparator({
|
||||
className={cn("[&>svg]:size-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <ChevronRight />}
|
||||
{children ?? <RiArrowRightSLine />}
|
||||
</li>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function BreadcrumbEllipsis({
|
||||
@@ -92,18 +92,18 @@ function BreadcrumbEllipsis({
|
||||
className={cn("flex size-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="size-4" />
|
||||
<RiMore2Line className="size-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
} from "lucide-react"
|
||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import { Button, buttonVariants } from "@/components/ui/button"
|
||||
RiArrowDownSLine,
|
||||
RiArrowLeftSLine,
|
||||
RiArrowRightSLine,
|
||||
} from "@remixicon/react";
|
||||
import * as React from "react";
|
||||
import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker";
|
||||
|
||||
function Calendar({
|
||||
className,
|
||||
@@ -21,15 +20,15 @@ function Calendar({
|
||||
components,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DayPicker> & {
|
||||
buttonVariant?: React.ComponentProps<typeof Button>["variant"]
|
||||
buttonVariant?: React.ComponentProps<typeof Button>["variant"];
|
||||
}) {
|
||||
const defaultClassNames = getDefaultClassNames()
|
||||
const defaultClassNames = getDefaultClassNames();
|
||||
|
||||
return (
|
||||
<DayPicker
|
||||
showOutsideDays={showOutsideDays}
|
||||
className={cn(
|
||||
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent",
|
||||
"bg-background group/calendar p-3 [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent",
|
||||
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||
className
|
||||
@@ -136,27 +135,30 @@ function Calendar({
|
||||
className={cn(className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
Chevron: ({ className, orientation, ...props }) => {
|
||||
if (orientation === "left") {
|
||||
return (
|
||||
<ChevronLeftIcon className={cn("size-4", className)} {...props} />
|
||||
)
|
||||
<RiArrowLeftSLine
|
||||
className={cn("size-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (orientation === "right") {
|
||||
return (
|
||||
<ChevronRightIcon
|
||||
<RiArrowRightSLine
|
||||
className={cn("size-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ChevronDownIcon className={cn("size-4", className)} {...props} />
|
||||
)
|
||||
<RiArrowDownSLine className={cn("size-4", className)} {...props} />
|
||||
);
|
||||
},
|
||||
DayButton: CalendarDayButton,
|
||||
WeekNumber: ({ children, ...props }) => {
|
||||
@@ -166,13 +168,13 @@ function Calendar({
|
||||
{children}
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
);
|
||||
},
|
||||
...components,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CalendarDayButton({
|
||||
@@ -181,12 +183,12 @@ function CalendarDayButton({
|
||||
modifiers,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DayButton>) {
|
||||
const defaultClassNames = getDefaultClassNames()
|
||||
const defaultClassNames = getDefaultClassNames();
|
||||
|
||||
const ref = React.useRef<HTMLButtonElement>(null)
|
||||
const ref = React.useRef<HTMLButtonElement>(null);
|
||||
React.useEffect(() => {
|
||||
if (modifiers.focused) ref.current?.focus()
|
||||
}, [modifiers.focused])
|
||||
if (modifiers.focused) ref.current?.focus();
|
||||
}, [modifiers.focused]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -210,7 +212,7 @@ function CalendarDayButton({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Calendar, CalendarDayButton }
|
||||
export { Calendar, CalendarDayButton };
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { CheckIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { RiCheckLine } from "@remixicon/react";
|
||||
import * as React from "react";
|
||||
|
||||
function Checkbox({
|
||||
className,
|
||||
@@ -14,7 +13,7 @@ function Checkbox({
|
||||
<CheckboxPrimitive.Root
|
||||
data-slot="checkbox"
|
||||
className={cn(
|
||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-lg border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -23,10 +22,10 @@ function Checkbox({
|
||||
data-slot="checkbox-indicator"
|
||||
className="grid place-content-center text-current transition-none"
|
||||
>
|
||||
<CheckIcon className="size-3.5" />
|
||||
<RiCheckLine className="size-3.5" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Checkbox }
|
||||
export { Checkbox };
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { Command as CommandPrimitive } from "cmdk"
|
||||
import { SearchIcon } from "lucide-react"
|
||||
import { Command as CommandPrimitive } from "cmdk";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
} from "@/components/ui/dialog";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { RiSearchLine } from "@remixicon/react";
|
||||
|
||||
function Command({
|
||||
className,
|
||||
@@ -26,7 +26,7 @@ function Command({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandDialog({
|
||||
@@ -37,10 +37,10 @@ function CommandDialog({
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof Dialog> & {
|
||||
title?: string
|
||||
description?: string
|
||||
className?: string
|
||||
showCloseButton?: boolean
|
||||
title?: string;
|
||||
description?: string;
|
||||
className?: string;
|
||||
showCloseButton?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
@@ -52,12 +52,12 @@ function CommandDialog({
|
||||
className={cn("overflow-hidden p-0", className)}
|
||||
showCloseButton={showCloseButton}
|
||||
>
|
||||
<Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
<Command className="**:[[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandInput({
|
||||
@@ -69,7 +69,7 @@ function CommandInput({
|
||||
data-slot="command-input-wrapper"
|
||||
className="flex h-9 items-center gap-2 border-b px-3"
|
||||
>
|
||||
<SearchIcon className="size-4 shrink-0 opacity-50" />
|
||||
<RiSearchLine className="size-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
data-slot="command-input"
|
||||
className={cn(
|
||||
@@ -79,7 +79,7 @@ function CommandInput({
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandList({
|
||||
@@ -95,7 +95,7 @@ function CommandList({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandEmpty({
|
||||
@@ -107,7 +107,7 @@ function CommandEmpty({
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandGroup({
|
||||
@@ -118,12 +118,12 @@ function CommandGroup({
|
||||
<CommandPrimitive.Group
|
||||
data-slot="command-group"
|
||||
className={cn(
|
||||
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
||||
"text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:py-1.5 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandSeparator({
|
||||
@@ -136,7 +136,7 @@ function CommandSeparator({
|
||||
className={cn("bg-border -mx-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandItem({
|
||||
@@ -152,7 +152,7 @@ function CommandItem({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function CommandShortcut({
|
||||
@@ -168,17 +168,17 @@ function CommandShortcut({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
}
|
||||
CommandShortcut,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { RiCalendarLine } from "@remixicon/react";
|
||||
import * as React from "react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Calendar } from "@/components/ui/calendar";
|
||||
@@ -164,6 +164,8 @@ export function DatePicker({
|
||||
month={month}
|
||||
onMonthChange={setMonth}
|
||||
onSelect={handleCalendarSelect}
|
||||
fromYear={2020}
|
||||
toYear={new Date().getFullYear() + 10}
|
||||
locale={{
|
||||
localize: {
|
||||
day: (n) => ["D", "S", "T", "Q", "Q", "S", "S"][n],
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { RiCloseLine } from "@remixicon/react";
|
||||
|
||||
function Dialog({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />;
|
||||
}
|
||||
|
||||
function DialogTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function DialogPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />;
|
||||
}
|
||||
|
||||
function DialogClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />;
|
||||
}
|
||||
|
||||
function DialogOverlay({
|
||||
@@ -43,7 +43,7 @@ function DialogOverlay({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogContent({
|
||||
@@ -52,7 +52,7 @@ function DialogContent({
|
||||
showCloseButton = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
||||
showCloseButton?: boolean
|
||||
showCloseButton?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DialogPortal data-slot="dialog-portal">
|
||||
@@ -71,13 +71,13 @@ function DialogContent({
|
||||
data-slot="dialog-close"
|
||||
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<XIcon />
|
||||
<RiCloseLine />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
)}
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -87,7 +87,7 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -100,7 +100,7 @@ function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogTitle({
|
||||
@@ -113,7 +113,7 @@ function DialogTitle({
|
||||
className={cn("text-lg leading-none font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function DialogDescription({
|
||||
@@ -126,7 +126,7 @@ function DialogDescription({
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -140,4 +140,4 @@ export {
|
||||
DialogPortal,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||
import { CircleIcon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
||||
import { RiCircleLine } from "@remixicon/react";
|
||||
import * as React from "react";
|
||||
|
||||
function RadioGroup({
|
||||
className,
|
||||
@@ -16,7 +15,7 @@ function RadioGroup({
|
||||
className={cn("grid gap-3", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function RadioGroupItem({
|
||||
@@ -36,10 +35,10 @@ function RadioGroupItem({
|
||||
data-slot="radio-group-indicator"
|
||||
className="relative flex items-center justify-center"
|
||||
>
|
||||
<CircleIcon className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||
<RiCircleLine className="fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { RadioGroup, RadioGroupItem }
|
||||
export { RadioGroup, RadioGroupItem };
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import {
|
||||
RiArrowDownSLine,
|
||||
RiArrowUpSLine,
|
||||
RiCheckLine,
|
||||
} from "@remixicon/react";
|
||||
import * as React from "react";
|
||||
|
||||
function Select({
|
||||
...props
|
||||
@@ -44,7 +47,7 @@ function SelectTrigger({
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
<RiArrowDownSLine className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
);
|
||||
@@ -116,7 +119,7 @@ function SelectItem({
|
||||
>
|
||||
<span className="absolute right-2 flex size-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="size-4" />
|
||||
<RiCheckLine className="size-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
@@ -150,7 +153,7 @@ function SelectScrollUpButton({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
<RiArrowUpSLine className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
);
|
||||
}
|
||||
@@ -168,7 +171,7 @@ function SelectScrollDownButton({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
<RiArrowDownSLine className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { XIcon } from "lucide-react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog";
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { RiCloseLine } from "@remixicon/react";
|
||||
|
||||
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
|
||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />
|
||||
return <SheetPrimitive.Root data-slot="sheet" {...props} />;
|
||||
}
|
||||
|
||||
function SheetTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
|
||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
|
||||
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />;
|
||||
}
|
||||
|
||||
function SheetClose({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
|
||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
|
||||
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />;
|
||||
}
|
||||
|
||||
function SheetPortal({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
|
||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
|
||||
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />;
|
||||
}
|
||||
|
||||
function SheetOverlay({
|
||||
@@ -41,7 +41,7 @@ function SheetOverlay({
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetContent({
|
||||
@@ -50,7 +50,7 @@ function SheetContent({
|
||||
side = "right",
|
||||
...props
|
||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
side?: "top" | "right" | "bottom" | "left";
|
||||
}) {
|
||||
return (
|
||||
<SheetPortal>
|
||||
@@ -73,12 +73,12 @@ function SheetContent({
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none">
|
||||
<XIcon className="size-4" />
|
||||
<RiCloseLine className="size-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -88,7 +88,7 @@ function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("flex flex-col gap-1.5 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
@@ -98,7 +98,7 @@ function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetTitle({
|
||||
@@ -111,7 +111,7 @@ function SheetTitle({
|
||||
className={cn("text-foreground font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SheetDescription({
|
||||
@@ -124,16 +124,16 @@ function SheetDescription({
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
};
|
||||
|
||||
@@ -20,8 +20,8 @@ import {
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { RiLayoutLeft2Line } from "@remixicon/react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { PanelLeftIcon } from "lucide-react";
|
||||
import * as React from "react";
|
||||
|
||||
const SIDEBAR_COOKIE_NAME = "sidebar_state";
|
||||
@@ -272,7 +272,7 @@ function SidebarTrigger({
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<PanelLeftIcon />
|
||||
<RiLayoutLeft2Line />
|
||||
<span className="sr-only">Toggle Sidebar</span>
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import {
|
||||
CircleCheckIcon,
|
||||
InfoIcon,
|
||||
Loader2Icon,
|
||||
OctagonXIcon,
|
||||
TriangleAlertIcon,
|
||||
} from "lucide-react"
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||
RiAlertLine,
|
||||
RiCheckboxCircleLine,
|
||||
RiCloseCircleLine,
|
||||
RiInformationLine,
|
||||
RiLoader4Line,
|
||||
} from "@remixicon/react";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner";
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
const { theme = "system" } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
icons={{
|
||||
success: <CircleCheckIcon className="size-4" />,
|
||||
info: <InfoIcon className="size-4" />,
|
||||
warning: <TriangleAlertIcon className="size-4" />,
|
||||
error: <OctagonXIcon className="size-4" />,
|
||||
loading: <Loader2Icon className="size-4 animate-spin" />,
|
||||
success: <RiCheckboxCircleLine className="size-4" />,
|
||||
info: <RiInformationLine className="size-4" />,
|
||||
warning: <RiAlertLine className="size-4" />,
|
||||
error: <RiCloseCircleLine className="size-4" />,
|
||||
loading: <RiLoader4Line className="size-4 animate-spin" />,
|
||||
}}
|
||||
style={
|
||||
{
|
||||
@@ -34,7 +34,7 @@ const Toaster = ({ ...props }: ToasterProps) => {
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster }
|
||||
export { Toaster };
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { Loader2Icon } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils/ui"
|
||||
import { cn } from "@/lib/utils/ui";
|
||||
import { RiLoader4Line } from "@remixicon/react";
|
||||
|
||||
function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
|
||||
return (
|
||||
<Loader2Icon
|
||||
<RiLoader4Line
|
||||
role="status"
|
||||
aria-label="Loading"
|
||||
className={cn("size-4 animate-spin", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export { Spinner }
|
||||
export { Spinner };
|
||||
|
||||
Reference in New Issue
Block a user