refactor: migrate from ESLint to Biome and extract SQL queries to data.ts

- Replace ESLint with Biome for linting and formatting
- Configure Biome with tabs, double quotes, and organized imports
- Move all SQL/Drizzle queries from page.tsx files to data.ts files
- Create new data.ts files for: ajustes, dashboard, relatorios/categorias
- Update existing data.ts files: extrato, fatura (add lancamentos queries)
- Remove all drizzle-orm imports from page.tsx files
- Update README.md with new tooling info

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-01-27 13:15:37 +00:00
parent 8ffe61c59b
commit a7f63fb77a
442 changed files with 66141 additions and 69292 deletions

View File

@@ -1,94 +1,92 @@
import { useEffect } from "react";
type UseCalculatorKeyboardParams = {
canCopy: boolean;
onCopy: () => void | Promise<void>;
onPaste: () => void | Promise<void>;
canCopy: boolean;
onCopy: () => void | Promise<void>;
onPaste: () => void | Promise<void>;
};
function shouldIgnoreForEditableTarget(target: EventTarget | null): boolean {
if (!target || !(target instanceof HTMLElement)) {
return false;
}
if (!target || !(target instanceof HTMLElement)) {
return false;
}
const tagName = target.tagName;
return (
tagName === "INPUT" ||
tagName === "TEXTAREA" ||
target.isContentEditable
);
const tagName = target.tagName;
return (
tagName === "INPUT" || tagName === "TEXTAREA" || target.isContentEditable
);
}
export function useCalculatorKeyboard({
canCopy,
onCopy,
onPaste,
canCopy,
onCopy,
onPaste,
}: UseCalculatorKeyboardParams) {
useEffect(() => {
if (!canCopy) {
return;
}
useEffect(() => {
if (!canCopy) {
return;
}
const handleKeyDown = (event: KeyboardEvent) => {
if (!(event.ctrlKey || event.metaKey)) {
return;
}
const handleKeyDown = (event: KeyboardEvent) => {
if (!(event.ctrlKey || event.metaKey)) {
return;
}
if (shouldIgnoreForEditableTarget(event.target)) {
return;
}
if (shouldIgnoreForEditableTarget(event.target)) {
return;
}
if (event.key.toLowerCase() !== "c") {
return;
}
if (event.key.toLowerCase() !== "c") {
return;
}
const selection = window.getSelection();
if (selection && selection.toString().trim().length > 0) {
return;
}
const selection = window.getSelection();
if (selection && selection.toString().trim().length > 0) {
return;
}
event.preventDefault();
void onCopy();
};
event.preventDefault();
void onCopy();
};
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keydown", handleKeyDown);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [canCopy, onCopy]);
return () => {
document.removeEventListener("keydown", handleKeyDown);
};
}, [canCopy, onCopy]);
useEffect(() => {
const handlePasteShortcut = (event: KeyboardEvent) => {
if (!(event.ctrlKey || event.metaKey)) {
return;
}
useEffect(() => {
const handlePasteShortcut = (event: KeyboardEvent) => {
if (!(event.ctrlKey || event.metaKey)) {
return;
}
if (event.key.toLowerCase() !== "v") {
return;
}
if (event.key.toLowerCase() !== "v") {
return;
}
if (shouldIgnoreForEditableTarget(event.target)) {
return;
}
if (shouldIgnoreForEditableTarget(event.target)) {
return;
}
const selection = window.getSelection();
if (selection && selection.toString().trim().length > 0) {
return;
}
const selection = window.getSelection();
if (selection && selection.toString().trim().length > 0) {
return;
}
if (!navigator.clipboard?.readText) {
return;
}
if (!navigator.clipboard?.readText) {
return;
}
event.preventDefault();
void onPaste();
};
event.preventDefault();
void onPaste();
};
document.addEventListener("keydown", handlePasteShortcut);
document.addEventListener("keydown", handlePasteShortcut);
return () => {
document.removeEventListener("keydown", handlePasteShortcut);
};
}, [onPaste]);
return () => {
document.removeEventListener("keydown", handlePasteShortcut);
};
}, [onPaste]);
}

View File

@@ -1,384 +1,386 @@
import type { VariantProps } from "class-variance-authority";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { buttonVariants } from "@/components/ui/button";
import {
OPERATOR_SYMBOLS,
formatLocaleValue,
formatNumber,
normalizeClipboardNumber,
performOperation,
type Operator,
formatLocaleValue,
formatNumber,
normalizeClipboardNumber,
OPERATOR_SYMBOLS,
type Operator,
performOperation,
} from "@/lib/utils/calculator";
import { type buttonVariants } from "@/components/ui/button";
import { type VariantProps } from "class-variance-authority";
export type CalculatorButtonConfig = {
label: string;
onClick: () => void;
variant?: VariantProps<typeof buttonVariants>["variant"];
colSpan?: number;
label: string;
onClick: () => void;
variant?: VariantProps<typeof buttonVariants>["variant"];
colSpan?: number;
};
export function useCalculatorState() {
const [display, setDisplay] = useState("0");
const [accumulator, setAccumulator] = useState<number | null>(null);
const [operator, setOperator] = useState<Operator | null>(null);
const [overwrite, setOverwrite] = useState(false);
const [history, setHistory] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const resetCopiedTimeoutRef = useRef<number | undefined>(undefined);
const [display, setDisplay] = useState("0");
const [accumulator, setAccumulator] = useState<number | null>(null);
const [operator, setOperator] = useState<Operator | null>(null);
const [overwrite, setOverwrite] = useState(false);
const [history, setHistory] = useState<string | null>(null);
const [copied, setCopied] = useState(false);
const resetCopiedTimeoutRef = useRef<number | undefined>(undefined);
const currentValue = useMemo(() => Number(display), [display]);
const currentValue = useMemo(() => Number(display), [display]);
const resultText = useMemo(() => {
if (display === "Erro") {
return null;
}
const resultText = useMemo(() => {
if (display === "Erro") {
return null;
}
const normalized = formatNumber(currentValue);
if (normalized === "Erro") {
return null;
}
const normalized = formatNumber(currentValue);
if (normalized === "Erro") {
return null;
}
return formatLocaleValue(normalized);
}, [currentValue, display]);
return formatLocaleValue(normalized);
}, [currentValue, display]);
const reset = useCallback(() => {
setDisplay("0");
setAccumulator(null);
setOperator(null);
setOverwrite(false);
setHistory(null);
}, []);
const reset = useCallback(() => {
setDisplay("0");
setAccumulator(null);
setOperator(null);
setOverwrite(false);
setHistory(null);
}, []);
const inputDigit = useCallback(
(digit: string) => {
// Check conditions before state updates
const shouldReset = overwrite || display === "Erro";
const inputDigit = useCallback(
(digit: string) => {
// Check conditions before state updates
const shouldReset = overwrite || display === "Erro";
setDisplay((prev) => {
if (shouldReset) {
return digit;
}
setDisplay((prev) => {
if (shouldReset) {
return digit;
}
if (prev === "0") {
return digit;
}
if (prev === "0") {
return digit;
}
// Limitar a 10 dígitos (excluindo sinal negativo e ponto decimal)
const digitCount = prev.replace(/[-.]/g, "").length;
if (digitCount >= 10) {
return prev;
}
// Limitar a 10 dígitos (excluindo sinal negativo e ponto decimal)
const digitCount = prev.replace(/[-.]/g, "").length;
if (digitCount >= 10) {
return prev;
}
return `${prev}${digit}`;
});
return `${prev}${digit}`;
});
// Update related states after display update
if (shouldReset) {
setOverwrite(false);
setHistory(null);
}
},
[overwrite, display]
);
// Update related states after display update
if (shouldReset) {
setOverwrite(false);
setHistory(null);
}
},
[overwrite, display],
);
const inputDecimal = useCallback(() => {
// Check conditions before state updates
const shouldReset = overwrite || display === "Erro";
const inputDecimal = useCallback(() => {
// Check conditions before state updates
const shouldReset = overwrite || display === "Erro";
setDisplay((prev) => {
if (shouldReset) {
return "0.";
}
setDisplay((prev) => {
if (shouldReset) {
return "0.";
}
if (prev.includes(".")) {
return prev;
}
if (prev.includes(".")) {
return prev;
}
// Limitar a 10 dígitos antes de adicionar o ponto decimal
const digitCount = prev.replace(/[-]/g, "").length;
if (digitCount >= 10) {
return prev;
}
// Limitar a 10 dígitos antes de adicionar o ponto decimal
const digitCount = prev.replace(/[-]/g, "").length;
if (digitCount >= 10) {
return prev;
}
return `${prev}.`;
});
return `${prev}.`;
});
// Update related states after display update
if (shouldReset) {
setOverwrite(false);
setHistory(null);
}
}, [overwrite, display]);
// Update related states after display update
if (shouldReset) {
setOverwrite(false);
setHistory(null);
}
}, [overwrite, display]);
const setNextOperator = useCallback(
(nextOperator: Operator) => {
if (display === "Erro") {
reset();
return;
}
const setNextOperator = useCallback(
(nextOperator: Operator) => {
if (display === "Erro") {
reset();
return;
}
const value = currentValue;
const value = currentValue;
if (accumulator === null || operator === null || overwrite) {
setAccumulator(value);
} else {
const result = performOperation(accumulator, value, operator);
const formatted = formatNumber(result);
setAccumulator(Number.isFinite(result) ? result : null);
setDisplay(formatted);
if (!Number.isFinite(result)) {
setOperator(null);
setOverwrite(true);
setHistory(null);
return;
}
}
if (accumulator === null || operator === null || overwrite) {
setAccumulator(value);
} else {
const result = performOperation(accumulator, value, operator);
const formatted = formatNumber(result);
setAccumulator(Number.isFinite(result) ? result : null);
setDisplay(formatted);
if (!Number.isFinite(result)) {
setOperator(null);
setOverwrite(true);
setHistory(null);
return;
}
}
setOperator(nextOperator);
setOverwrite(true);
setHistory(null);
},
[accumulator, currentValue, display, operator, overwrite, reset]
);
setOperator(nextOperator);
setOverwrite(true);
setHistory(null);
},
[accumulator, currentValue, display, operator, overwrite, reset],
);
const evaluate = useCallback(() => {
if (operator === null || accumulator === null || display === "Erro") {
return;
}
const evaluate = useCallback(() => {
if (operator === null || accumulator === null || display === "Erro") {
return;
}
const value = currentValue;
const left = formatNumber(accumulator);
const right = formatNumber(value);
const symbol = OPERATOR_SYMBOLS[operator];
const operation = `${formatLocaleValue(left)} ${symbol} ${formatLocaleValue(
right
)}`;
const result = performOperation(accumulator, value, operator);
const formatted = formatNumber(result);
const value = currentValue;
const left = formatNumber(accumulator);
const right = formatNumber(value);
const symbol = OPERATOR_SYMBOLS[operator];
const operation = `${formatLocaleValue(left)} ${symbol} ${formatLocaleValue(
right,
)}`;
const result = performOperation(accumulator, value, operator);
const formatted = formatNumber(result);
setDisplay(formatted);
setAccumulator(Number.isFinite(result) ? result : null);
setOperator(null);
setOverwrite(true);
setHistory(operation);
}, [accumulator, currentValue, display, operator]);
setDisplay(formatted);
setAccumulator(Number.isFinite(result) ? result : null);
setOperator(null);
setOverwrite(true);
setHistory(operation);
}, [accumulator, currentValue, display, operator]);
const toggleSign = useCallback(() => {
setDisplay((prev) => {
if (prev === "Erro") {
return prev;
}
if (prev.startsWith("-")) {
return prev.slice(1);
}
return prev === "0" ? prev : `-${prev}`;
});
if (overwrite) {
setOverwrite(false);
setHistory(null);
}
}, [overwrite]);
const toggleSign = useCallback(() => {
setDisplay((prev) => {
if (prev === "Erro") {
return prev;
}
if (prev.startsWith("-")) {
return prev.slice(1);
}
return prev === "0" ? prev : `-${prev}`;
});
if (overwrite) {
setOverwrite(false);
setHistory(null);
}
}, [overwrite]);
const deleteLastDigit = useCallback(() => {
setHistory(null);
const deleteLastDigit = useCallback(() => {
setHistory(null);
// Check conditions before state updates
const isError = display === "Erro";
// Check conditions before state updates
const isError = display === "Erro";
setDisplay((prev) => {
if (prev === "Erro") {
return "0";
}
setDisplay((prev) => {
if (prev === "Erro") {
return "0";
}
if (overwrite) {
return "0";
}
if (overwrite) {
return "0";
}
if (prev.length <= 1 || (prev.length === 2 && prev.startsWith("-"))) {
return "0";
}
if (prev.length <= 1 || (prev.length === 2 && prev.startsWith("-"))) {
return "0";
}
return prev.slice(0, -1);
});
return prev.slice(0, -1);
});
// Update related states after display update
if (isError) {
setAccumulator(null);
setOperator(null);
setOverwrite(false);
} else if (overwrite) {
setOverwrite(false);
}
}, [overwrite, display]);
// Update related states after display update
if (isError) {
setAccumulator(null);
setOperator(null);
setOverwrite(false);
} else if (overwrite) {
setOverwrite(false);
}
}, [overwrite, display]);
const applyPercent = useCallback(() => {
setDisplay((prev) => {
if (prev === "Erro") {
return prev;
}
const value = Number(prev);
return formatNumber(value / 100);
});
setOverwrite(true);
setHistory(null);
}, []);
const applyPercent = useCallback(() => {
setDisplay((prev) => {
if (prev === "Erro") {
return prev;
}
const value = Number(prev);
return formatNumber(value / 100);
});
setOverwrite(true);
setHistory(null);
}, []);
const expression = useMemo(() => {
if (display === "Erro") {
return "Erro";
}
const expression = useMemo(() => {
if (display === "Erro") {
return "Erro";
}
if (operator && accumulator !== null) {
const symbol = OPERATOR_SYMBOLS[operator];
const left = formatLocaleValue(formatNumber(accumulator));
if (operator && accumulator !== null) {
const symbol = OPERATOR_SYMBOLS[operator];
const left = formatLocaleValue(formatNumber(accumulator));
if (overwrite) {
return `${left} ${symbol}`;
}
if (overwrite) {
return `${left} ${symbol}`;
}
return `${left} ${symbol} ${formatLocaleValue(display)}`;
}
return `${left} ${symbol} ${formatLocaleValue(display)}`;
}
return formatLocaleValue(display);
}, [accumulator, display, operator, overwrite]);
return formatLocaleValue(display);
}, [accumulator, display, operator, overwrite]);
const buttons = useMemo(() => {
const makeOperatorHandler = (nextOperator: Operator) => () =>
setNextOperator(nextOperator);
const buttons = useMemo(() => {
const makeOperatorHandler = (nextOperator: Operator) => () =>
setNextOperator(nextOperator);
return [
[
{ label: "C", onClick: reset, variant: "destructive" },
{ label: "⌫", onClick: deleteLastDigit, variant: "default" },
{ label: "%", onClick: applyPercent, variant: "default" },
{
label: "÷",
onClick: makeOperatorHandler("divide"),
variant: "outline",
},
],
[
{ label: "7", onClick: () => inputDigit("7") },
{ label: "8", onClick: () => inputDigit("8") },
{ label: "9", onClick: () => inputDigit("9") },
{
label: "×",
onClick: makeOperatorHandler("multiply"),
variant: "outline",
},
],
[
{ label: "4", onClick: () => inputDigit("4") },
{ label: "5", onClick: () => inputDigit("5") },
{ label: "6", onClick: () => inputDigit("6") },
{
label: "-",
onClick: makeOperatorHandler("subtract"),
variant: "outline",
},
],
[
{ label: "1", onClick: () => inputDigit("1") },
{ label: "2", onClick: () => inputDigit("2") },
{ label: "3", onClick: () => inputDigit("3") },
{ label: "+", onClick: makeOperatorHandler("add"), variant: "outline" },
],
[
{ label: "±", onClick: toggleSign, variant: "default" },
{ label: "0", onClick: () => inputDigit("0") },
{ label: ",", onClick: inputDecimal },
{ label: "=", onClick: evaluate, variant: "default" },
],
] satisfies CalculatorButtonConfig[][];
}, [
applyPercent,
deleteLastDigit,
evaluate,
inputDecimal,
inputDigit,
reset,
setNextOperator,
toggleSign,
]);
return [
[
{ label: "C", onClick: reset, variant: "destructive" },
{ label: "⌫", onClick: deleteLastDigit, variant: "default" },
{ label: "%", onClick: applyPercent, variant: "default" },
{
label: "÷",
onClick: makeOperatorHandler("divide"),
variant: "outline",
},
],
[
{ label: "7", onClick: () => inputDigit("7") },
{ label: "8", onClick: () => inputDigit("8") },
{ label: "9", onClick: () => inputDigit("9") },
{
label: "×",
onClick: makeOperatorHandler("multiply"),
variant: "outline",
},
],
[
{ label: "4", onClick: () => inputDigit("4") },
{ label: "5", onClick: () => inputDigit("5") },
{ label: "6", onClick: () => inputDigit("6") },
{
label: "-",
onClick: makeOperatorHandler("subtract"),
variant: "outline",
},
],
[
{ label: "1", onClick: () => inputDigit("1") },
{ label: "2", onClick: () => inputDigit("2") },
{ label: "3", onClick: () => inputDigit("3") },
{ label: "+", onClick: makeOperatorHandler("add"), variant: "outline" },
],
[
{ label: "±", onClick: toggleSign, variant: "default" },
{ label: "0", onClick: () => inputDigit("0") },
{ label: ",", onClick: inputDecimal },
{ label: "=", onClick: evaluate, variant: "default" },
],
] satisfies CalculatorButtonConfig[][];
}, [
applyPercent,
deleteLastDigit,
evaluate,
inputDecimal,
inputDigit,
reset,
setNextOperator,
toggleSign,
]);
const copyToClipboard = useCallback(async () => {
if (!resultText) return;
const copyToClipboard = useCallback(async () => {
if (!resultText) return;
try {
await navigator.clipboard.writeText(resultText);
try {
await navigator.clipboard.writeText(resultText);
setCopied(true);
if (resetCopiedTimeoutRef.current !== undefined) {
window.clearTimeout(resetCopiedTimeoutRef.current);
}
resetCopiedTimeoutRef.current = window.setTimeout(() => {
setCopied(false);
}, 2000);
} catch (error) {
console.error("Não foi possível copiar o resultado da calculadora.", error);
}
}, [resultText]);
setCopied(true);
if (resetCopiedTimeoutRef.current !== undefined) {
window.clearTimeout(resetCopiedTimeoutRef.current);
}
resetCopiedTimeoutRef.current = window.setTimeout(() => {
setCopied(false);
}, 2000);
} catch (error) {
console.error(
"Não foi possível copiar o resultado da calculadora.",
error,
);
}
}, [resultText]);
const pasteFromClipboard = useCallback(async () => {
if (!navigator.clipboard?.readText) return;
const pasteFromClipboard = useCallback(async () => {
if (!navigator.clipboard?.readText) return;
try {
const rawValue = await navigator.clipboard.readText();
const normalized = normalizeClipboardNumber(rawValue);
try {
const rawValue = await navigator.clipboard.readText();
const normalized = normalizeClipboardNumber(rawValue);
if (resetCopiedTimeoutRef.current !== undefined) {
window.clearTimeout(resetCopiedTimeoutRef.current);
resetCopiedTimeoutRef.current = undefined;
}
if (resetCopiedTimeoutRef.current !== undefined) {
window.clearTimeout(resetCopiedTimeoutRef.current);
resetCopiedTimeoutRef.current = undefined;
}
setCopied(false);
setCopied(false);
if (!normalized) {
setDisplay("Erro");
setAccumulator(null);
setOperator(null);
setOverwrite(true);
setHistory(null);
return;
}
if (!normalized) {
setDisplay("Erro");
setAccumulator(null);
setOperator(null);
setOverwrite(true);
setHistory(null);
return;
}
// Limitar a 10 dígitos
const digitCount = normalized.replace(/[-.]/g, "").length;
if (digitCount > 10) {
setDisplay("Erro");
setAccumulator(null);
setOperator(null);
setOverwrite(true);
setHistory(null);
return;
}
// Limitar a 10 dígitos
const digitCount = normalized.replace(/[-.]/g, "").length;
if (digitCount > 10) {
setDisplay("Erro");
setAccumulator(null);
setOperator(null);
setOverwrite(true);
setHistory(null);
return;
}
setDisplay(normalized);
setAccumulator(null);
setOperator(null);
setOverwrite(false);
setHistory(null);
} catch (error) {
console.error("Não foi possível colar o valor na calculadora.", error);
}
}, []);
setDisplay(normalized);
setAccumulator(null);
setOperator(null);
setOverwrite(false);
setHistory(null);
} catch (error) {
console.error("Não foi possível colar o valor na calculadora.", error);
}
}, []);
useEffect(() => {
return () => {
if (resetCopiedTimeoutRef.current !== undefined) {
window.clearTimeout(resetCopiedTimeoutRef.current);
}
};
}, []);
useEffect(() => {
return () => {
if (resetCopiedTimeoutRef.current !== undefined) {
window.clearTimeout(resetCopiedTimeoutRef.current);
}
};
}, []);
return {
expression,
history,
resultText,
copied,
buttons,
copyToClipboard,
pasteFromClipboard,
};
return {
expression,
history,
resultText,
copied,
buttons,
copyToClipboard,
pasteFromClipboard,
};
}

View File

@@ -18,34 +18,34 @@ import { useCallback, useEffect, useState } from "react";
* ```
*/
export function useControlledState<T>(
controlledValue: T | undefined,
defaultValue: T,
onChange?: (value: T) => void
controlledValue: T | undefined,
defaultValue: T,
onChange?: (value: T) => void,
): [T, (value: T) => void] {
const [internalValue, setInternalValue] = useState<T>(defaultValue);
const [internalValue, setInternalValue] = useState<T>(defaultValue);
// Sync internal value when controlled value changes
useEffect(() => {
if (controlledValue !== undefined) {
setInternalValue(controlledValue);
}
}, [controlledValue]);
// Sync internal value when controlled value changes
useEffect(() => {
if (controlledValue !== undefined) {
setInternalValue(controlledValue);
}
}, [controlledValue]);
// Use controlled value if provided, otherwise use internal value
const value = controlledValue !== undefined ? controlledValue : internalValue;
// Use controlled value if provided, otherwise use internal value
const value = controlledValue !== undefined ? controlledValue : internalValue;
const setValue = useCallback(
(newValue: T) => {
// Update internal state if uncontrolled
if (controlledValue === undefined) {
setInternalValue(newValue);
}
const setValue = useCallback(
(newValue: T) => {
// Update internal state if uncontrolled
if (controlledValue === undefined) {
setInternalValue(newValue);
}
// Always call onChange if provided
onChange?.(newValue);
},
[controlledValue, onChange]
);
// Always call onChange if provided
onChange?.(newValue);
},
[controlledValue, onChange],
);
return [value, setValue];
return [value, setValue];
}

View File

@@ -16,40 +16,38 @@ import { useCallback, useState } from "react";
* updateField('name', 'John');
* ```
*/
export function useFormState<T extends Record<string, any>>(
initialValues: T
) {
const [formState, setFormState] = useState<T>(initialValues);
export function useFormState<T extends Record<string, any>>(initialValues: T) {
const [formState, setFormState] = useState<T>(initialValues);
/**
* Updates a single field in the form state
*/
const updateField = useCallback(<K extends keyof T>(
field: K,
value: T[K]
) => {
setFormState((prev) => ({ ...prev, [field]: value }));
}, []);
/**
* Updates a single field in the form state
*/
const updateField = useCallback(
<K extends keyof T>(field: K, value: T[K]) => {
setFormState((prev) => ({ ...prev, [field]: value }));
},
[],
);
/**
* Resets form to initial values
*/
const resetForm = useCallback(() => {
setFormState(initialValues);
}, [initialValues]);
/**
* Resets form to initial values
*/
const resetForm = useCallback(() => {
setFormState(initialValues);
}, [initialValues]);
/**
* Updates multiple fields at once
*/
const updateFields = useCallback((updates: Partial<T>) => {
setFormState((prev) => ({ ...prev, ...updates }));
}, []);
/**
* Updates multiple fields at once
*/
const updateFields = useCallback((updates: Partial<T>) => {
setFormState((prev) => ({ ...prev, ...updates }));
}, []);
return {
formState,
updateField,
updateFields,
resetForm,
setFormState,
};
return {
formState,
updateField,
updateFields,
resetForm,
setFormState,
};
}

View File

@@ -2,10 +2,10 @@ import { useCallback } from "react";
import { deriveNameFromLogo } from "@/lib/logo";
interface UseLogoSelectionProps {
mode: "create" | "update";
currentLogo: string;
currentName: string;
onUpdate: (updates: { logo: string; name?: string }) => void;
mode: "create" | "update";
currentLogo: string;
currentName: string;
onUpdate: (updates: { logo: string; name?: string }) => void;
}
/**
@@ -30,29 +30,29 @@ interface UseLogoSelectionProps {
* ```
*/
export function useLogoSelection({
mode,
currentLogo,
currentName,
onUpdate,
mode,
currentLogo,
currentName,
onUpdate,
}: UseLogoSelectionProps) {
const handleLogoSelection = useCallback(
(newLogo: string) => {
const derived = deriveNameFromLogo(newLogo);
const previousDerived = deriveNameFromLogo(currentLogo);
const handleLogoSelection = useCallback(
(newLogo: string) => {
const derived = deriveNameFromLogo(newLogo);
const previousDerived = deriveNameFromLogo(currentLogo);
const shouldUpdateName =
mode === "create" ||
currentName.trim().length === 0 ||
previousDerived === currentName.trim();
const shouldUpdateName =
mode === "create" ||
currentName.trim().length === 0 ||
previousDerived === currentName.trim();
if (shouldUpdateName) {
onUpdate({ logo: newLogo, name: derived });
} else {
onUpdate({ logo: newLogo });
}
},
[mode, currentLogo, currentName, onUpdate]
);
if (shouldUpdateName) {
onUpdate({ logo: newLogo, name: derived });
} else {
onUpdate({ logo: newLogo });
}
},
[mode, currentLogo, currentName, onUpdate],
);
return handleLogoSelection;
return handleLogoSelection;
}

View File

@@ -1,19 +1,21 @@
import * as React from "react"
import * as React from "react";
const MOBILE_BREAKPOINT = 768
const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
undefined,
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
};
mql.addEventListener("change", onChange);
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
return () => mql.removeEventListener("change", onChange);
}, []);
return !!isMobile
return !!isMobile;
}

View File

@@ -10,76 +10,76 @@ const PERIOD_PARAM = "periodo";
const normalizeMonth = (value: string) => value.trim().toLowerCase();
export function useMonthPeriod() {
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const router = useRouter();
// Get current date info
const now = new Date();
const currentYear = now.getFullYear();
const currentMonthName = MONTH_NAMES[now.getMonth()];
const optionsMeses = [...MONTH_NAMES];
// Get current date info
const now = new Date();
const currentYear = now.getFullYear();
const currentMonthName = MONTH_NAMES[now.getMonth()];
const optionsMeses = [...MONTH_NAMES];
const defaultMonth = currentMonthName;
const defaultYear = currentYear.toString();
const defaultMonth = currentMonthName;
const defaultYear = currentYear.toString();
const periodFromParams = searchParams.get(PERIOD_PARAM);
const periodFromParams = searchParams.get(PERIOD_PARAM);
const { month: currentMonth, year: currentYearValue } = useMemo(() => {
if (!periodFromParams) {
return { month: defaultMonth, year: defaultYear };
}
const { month: currentMonth, year: currentYearValue } = useMemo(() => {
if (!periodFromParams) {
return { month: defaultMonth, year: defaultYear };
}
const [rawMonth, rawYear] = periodFromParams.split("-");
const normalizedMonth = normalizeMonth(rawMonth ?? "");
const normalizedYear = (rawYear ?? "").trim();
const monthExists = optionsMeses.includes(normalizedMonth);
const parsedYear = Number.parseInt(normalizedYear, 10);
const [rawMonth, rawYear] = periodFromParams.split("-");
const normalizedMonth = normalizeMonth(rawMonth ?? "");
const normalizedYear = (rawYear ?? "").trim();
const monthExists = optionsMeses.includes(normalizedMonth);
const parsedYear = Number.parseInt(normalizedYear, 10);
if (!monthExists || Number.isNaN(parsedYear)) {
return { month: defaultMonth, year: defaultYear };
}
if (!monthExists || Number.isNaN(parsedYear)) {
return { month: defaultMonth, year: defaultYear };
}
return {
month: normalizedMonth,
year: parsedYear.toString(),
};
}, [periodFromParams, defaultMonth, defaultYear, optionsMeses]);
return {
month: normalizedMonth,
year: parsedYear.toString(),
};
}, [periodFromParams, defaultMonth, defaultYear, optionsMeses]);
const buildHref = useCallback(
(month: string, year: string | number) => {
const normalizedMonth = normalizeMonth(month);
const normalizedYear = String(year).trim();
const buildHref = useCallback(
(month: string, year: string | number) => {
const normalizedMonth = normalizeMonth(month);
const normalizedYear = String(year).trim();
const params = new URLSearchParams(searchParams.toString());
params.set(PERIOD_PARAM, `${normalizedMonth}-${normalizedYear}`);
const params = new URLSearchParams(searchParams.toString());
params.set(PERIOD_PARAM, `${normalizedMonth}-${normalizedYear}`);
return `${pathname}?${params.toString()}`;
},
[pathname, searchParams]
);
return `${pathname}?${params.toString()}`;
},
[pathname, searchParams],
);
const replacePeriod = useCallback(
(target: string) => {
if (!target) {
return;
}
const replacePeriod = useCallback(
(target: string) => {
if (!target) {
return;
}
router.replace(target, { scroll: false });
},
[router]
);
router.replace(target, { scroll: false });
},
[router],
);
return {
monthNames: optionsMeses,
pathname,
currentMonth,
currentYear: currentYearValue,
defaultMonth,
defaultYear,
buildHref,
replacePeriod,
};
return {
monthNames: optionsMeses,
pathname,
currentMonth,
currentYear: currentYearValue,
defaultMonth,
defaultYear,
buildHref,
replacePeriod,
};
}
export { PERIOD_PARAM as MONTH_PERIOD_PARAM };