feat(tax-regimes): implement tax regime management features
- Add entities for tax regime forms and error view models. - Create schemas for tax regime forms using Zod for validation. - Implement UI components for creating, deleting, and managing tax regimes. - Develop hooks for CRUD operations on tax regimes, including create, update, delete, and enable/disable functionalities. - Add error handling and loading states for better user experience. - Create utility functions for building form data and handling API responses. - Implement skeleton and empty state components for improved UI feedback.
This commit is contained in:
parent
ef2ae58046
commit
7f305c930e
@ -127,6 +127,108 @@
|
||||
"description": "An unexpected error occurred while loading payment methods."
|
||||
}
|
||||
}
|
||||
},
|
||||
"taxRegimes": {
|
||||
"title": "Tax regimes",
|
||||
"description": "Manage the tax regimes available for the company.",
|
||||
"fields": {
|
||||
"code": "Code",
|
||||
"codeDescription": "Official tax regime code.",
|
||||
"codePlaceholder": "e.g. 01",
|
||||
"description": "Description",
|
||||
"descriptionDescription": "Visible name of the tax regime.",
|
||||
"descriptionPlaceholder": "e.g. General regime",
|
||||
"active": "Active",
|
||||
"activeDescription": "Controls whether the tax regime is available."
|
||||
},
|
||||
"actions": {
|
||||
"create": "New tax regime",
|
||||
"creating": "Creating…",
|
||||
"edit": "Edit",
|
||||
"save": "Save",
|
||||
"saving": "Saving…",
|
||||
"cancel": "Cancel",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"delete": "Delete",
|
||||
"deleting": "Deleting…",
|
||||
"more": "More actions"
|
||||
},
|
||||
"status": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"system": "System"
|
||||
},
|
||||
"create": {
|
||||
"title": "New tax regime",
|
||||
"description": "Create a new tax regime for this company."
|
||||
},
|
||||
"edit": {
|
||||
"title": "Edit tax regime",
|
||||
"description": "Update the selected tax regime information."
|
||||
},
|
||||
"delete": {
|
||||
"title": "Delete tax regime",
|
||||
"description": "This tax regime will stop being available for the company."
|
||||
},
|
||||
"empty": {
|
||||
"title": "There are no tax regimes configured.",
|
||||
"description": "Available tax regimes will appear here."
|
||||
},
|
||||
"messages": {
|
||||
"createdTitle": "Tax regime created",
|
||||
"created": "The tax regime was created successfully.",
|
||||
"updatedTitle": "Tax regime updated",
|
||||
"updated": "Changes were saved successfully.",
|
||||
"enabledTitle": "Tax regime enabled",
|
||||
"enabled": "The tax regime is available again.",
|
||||
"disabledTitle": "Tax regime disabled",
|
||||
"disabled": "The tax regime is no longer available.",
|
||||
"deletedTitle": "Tax regime deleted",
|
||||
"deleted": "The tax regime is no longer available.",
|
||||
"validationTitle": "Review the fields",
|
||||
"validationDescription": "There are validation errors in the form."
|
||||
},
|
||||
"protections": {
|
||||
"systemEdit": "System tax regimes cannot be edited.",
|
||||
"systemDisable": "System tax regimes cannot be disabled.",
|
||||
"systemDelete": "System tax regimes cannot be deleted.",
|
||||
"codeEdit": "The code cannot be changed once created."
|
||||
},
|
||||
"pageError": {
|
||||
"actions": {
|
||||
"retry": "Retry",
|
||||
"retrying": "Retrying…"
|
||||
},
|
||||
"forbidden": {
|
||||
"title": "You do not have access to this setting",
|
||||
"description": "You do not have permission to view tax regimes."
|
||||
},
|
||||
"notFound": {
|
||||
"title": "The settings could not be loaded",
|
||||
"description": "The requested settings are no longer available."
|
||||
},
|
||||
"network": {
|
||||
"title": "Could not connect to the server",
|
||||
"description": "Check your connection and try again."
|
||||
},
|
||||
"timeout": {
|
||||
"title": "Loading is taking too long",
|
||||
"description": "The server did not respond in time. Try again."
|
||||
},
|
||||
"unavailable": {
|
||||
"title": "Service unavailable",
|
||||
"description": "The settings are not available right now."
|
||||
},
|
||||
"server": {
|
||||
"title": "The settings could not be loaded",
|
||||
"description": "An internal error occurred while loading tax regimes."
|
||||
},
|
||||
"unknown": {
|
||||
"title": "The settings could not be loaded",
|
||||
"description": "An unexpected error occurred while loading tax regimes."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,6 +127,108 @@
|
||||
"description": "Se produjo un error inesperado al obtener los métodos de pago."
|
||||
}
|
||||
}
|
||||
},
|
||||
"taxRegimes": {
|
||||
"title": "Regímenes fiscales",
|
||||
"description": "Gestiona los regímenes fiscales disponibles para la empresa.",
|
||||
"fields": {
|
||||
"code": "Código",
|
||||
"codeDescription": "Código oficial del régimen fiscal.",
|
||||
"codePlaceholder": "Ej. 01",
|
||||
"description": "Descripción",
|
||||
"descriptionDescription": "Nombre visible del régimen fiscal.",
|
||||
"descriptionPlaceholder": "Ej. Régimen general",
|
||||
"active": "Activo",
|
||||
"activeDescription": "Controla si el régimen fiscal aparece como disponible."
|
||||
},
|
||||
"actions": {
|
||||
"create": "Nuevo régimen",
|
||||
"creating": "Creando…",
|
||||
"edit": "Editar",
|
||||
"save": "Guardar",
|
||||
"saving": "Guardando…",
|
||||
"cancel": "Cancelar",
|
||||
"enable": "Activar",
|
||||
"disable": "Desactivar",
|
||||
"delete": "Eliminar",
|
||||
"deleting": "Eliminando…",
|
||||
"more": "Más acciones"
|
||||
},
|
||||
"status": {
|
||||
"active": "Activo",
|
||||
"inactive": "Inactivo",
|
||||
"system": "Sistema"
|
||||
},
|
||||
"create": {
|
||||
"title": "Nuevo régimen fiscal",
|
||||
"description": "Crea un nuevo régimen fiscal para esta empresa."
|
||||
},
|
||||
"edit": {
|
||||
"title": "Editar régimen fiscal",
|
||||
"description": "Actualiza la información del régimen fiscal seleccionado."
|
||||
},
|
||||
"delete": {
|
||||
"title": "Eliminar régimen fiscal",
|
||||
"description": "Este régimen fiscal dejará de estar disponible para la empresa."
|
||||
},
|
||||
"empty": {
|
||||
"title": "No hay regímenes fiscales configurados.",
|
||||
"description": "Cuando existan regímenes fiscales disponibles aparecerán aquí."
|
||||
},
|
||||
"messages": {
|
||||
"createdTitle": "Régimen fiscal creado",
|
||||
"created": "El régimen fiscal se ha creado correctamente.",
|
||||
"updatedTitle": "Régimen fiscal actualizado",
|
||||
"updated": "Los cambios se han guardado correctamente.",
|
||||
"enabledTitle": "Régimen fiscal activado",
|
||||
"enabled": "El régimen fiscal vuelve a estar disponible.",
|
||||
"disabledTitle": "Régimen fiscal desactivado",
|
||||
"disabled": "El régimen fiscal ha dejado de estar disponible.",
|
||||
"deletedTitle": "Régimen fiscal eliminado",
|
||||
"deleted": "El régimen fiscal ha dejado de estar disponible.",
|
||||
"validationTitle": "Revisa los campos",
|
||||
"validationDescription": "Hay errores de validación en el formulario."
|
||||
},
|
||||
"protections": {
|
||||
"systemEdit": "Los regímenes fiscales del sistema no se pueden editar.",
|
||||
"systemDisable": "Los regímenes fiscales del sistema no se pueden desactivar.",
|
||||
"systemDelete": "Los regímenes fiscales del sistema no se pueden eliminar.",
|
||||
"codeEdit": "El código no se puede modificar una vez creado."
|
||||
},
|
||||
"pageError": {
|
||||
"actions": {
|
||||
"retry": "Reintentar",
|
||||
"retrying": "Reintentando…"
|
||||
},
|
||||
"forbidden": {
|
||||
"title": "No tienes acceso a esta configuración",
|
||||
"description": "No dispones de permisos para consultar los regímenes fiscales."
|
||||
},
|
||||
"notFound": {
|
||||
"title": "No se pudo cargar la configuración",
|
||||
"description": "La configuración solicitada ya no está disponible."
|
||||
},
|
||||
"network": {
|
||||
"title": "No se pudo conectar con el servidor",
|
||||
"description": "Comprueba tu conexión e inténtalo de nuevo."
|
||||
},
|
||||
"timeout": {
|
||||
"title": "La carga está tardando demasiado",
|
||||
"description": "El servidor no respondió a tiempo. Inténtalo de nuevo."
|
||||
},
|
||||
"unavailable": {
|
||||
"title": "Servicio no disponible",
|
||||
"description": "La configuración no está disponible en este momento."
|
||||
},
|
||||
"server": {
|
||||
"title": "No se pudo cargar la configuración",
|
||||
"description": "Se produjo un error interno al obtener los regímenes fiscales."
|
||||
},
|
||||
"unknown": {
|
||||
"title": "No se pudo cargar la configuración",
|
||||
"description": "Se produjo un error inesperado al obtener los regímenes fiscales."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,12 @@ const PaymentMethodsSettingsPage = lazy(() =>
|
||||
}))
|
||||
);
|
||||
|
||||
const TaxRegimesSettingsPage = lazy(() =>
|
||||
import("./settings/tax-regimes").then((module) => ({
|
||||
default: module.TaxRegimesSettingsPage,
|
||||
}))
|
||||
);
|
||||
|
||||
export const CatalogsRoutes = (_params: ModuleClientParams): ModuleRouteObject[] => {
|
||||
return [
|
||||
{
|
||||
@ -18,5 +24,13 @@ export const CatalogsRoutes = (_params: ModuleClientParams): ModuleRouteObject[]
|
||||
},
|
||||
element: <PaymentMethodsSettingsPage />,
|
||||
},
|
||||
{
|
||||
path: "settings/general/tax-regimes",
|
||||
handle: {
|
||||
layout: "app-sidebar",
|
||||
protected: true,
|
||||
},
|
||||
element: <TaxRegimesSettingsPage />,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@ -12,6 +12,12 @@ test("publica la entry de Settings para payment methods", () => {
|
||||
labelKey: "settings.paymentMethods.title",
|
||||
path: "/settings/general/payment-methods",
|
||||
},
|
||||
{
|
||||
id: "tax-regimes",
|
||||
sectionId: "general",
|
||||
labelKey: "settings.taxRegimes.title",
|
||||
path: "/settings/general/tax-regimes",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
@ -24,3 +30,12 @@ test("expone una ruta modular para settings/general/payment-methods", () => {
|
||||
assert.equal(settingsRoute?.handle?.layout, "app-sidebar");
|
||||
assert.equal(settingsRoute?.handle?.protected, true);
|
||||
});
|
||||
|
||||
test("expone una ruta modular para settings/general/tax-regimes", () => {
|
||||
const routes = CatalogsModuleManifest.routes?.({});
|
||||
const settingsRoute = routes?.find((route) => route.path === "settings/general/tax-regimes");
|
||||
|
||||
assert.ok(settingsRoute);
|
||||
assert.equal(settingsRoute?.handle?.layout, "app-sidebar");
|
||||
assert.equal(settingsRoute?.handle?.protected, true);
|
||||
});
|
||||
|
||||
@ -22,6 +22,12 @@ export const CatalogsModuleManifest: IModuleClient = {
|
||||
labelKey: "settings.paymentMethods.title",
|
||||
path: "/settings/general/payment-methods",
|
||||
},
|
||||
{
|
||||
id: "tax-regimes",
|
||||
sectionId: "general",
|
||||
labelKey: "settings.taxRegimes.title",
|
||||
path: "/settings/general/tax-regimes",
|
||||
},
|
||||
],
|
||||
},
|
||||
routes: (params) => CatalogsRoutes(params),
|
||||
|
||||
@ -1 +1,2 @@
|
||||
export * from "./payment-methods";
|
||||
export * from "./tax-regimes";
|
||||
|
||||
@ -78,6 +78,7 @@ export const usePaymentMethodsSettingsController = () => {
|
||||
|
||||
const isSaving =
|
||||
createMutation.isPending || updateMutation.isPending || editingPaymentMethodQuery.isLoading;
|
||||
const isTogglingActive = enableMutation.isPending || disableMutation.isPending;
|
||||
|
||||
const closeCreateDialog = useCallback(() => {
|
||||
if (isSaving) {
|
||||
@ -209,6 +210,10 @@ export const usePaymentMethodsSettingsController = () => {
|
||||
|
||||
const enable = useCallback(
|
||||
(paymentMethod: PaymentMethodListRow) => {
|
||||
if (isTogglingActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
enableMutation
|
||||
.mutateAsync({ paymentMethodId: paymentMethod.id })
|
||||
.then(() => {
|
||||
@ -222,11 +227,15 @@ export const usePaymentMethodsSettingsController = () => {
|
||||
showErrorToast(userMessage.title, userMessage.description);
|
||||
});
|
||||
},
|
||||
[enableMutation, t]
|
||||
[enableMutation, isTogglingActive, t]
|
||||
);
|
||||
|
||||
const disable = useCallback(
|
||||
(paymentMethod: PaymentMethodListRow) => {
|
||||
if (isTogglingActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
disableMutation
|
||||
.mutateAsync({ paymentMethodId: paymentMethod.id })
|
||||
.then(() => {
|
||||
@ -240,7 +249,7 @@ export const usePaymentMethodsSettingsController = () => {
|
||||
showErrorToast(userMessage.title, userMessage.description);
|
||||
});
|
||||
},
|
||||
[disableMutation, t]
|
||||
[disableMutation, isTogglingActive, t]
|
||||
);
|
||||
|
||||
const setDefault = useCallback(
|
||||
@ -307,6 +316,7 @@ export const usePaymentMethodsSettingsController = () => {
|
||||
deletingPaymentMethod,
|
||||
isSaving,
|
||||
isDeleting: deleteMutation.isPending,
|
||||
isTogglingActive,
|
||||
isChangingDefault: setDefaultMutation.isPending,
|
||||
openCreateDialog,
|
||||
closeCreateDialog,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@repo/shadcn-ui/components";
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
import { Button, Spinner } from "@repo/shadcn-ui/components";
|
||||
import { MoreHorizontalIcon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
@ -12,6 +12,7 @@ interface PaymentMethodRowActionsProps {
|
||||
onDelete: (paymentMethod: PaymentMethodListRow) => void;
|
||||
activeDisabledReason?: string;
|
||||
deleteDisabledReason?: string;
|
||||
isTogglingActive?: boolean;
|
||||
}
|
||||
|
||||
export const PaymentMethodRowActions = ({
|
||||
@ -21,11 +22,12 @@ export const PaymentMethodRowActions = ({
|
||||
onDelete,
|
||||
activeDisabledReason,
|
||||
deleteDisabledReason,
|
||||
isTogglingActive = false,
|
||||
}: PaymentMethodRowActionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const editDisabled = paymentMethod.isSystem;
|
||||
const toggleDisabled = paymentMethod.isSystem || paymentMethod.isDefault;
|
||||
const toggleDisabled = paymentMethod.isSystem || paymentMethod.isDefault || isTogglingActive;
|
||||
const deleteDisabled = paymentMethod.isSystem || paymentMethod.isDefault;
|
||||
|
||||
return (
|
||||
@ -49,12 +51,14 @@ export const PaymentMethodRowActions = ({
|
||||
? t("settings.paymentMethods.actions.disable")
|
||||
: t("settings.paymentMethods.actions.enable")
|
||||
}
|
||||
aria-busy={isTogglingActive}
|
||||
disabled={toggleDisabled}
|
||||
onClick={() => onToggleActive(paymentMethod)}
|
||||
size="sm"
|
||||
title={toggleDisabled ? activeDisabledReason : undefined}
|
||||
variant="outline"
|
||||
>
|
||||
{isTogglingActive ? <Spinner aria-hidden className="mr-2 size-4" /> : null}
|
||||
{paymentMethod.isActive
|
||||
? t("settings.paymentMethods.actions.disable")
|
||||
: t("settings.paymentMethods.actions.enable")}
|
||||
|
||||
@ -160,6 +160,7 @@ export const PaymentMethodsSettingsPage = () => {
|
||||
paymentMethod.isDefault,
|
||||
paymentMethod.isSystem
|
||||
)}
|
||||
isTogglingActive={featureCtrl.isTogglingActive}
|
||||
onDelete={featureCtrl.openDeleteDialog}
|
||||
onEdit={featureCtrl.openEditDialog}
|
||||
onToggleActive={(item) => {
|
||||
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./use-tax-regimes-settings-controller";
|
||||
export * from "./use-tax-regimes-settings-page-controller";
|
||||
@ -0,0 +1,282 @@
|
||||
import { applyValidationErrorCollection } from "@erp/core";
|
||||
import { getUserErrorMessage } from "@erp/core/client";
|
||||
import { useHookForm } from "@erp/core/hooks";
|
||||
import { UniqueID, ValidationErrorCollection, isValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import {
|
||||
focusFirstInputFormError,
|
||||
showErrorToast,
|
||||
showSuccessToast,
|
||||
showWarningToast,
|
||||
} from "@repo/rdx-ui/helpers";
|
||||
import { useCallback, useEffect, useId, useState } from "react";
|
||||
import type { FormEvent } from "react";
|
||||
import type { FieldErrors } from "react-hook-form";
|
||||
|
||||
import { useTranslation } from "../../../i18n";
|
||||
import {
|
||||
type TaxRegimeListRow,
|
||||
useTaxRegimeCreateMutation,
|
||||
useTaxRegimeDeleteMutation,
|
||||
useTaxRegimeDetailQuery,
|
||||
useTaxRegimeDisableMutation,
|
||||
useTaxRegimeEnableMutation,
|
||||
useTaxRegimeUpdateMutation,
|
||||
} from "../../../tax-regimes/shared";
|
||||
import type { TaxRegimeForm } from "../entities";
|
||||
import { TaxRegimeFormSchema } from "../entities";
|
||||
import { buildTaxRegimeFormDefault, buildTaxRegimeFormFromEntity } from "../utils";
|
||||
import { buildCreateTaxRegimeParams, buildUpdateTaxRegimeParams } from "../utils/build-tax-regime-params";
|
||||
|
||||
const normalizeFormSubmitError = (error: unknown): Error | ValidationErrorCollection => {
|
||||
if (isValidationErrorCollection(error)) {
|
||||
return error;
|
||||
}
|
||||
|
||||
if (error instanceof Error) {
|
||||
return error;
|
||||
}
|
||||
|
||||
return new Error("Unknown error");
|
||||
};
|
||||
|
||||
export const useTaxRegimesSettingsController = () => {
|
||||
const { t } = useTranslation();
|
||||
const formId = useId();
|
||||
const createMutation = useTaxRegimeCreateMutation();
|
||||
const updateMutation = useTaxRegimeUpdateMutation();
|
||||
const deleteMutation = useTaxRegimeDeleteMutation();
|
||||
const enableMutation = useTaxRegimeEnableMutation();
|
||||
const disableMutation = useTaxRegimeDisableMutation();
|
||||
|
||||
const [isCreateOpen, setIsCreateOpen] = useState(false);
|
||||
const [editingTaxRegimeId, setEditingTaxRegimeId] = useState<string | null>(null);
|
||||
const [deletingTaxRegime, setDeletingTaxRegime] = useState<TaxRegimeListRow | null>(null);
|
||||
|
||||
const editingTaxRegimeQuery = useTaxRegimeDetailQuery(editingTaxRegimeId ?? undefined, {
|
||||
enabled: editingTaxRegimeId !== null,
|
||||
});
|
||||
const editingTaxRegime = editingTaxRegimeQuery.data ?? null;
|
||||
|
||||
const form = useHookForm<TaxRegimeForm>({
|
||||
resolverSchema: TaxRegimeFormSchema,
|
||||
initialValues: buildTaxRegimeFormDefault(),
|
||||
disabled:
|
||||
createMutation.isPending || updateMutation.isPending || editingTaxRegimeQuery.isLoading,
|
||||
});
|
||||
|
||||
const isSaving =
|
||||
createMutation.isPending || updateMutation.isPending || editingTaxRegimeQuery.isLoading;
|
||||
const isTogglingActive = enableMutation.isPending || disableMutation.isPending;
|
||||
|
||||
const closeCreateDialog = useCallback(() => {
|
||||
if (isSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreateOpen(false);
|
||||
form.reset(buildTaxRegimeFormDefault(), { keepDirty: false });
|
||||
form.clearErrors();
|
||||
}, [form, isSaving]);
|
||||
|
||||
const openCreateDialog = useCallback(() => {
|
||||
setEditingTaxRegimeId(null);
|
||||
form.reset(buildTaxRegimeFormDefault(), { keepDirty: false });
|
||||
form.clearErrors();
|
||||
setIsCreateOpen(true);
|
||||
}, [form]);
|
||||
|
||||
const openEditDialog = useCallback(
|
||||
(taxRegime: TaxRegimeListRow) => {
|
||||
if (taxRegime.isSystem) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreateOpen(false);
|
||||
setEditingTaxRegimeId(taxRegime.id);
|
||||
form.reset(buildTaxRegimeFormDefault(), { keepDirty: false });
|
||||
form.clearErrors();
|
||||
},
|
||||
[form]
|
||||
);
|
||||
|
||||
const closeEditDialog = useCallback(() => {
|
||||
if (isSaving) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEditingTaxRegimeId(null);
|
||||
form.reset(buildTaxRegimeFormDefault(), { keepDirty: false });
|
||||
form.clearErrors();
|
||||
}, [form, isSaving]);
|
||||
|
||||
const openDeleteDialog = useCallback((taxRegime: TaxRegimeListRow) => {
|
||||
setDeletingTaxRegime(taxRegime);
|
||||
}, []);
|
||||
|
||||
const closeDeleteDialog = useCallback(() => {
|
||||
if (deleteMutation.isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDeletingTaxRegime(null);
|
||||
}, [deleteMutation.isPending]);
|
||||
|
||||
const onInvalidSubmit = useCallback(
|
||||
(errors: FieldErrors<TaxRegimeForm>) => {
|
||||
focusFirstInputFormError(errors, form);
|
||||
showWarningToast(
|
||||
t("settings.taxRegimes.messages.validationTitle"),
|
||||
t("settings.taxRegimes.messages.validationDescription")
|
||||
);
|
||||
},
|
||||
[form, t]
|
||||
);
|
||||
|
||||
const submitTaxRegime = form.handleSubmit(
|
||||
async (values) => {
|
||||
try {
|
||||
if (editingTaxRegime) {
|
||||
await updateMutation.mutateAsync(buildUpdateTaxRegimeParams(editingTaxRegime.id, values));
|
||||
|
||||
showSuccessToast(
|
||||
t("settings.taxRegimes.messages.updatedTitle"),
|
||||
t("settings.taxRegimes.messages.updated")
|
||||
);
|
||||
|
||||
closeEditDialog();
|
||||
return;
|
||||
}
|
||||
|
||||
const id = UniqueID.generateNewID().toString();
|
||||
await createMutation.mutateAsync(buildCreateTaxRegimeParams(id, values));
|
||||
|
||||
showSuccessToast(
|
||||
t("settings.taxRegimes.messages.createdTitle"),
|
||||
t("settings.taxRegimes.messages.created")
|
||||
);
|
||||
|
||||
closeCreateDialog();
|
||||
} catch (error: unknown) {
|
||||
const normalizedError = normalizeFormSubmitError(error);
|
||||
|
||||
if (isValidationErrorCollection(normalizedError)) {
|
||||
applyValidationErrorCollection(form, normalizedError);
|
||||
focusFirstInputFormError(form.formState.errors, form);
|
||||
showWarningToast(
|
||||
t("settings.taxRegimes.messages.validationTitle"),
|
||||
t("settings.taxRegimes.messages.validationDescription")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const userMessage = getUserErrorMessage(normalizedError);
|
||||
showErrorToast(userMessage.title, userMessage.description);
|
||||
}
|
||||
},
|
||||
onInvalidSubmit
|
||||
);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
(event: FormEvent<HTMLFormElement>) => {
|
||||
submitTaxRegime(event).catch((error: unknown) => {
|
||||
const userMessage = getUserErrorMessage(error);
|
||||
showErrorToast(userMessage.title, userMessage.description);
|
||||
});
|
||||
},
|
||||
[submitTaxRegime]
|
||||
);
|
||||
|
||||
const enable = useCallback(
|
||||
(taxRegime: TaxRegimeListRow) => {
|
||||
if (isTogglingActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
enableMutation
|
||||
.mutateAsync({ taxRegimeId: taxRegime.id })
|
||||
.then(() => {
|
||||
showSuccessToast(
|
||||
t("settings.taxRegimes.messages.enabledTitle"),
|
||||
t("settings.taxRegimes.messages.enabled")
|
||||
);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const userMessage = getUserErrorMessage(error);
|
||||
showErrorToast(userMessage.title, userMessage.description);
|
||||
});
|
||||
},
|
||||
[enableMutation, isTogglingActive, t]
|
||||
);
|
||||
|
||||
const disable = useCallback(
|
||||
(taxRegime: TaxRegimeListRow) => {
|
||||
if (isTogglingActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
disableMutation
|
||||
.mutateAsync({ taxRegimeId: taxRegime.id })
|
||||
.then(() => {
|
||||
showSuccessToast(
|
||||
t("settings.taxRegimes.messages.disabledTitle"),
|
||||
t("settings.taxRegimes.messages.disabled")
|
||||
);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const userMessage = getUserErrorMessage(error);
|
||||
showErrorToast(userMessage.title, userMessage.description);
|
||||
});
|
||||
},
|
||||
[disableMutation, isTogglingActive, t]
|
||||
);
|
||||
|
||||
const confirmDelete = useCallback(() => {
|
||||
if (!deletingTaxRegime || deleteMutation.isPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
deleteMutation
|
||||
.mutateAsync({ taxRegimeId: deletingTaxRegime.id })
|
||||
.then(() => {
|
||||
showSuccessToast(
|
||||
t("settings.taxRegimes.messages.deletedTitle"),
|
||||
t("settings.taxRegimes.messages.deleted")
|
||||
);
|
||||
closeDeleteDialog();
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const userMessage = getUserErrorMessage(error);
|
||||
showErrorToast(userMessage.title, userMessage.description);
|
||||
});
|
||||
}, [closeDeleteDialog, deleteMutation, deletingTaxRegime, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editingTaxRegime) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.reset(buildTaxRegimeFormFromEntity(editingTaxRegime), { keepDirty: false });
|
||||
}, [editingTaxRegime, form]);
|
||||
|
||||
return {
|
||||
form,
|
||||
formId,
|
||||
isCreateOpen,
|
||||
editingTaxRegime,
|
||||
isEditingTaxRegimeLoading: editingTaxRegimeQuery.isLoading,
|
||||
deletingTaxRegime,
|
||||
isSaving,
|
||||
isDeleting: deleteMutation.isPending,
|
||||
isTogglingActive,
|
||||
openCreateDialog,
|
||||
closeCreateDialog,
|
||||
openEditDialog,
|
||||
closeEditDialog,
|
||||
openDeleteDialog,
|
||||
closeDeleteDialog,
|
||||
onSubmit,
|
||||
enable,
|
||||
disable,
|
||||
confirmDelete,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,45 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
import { useTranslation } from "../../../i18n";
|
||||
import { useTaxRegimesListQuery } from "../../../tax-regimes/shared";
|
||||
import {
|
||||
buildTaxRegimesSettingsPageErrorViewModel,
|
||||
resolveTaxRegimesSettingsFeedbackState,
|
||||
} from "../utils";
|
||||
|
||||
import { useTaxRegimesSettingsController } from "./use-tax-regimes-settings-controller";
|
||||
|
||||
export const useTaxRegimesSettingsPageController = () => {
|
||||
const { t } = useTranslation();
|
||||
const listQuery = useTaxRegimesListQuery();
|
||||
const controller = useTaxRegimesSettingsController();
|
||||
|
||||
const feedbackState = resolveTaxRegimesSettingsFeedbackState({
|
||||
isLoading: listQuery.isLoading,
|
||||
isError: listQuery.isError,
|
||||
error: listQuery.error,
|
||||
});
|
||||
|
||||
const pageError = useMemo(() => {
|
||||
if (!feedbackState.hasBlockingError) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return buildTaxRegimesSettingsPageErrorViewModel(listQuery.error, (key) => t(key));
|
||||
}, [feedbackState.hasBlockingError, listQuery.error, t]);
|
||||
|
||||
const retry = useCallback(async () => {
|
||||
await listQuery.refetch();
|
||||
}, [listQuery]);
|
||||
|
||||
return {
|
||||
list: listQuery.data?.items ?? [],
|
||||
totalItems: listQuery.data?.totalItems ?? 0,
|
||||
isInitialLoading: feedbackState.isInitialLoading,
|
||||
pageError,
|
||||
retry,
|
||||
isRetrying: listQuery.isFetching && !listQuery.isLoading,
|
||||
listQuery,
|
||||
controller,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,3 @@
|
||||
export * from "./tax-regime-form.entity";
|
||||
export * from "./tax-regime-form.schema";
|
||||
export * from "./tax-regimes-settings-page-error-view-model.entity";
|
||||
@ -0,0 +1,5 @@
|
||||
export interface TaxRegimeForm {
|
||||
code: string;
|
||||
description: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const TaxRegimeFormSchema = z.object({
|
||||
code: z.string().trim().regex(/^\d{2}$/),
|
||||
description: z.string().trim().min(1),
|
||||
isActive: z.boolean(),
|
||||
});
|
||||
|
||||
export type TaxRegimeFormValues = z.infer<typeof TaxRegimeFormSchema>;
|
||||
@ -0,0 +1,9 @@
|
||||
import type { PageErrorStateVariant } from "@erp/core/components";
|
||||
|
||||
export interface TaxRegimesSettingsPageErrorViewModel {
|
||||
variant: PageErrorStateVariant;
|
||||
title: string;
|
||||
description: string;
|
||||
reference?: string;
|
||||
canRetry: boolean;
|
||||
}
|
||||
1
modules/catalogs/src/web/settings/tax-regimes/index.ts
Normal file
1
modules/catalogs/src/web/settings/tax-regimes/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./ui";
|
||||
@ -0,0 +1,62 @@
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
Button,
|
||||
Spinner,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import type { TaxRegimeListRow } from "../../../../tax-regimes/shared";
|
||||
|
||||
interface DeleteTaxRegimeDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
target: TaxRegimeListRow | null;
|
||||
isSubmitting: boolean;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const DeleteTaxRegimeDialog = ({
|
||||
open,
|
||||
onOpenChange,
|
||||
target,
|
||||
isSubmitting,
|
||||
onConfirm,
|
||||
}: DeleteTaxRegimeDialogProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<AlertDialog onOpenChange={onOpenChange} open={open}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t("settings.taxRegimes.delete.title")}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{t("settings.taxRegimes.delete.description", {
|
||||
code: target?.code ?? "",
|
||||
})}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<AlertDialogFooter>
|
||||
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
|
||||
{t("settings.taxRegimes.actions.cancel")}
|
||||
</Button>
|
||||
<Button disabled={isSubmitting || !target} onClick={onConfirm} variant="destructive">
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Spinner aria-hidden className="mr-2 size-4" />
|
||||
{t("settings.taxRegimes.actions.deleting")}
|
||||
</>
|
||||
) : (
|
||||
t("settings.taxRegimes.actions.delete")
|
||||
)}
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,5 @@
|
||||
export * from "./tax-regime-form-dialog";
|
||||
export * from "./delete-tax-regime-dialog";
|
||||
export * from "./tax-regime-row-actions";
|
||||
export * from "./tax-regimes-empty-state";
|
||||
export * from "./tax-regimes-settings-skeleton";
|
||||
@ -0,0 +1,124 @@
|
||||
import { useEffect } from "react";
|
||||
import type { FormEventHandler } from "react";
|
||||
import { FormProvider } from "react-hook-form";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
|
||||
import { RdxFormGrid, SwitchField, TextField } from "@repo/rdx-ui/components";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Spinner,
|
||||
} from "@repo/shadcn-ui/components";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import type { TaxRegimeForm } from "../../entities";
|
||||
|
||||
interface TaxRegimeFormDialogProps {
|
||||
open: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
submitLabel: string;
|
||||
openChange: (open: boolean) => void;
|
||||
formId: string;
|
||||
form: UseFormReturn<TaxRegimeForm>;
|
||||
onSubmit: FormEventHandler<HTMLFormElement>;
|
||||
isSubmitting: boolean;
|
||||
isEditMode: boolean;
|
||||
}
|
||||
|
||||
export const TaxRegimeFormDialog = ({
|
||||
open,
|
||||
title,
|
||||
description,
|
||||
submitLabel,
|
||||
openChange,
|
||||
formId,
|
||||
form,
|
||||
onSubmit,
|
||||
isSubmitting,
|
||||
isEditMode,
|
||||
}: TaxRegimeFormDialogProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
|
||||
form.setFocus(isEditMode ? "description" : "code");
|
||||
}, [form, isEditMode, open]);
|
||||
|
||||
return (
|
||||
<Dialog onOpenChange={openChange} open={open}>
|
||||
<DialogContent className="sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogDescription>{description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<FormProvider {...form}>
|
||||
<form className="space-y-6" id={formId} noValidate onSubmit={onSubmit}>
|
||||
<RdxFormGrid className="grid-cols-1 md:grid-cols-2">
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
className="w-full"
|
||||
description={
|
||||
isEditMode
|
||||
? t("settings.taxRegimes.protections.codeEdit")
|
||||
: t("settings.taxRegimes.fields.codeDescription")
|
||||
}
|
||||
disabled={isSubmitting || isEditMode}
|
||||
label={t("settings.taxRegimes.fields.code")}
|
||||
name="code"
|
||||
placeholder={t("settings.taxRegimes.fields.codePlaceholder")}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
autoComplete="off"
|
||||
className="w-full"
|
||||
description={t("settings.taxRegimes.fields.descriptionDescription")}
|
||||
disabled={isSubmitting}
|
||||
label={t("settings.taxRegimes.fields.description")}
|
||||
name="description"
|
||||
placeholder={t("settings.taxRegimes.fields.descriptionPlaceholder")}
|
||||
/>
|
||||
|
||||
<div className="md:col-span-2">
|
||||
<SwitchField
|
||||
className="flex items-center justify-between rounded-lg border border-border px-4 py-3"
|
||||
description={t("settings.taxRegimes.fields.activeDescription")}
|
||||
disabled={isSubmitting}
|
||||
label={t("settings.taxRegimes.fields.active")}
|
||||
name="isActive"
|
||||
/>
|
||||
</div>
|
||||
</RdxFormGrid>
|
||||
</form>
|
||||
</FormProvider>
|
||||
|
||||
<DialogFooter>
|
||||
<Button disabled={isSubmitting} onClick={() => openChange(false)} variant="outline">
|
||||
{t("settings.taxRegimes.actions.cancel")}
|
||||
</Button>
|
||||
<Button disabled={isSubmitting} form={formId} type="submit">
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Spinner aria-hidden className="mr-2 size-4" />
|
||||
{isEditMode
|
||||
? t("settings.taxRegimes.actions.saving")
|
||||
: t("settings.taxRegimes.actions.creating")}
|
||||
</>
|
||||
) : (
|
||||
submitLabel
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,89 @@
|
||||
import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, Spinner } from "@repo/shadcn-ui/components";
|
||||
import { MoreHorizontalIcon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import type { TaxRegimeListRow } from "../../../../tax-regimes/shared";
|
||||
|
||||
interface TaxRegimeRowActionsProps {
|
||||
taxRegime: TaxRegimeListRow;
|
||||
onEdit: (taxRegime: TaxRegimeListRow) => void;
|
||||
onToggleActive: (taxRegime: TaxRegimeListRow) => void;
|
||||
onDelete: (taxRegime: TaxRegimeListRow) => void;
|
||||
activeDisabledReason?: string;
|
||||
deleteDisabledReason?: string;
|
||||
isTogglingActive?: boolean;
|
||||
}
|
||||
|
||||
export const TaxRegimeRowActions = ({
|
||||
taxRegime,
|
||||
onEdit,
|
||||
onToggleActive,
|
||||
onDelete,
|
||||
activeDisabledReason,
|
||||
deleteDisabledReason,
|
||||
isTogglingActive = false,
|
||||
}: TaxRegimeRowActionsProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const editDisabled = taxRegime.isSystem;
|
||||
const toggleDisabled = taxRegime.isSystem || isTogglingActive;
|
||||
const deleteDisabled = taxRegime.isSystem;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
aria-label={t("settings.taxRegimes.actions.edit")}
|
||||
disabled={editDisabled}
|
||||
onClick={() => onEdit(taxRegime)}
|
||||
size="sm"
|
||||
title={editDisabled ? t("settings.taxRegimes.protections.systemEdit") : undefined}
|
||||
variant="outline"
|
||||
>
|
||||
{t("settings.taxRegimes.actions.edit")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
aria-label={
|
||||
taxRegime.isActive
|
||||
? t("settings.taxRegimes.actions.disable")
|
||||
: t("settings.taxRegimes.actions.enable")
|
||||
}
|
||||
aria-busy={isTogglingActive}
|
||||
disabled={toggleDisabled}
|
||||
onClick={() => onToggleActive(taxRegime)}
|
||||
size="sm"
|
||||
title={toggleDisabled ? activeDisabledReason : undefined}
|
||||
variant="outline"
|
||||
>
|
||||
{isTogglingActive ? <Spinner aria-hidden className="mr-2 size-4" /> : null}
|
||||
{taxRegime.isActive
|
||||
? t("settings.taxRegimes.actions.disable")
|
||||
: t("settings.taxRegimes.actions.enable")}
|
||||
</Button>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger
|
||||
render={
|
||||
<Button aria-label={t("settings.taxRegimes.actions.more")} size="icon" variant="ghost">
|
||||
<MoreHorizontalIcon className="size-4" />
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="w-48"
|
||||
render={
|
||||
<DropdownMenuItem
|
||||
disabled={deleteDisabled}
|
||||
onClick={() => onDelete(taxRegime)}
|
||||
title={deleteDisabled ? deleteDisabledReason : undefined}
|
||||
variant="destructive"
|
||||
>
|
||||
{t("settings.taxRegimes.actions.delete")}
|
||||
</DropdownMenuItem>
|
||||
}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,22 @@
|
||||
import { Button } from "@repo/shadcn-ui/components";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
|
||||
export const TaxRegimesEmptyState = ({ onCreate }: { onCreate: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="rounded-xl border border-dashed border-border bg-muted/20 p-8 text-center">
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-semibold">{t("settings.taxRegimes.empty.title")}</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t("settings.taxRegimes.empty.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button className="mt-6" onClick={onCreate}>
|
||||
{t("settings.taxRegimes.actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import { Skeleton } from "@repo/shadcn-ui/components";
|
||||
|
||||
export const TaxRegimesSettingsSkeleton = () => {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Skeleton className="h-8 w-56" />
|
||||
<Skeleton className="h-4 w-80" />
|
||||
<div className="space-y-3">
|
||||
<Skeleton className="h-24 w-full" />
|
||||
<Skeleton className="h-24 w-full" />
|
||||
<Skeleton className="h-24 w-full" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,2 @@
|
||||
export * from "./pages";
|
||||
export * from "./components";
|
||||
@ -0,0 +1 @@
|
||||
export * from "./tax-regimes-settings-page";
|
||||
@ -0,0 +1,183 @@
|
||||
import { PageErrorState } from "@erp/core/components";
|
||||
import { RdxPageContainer } from "@repo/rdx-ui/components";
|
||||
import { Badge, Button, Card, CardContent } from "@repo/shadcn-ui/components";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
|
||||
import { useTranslation } from "../../../../i18n";
|
||||
import { useTaxRegimesSettingsPageController } from "../../controllers";
|
||||
import { DeleteTaxRegimeDialog } from "../components/delete-tax-regime-dialog";
|
||||
import { TaxRegimeFormDialog } from "../components/tax-regime-form-dialog";
|
||||
import { TaxRegimeRowActions } from "../components/tax-regime-row-actions";
|
||||
import { TaxRegimesEmptyState } from "../components/tax-regimes-empty-state";
|
||||
import { TaxRegimesSettingsSkeleton } from "../components/tax-regimes-settings-skeleton";
|
||||
|
||||
export const TaxRegimesSettingsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const ctrl = useTaxRegimesSettingsPageController();
|
||||
const featureCtrl = ctrl.controller;
|
||||
|
||||
if (ctrl.isInitialLoading) {
|
||||
return <TaxRegimesSettingsSkeleton />;
|
||||
}
|
||||
|
||||
if (ctrl.pageError) {
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
<PageErrorState
|
||||
description={ctrl.pageError.description}
|
||||
primaryAction={
|
||||
ctrl.pageError.canRetry
|
||||
? {
|
||||
label: ctrl.isRetrying
|
||||
? t("settings.taxRegimes.pageError.actions.retrying")
|
||||
: t("settings.taxRegimes.pageError.actions.retry"),
|
||||
onClick: ctrl.retry,
|
||||
isLoading: ctrl.isRetrying,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
reference={ctrl.pageError.reference}
|
||||
title={ctrl.pageError.title}
|
||||
variant={ctrl.pageError.variant}
|
||||
/>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
const activeDisabledReason = (isSystem: boolean) => {
|
||||
if (isSystem) {
|
||||
return t("settings.taxRegimes.protections.systemDisable");
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const deleteDisabledReason = (isSystem: boolean) => {
|
||||
if (isSystem) {
|
||||
return t("settings.taxRegimes.protections.systemDelete");
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<RdxPageContainer width="full">
|
||||
<div className="mx-auto flex w-full max-w-5xl flex-col gap-6">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-2xl font-semibold tracking-tight">
|
||||
{t("settings.taxRegimes.title")}
|
||||
</h2>
|
||||
<p className="max-w-3xl text-sm text-muted-foreground">
|
||||
{t("settings.taxRegimes.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Button onClick={featureCtrl.openCreateDialog}>
|
||||
<PlusIcon aria-hidden className="mr-2 size-4" />
|
||||
{t("settings.taxRegimes.actions.create")}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{ctrl.list.length === 0 ? (
|
||||
<TaxRegimesEmptyState onCreate={featureCtrl.openCreateDialog} />
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{ctrl.list.map((taxRegime) => (
|
||||
<Card key={taxRegime.id}>
|
||||
<CardContent className="flex flex-col gap-4 p-4 md:flex-row md:items-center md:justify-between">
|
||||
<div className="min-w-0 space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="rounded-md border border-border bg-muted px-2 py-1 font-mono text-xs font-medium text-muted-foreground">
|
||||
{taxRegime.code}
|
||||
</span>
|
||||
|
||||
{taxRegime.isSystem ? (
|
||||
<Badge variant="secondary">{t("settings.taxRegimes.status.system")}</Badge>
|
||||
) : null}
|
||||
|
||||
<Badge variant={taxRegime.isActive ? "outline" : "secondary"}>
|
||||
{taxRegime.isActive
|
||||
? t("settings.taxRegimes.status.active")
|
||||
: t("settings.taxRegimes.status.inactive")}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-foreground">{taxRegime.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TaxRegimeRowActions
|
||||
activeDisabledReason={activeDisabledReason(taxRegime.isSystem)}
|
||||
deleteDisabledReason={deleteDisabledReason(taxRegime.isSystem)}
|
||||
isTogglingActive={featureCtrl.isTogglingActive}
|
||||
onDelete={featureCtrl.openDeleteDialog}
|
||||
onEdit={featureCtrl.openEditDialog}
|
||||
onToggleActive={(item) => {
|
||||
if (item.isActive) {
|
||||
featureCtrl.disable(item);
|
||||
return;
|
||||
}
|
||||
|
||||
featureCtrl.enable(item);
|
||||
}}
|
||||
taxRegime={taxRegime}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TaxRegimeFormDialog
|
||||
description={t("settings.taxRegimes.create.description")}
|
||||
form={featureCtrl.form}
|
||||
formId={featureCtrl.formId}
|
||||
isEditMode={false}
|
||||
isSubmitting={featureCtrl.isSaving}
|
||||
onSubmit={featureCtrl.onSubmit}
|
||||
open={featureCtrl.isCreateOpen}
|
||||
openChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeCreateDialog();
|
||||
}
|
||||
}}
|
||||
submitLabel={t("settings.taxRegimes.actions.create")}
|
||||
title={t("settings.taxRegimes.create.title")}
|
||||
/>
|
||||
|
||||
<TaxRegimeFormDialog
|
||||
description={t("settings.taxRegimes.edit.description")}
|
||||
form={featureCtrl.form}
|
||||
formId={`${featureCtrl.formId}-edit`}
|
||||
isEditMode
|
||||
isSubmitting={featureCtrl.isSaving || featureCtrl.isEditingTaxRegimeLoading}
|
||||
onSubmit={featureCtrl.onSubmit}
|
||||
open={Boolean(featureCtrl.editingTaxRegime) || featureCtrl.isEditingTaxRegimeLoading}
|
||||
openChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeEditDialog();
|
||||
}
|
||||
}}
|
||||
submitLabel={t("settings.taxRegimes.actions.save")}
|
||||
title={t("settings.taxRegimes.edit.title")}
|
||||
/>
|
||||
|
||||
<DeleteTaxRegimeDialog
|
||||
isSubmitting={featureCtrl.isDeleting}
|
||||
onConfirm={featureCtrl.confirmDelete}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
featureCtrl.closeDeleteDialog();
|
||||
}
|
||||
}}
|
||||
open={Boolean(featureCtrl.deletingTaxRegime)}
|
||||
target={featureCtrl.deletingTaxRegime}
|
||||
/>
|
||||
</div>
|
||||
</RdxPageContainer>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import type { TaxRegime } from "../../../tax-regimes/shared";
|
||||
import type { TaxRegimeForm } from "../entities";
|
||||
|
||||
export const buildTaxRegimeFormDefault = (): TaxRegimeForm => ({
|
||||
code: "",
|
||||
description: "",
|
||||
isActive: true,
|
||||
});
|
||||
|
||||
export const buildTaxRegimeFormFromEntity = (taxRegime: TaxRegime): TaxRegimeForm => ({
|
||||
code: taxRegime.code,
|
||||
description: taxRegime.description,
|
||||
isActive: taxRegime.isActive,
|
||||
});
|
||||
@ -0,0 +1,26 @@
|
||||
import type { CreateTaxRegimeParams, UpdateTaxRegimeParams } from "../../../tax-regimes/shared";
|
||||
import type { TaxRegimeForm } from "../entities";
|
||||
|
||||
export const buildCreateTaxRegimeParams = (
|
||||
id: string,
|
||||
form: TaxRegimeForm
|
||||
): CreateTaxRegimeParams => ({
|
||||
id,
|
||||
data: {
|
||||
id,
|
||||
code: form.code.trim(),
|
||||
description: form.description.trim(),
|
||||
is_active: form.isActive,
|
||||
},
|
||||
});
|
||||
|
||||
export const buildUpdateTaxRegimeParams = (
|
||||
taxRegimeId: string,
|
||||
form: TaxRegimeForm
|
||||
): UpdateTaxRegimeParams => ({
|
||||
taxRegimeId,
|
||||
data: {
|
||||
description: form.description.trim(),
|
||||
is_active: form.isActive,
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,43 @@
|
||||
import { isApiClientError } from "@erp/core/client";
|
||||
|
||||
import type { TaxRegimesSettingsPageErrorViewModel } from "../entities";
|
||||
|
||||
export const buildTaxRegimesSettingsPageErrorViewModel = (
|
||||
error: unknown,
|
||||
translate: (key: string) => string
|
||||
): TaxRegimesSettingsPageErrorViewModel => {
|
||||
if (!isApiClientError(error)) {
|
||||
return {
|
||||
variant: "server",
|
||||
title: translate("settings.taxRegimes.pageError.unknown.title"),
|
||||
description: translate("settings.taxRegimes.pageError.unknown.description"),
|
||||
canRetry: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (error.kind === "forbidden") {
|
||||
return {
|
||||
variant: "forbidden",
|
||||
title: translate("settings.taxRegimes.pageError.forbidden.title"),
|
||||
description: translate("settings.taxRegimes.pageError.forbidden.description"),
|
||||
reference: error.correlationId,
|
||||
canRetry: false,
|
||||
};
|
||||
}
|
||||
|
||||
const variant =
|
||||
error.kind === "network" ||
|
||||
error.kind === "timeout" ||
|
||||
error.kind === "unavailable" ||
|
||||
error.kind === "notFound"
|
||||
? error.kind
|
||||
: "server";
|
||||
|
||||
return {
|
||||
variant,
|
||||
title: translate(`settings.taxRegimes.pageError.${variant}.title`),
|
||||
description: translate(`settings.taxRegimes.pageError.${variant}.description`),
|
||||
reference: error.correlationId,
|
||||
canRetry: true,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,4 @@
|
||||
export * from "./build-tax-regime-form";
|
||||
export * from "./build-tax-regime-params";
|
||||
export * from "./build-tax-regimes-settings-page-error-view-model";
|
||||
export * from "./resolve-tax-regimes-settings-feedback-state";
|
||||
@ -0,0 +1,14 @@
|
||||
import { isApiClientError } from "@erp/core/client";
|
||||
|
||||
export const resolveTaxRegimesSettingsFeedbackState = (params: {
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
error: unknown;
|
||||
}) => {
|
||||
const isCancelled = isApiClientError(params.error) && params.error.kind === "cancelled";
|
||||
|
||||
return {
|
||||
isInitialLoading: params.isLoading,
|
||||
hasBlockingError: params.isError && !isCancelled,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,15 @@
|
||||
import type { TaxRegimeDetailDTO } from "../../../../common";
|
||||
import type { TaxRegime } from "../entities";
|
||||
|
||||
export const GetTaxRegimeByIdAdapter = {
|
||||
fromDto(dto: TaxRegimeDetailDTO): TaxRegime {
|
||||
return {
|
||||
id: dto.id,
|
||||
companyId: dto.company_id,
|
||||
code: dto.code,
|
||||
description: dto.description,
|
||||
isActive: dto.is_active,
|
||||
isSystem: dto.is_system,
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -1 +1,2 @@
|
||||
export * from "./list-tax-regimes.adapter";
|
||||
export * from "./get-tax-regime-by-id.adapter";
|
||||
|
||||
@ -15,13 +15,13 @@ import type { TaxRegimeList, TaxRegimeListRow } from "../entities";
|
||||
*/
|
||||
|
||||
export const ListTaxRegimesAdapter = {
|
||||
fromDto(dto: ListTaxRegimesResult, context?: unknown): TaxRegimeList {
|
||||
fromDto(dto: ListTaxRegimesResult): TaxRegimeList {
|
||||
return {
|
||||
page: dto.page,
|
||||
perPage: dto.per_page,
|
||||
totalPages: dto.total_pages,
|
||||
totalItems: dto.total_items,
|
||||
items: dto.items.map((rowDto) => TaxRegimeListRowAdapter.fromDto(rowDto, context)),
|
||||
items: dto.items.map((rowDto) => TaxRegimeListRowAdapter.fromDto(rowDto)),
|
||||
};
|
||||
},
|
||||
};
|
||||
@ -39,7 +39,7 @@ export const ListTaxRegimesAdapter = {
|
||||
type ListTaxRegimesItemOutput = ListTaxRegimesResponseDTO["items"][number];
|
||||
|
||||
const TaxRegimeListRowAdapter = {
|
||||
fromDto(dto: ListTaxRegimesItemOutput, context?: unknown): TaxRegimeListRow {
|
||||
fromDto(dto: ListTaxRegimesItemOutput): TaxRegimeListRow {
|
||||
return {
|
||||
id: dto.id,
|
||||
companyId: dto.company_id,
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { CreateTaxRegimeRequestDTO, CreateTaxRegimeResponseDTO } from "../../../../common";
|
||||
|
||||
export interface CreateTaxRegimeParams {
|
||||
id: string;
|
||||
data: CreateTaxRegimeRequestDTO;
|
||||
}
|
||||
|
||||
export function createTaxRegime(
|
||||
dataSource: IDataSource,
|
||||
params: CreateTaxRegimeParams
|
||||
): Promise<CreateTaxRegimeResponseDTO> {
|
||||
return dataSource.createOne<CreateTaxRegimeRequestDTO, CreateTaxRegimeResponseDTO>(
|
||||
"catalogs/tax-regimes",
|
||||
params.data
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
export interface DeleteTaxRegimeParams {
|
||||
taxRegimeId: string;
|
||||
}
|
||||
|
||||
export function deleteTaxRegime(dataSource: IDataSource, params: DeleteTaxRegimeParams) {
|
||||
return dataSource.deleteOne("catalogs/tax-regimes", params.taxRegimeId);
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { DisableTaxRegimeByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface DisableTaxRegimeParams {
|
||||
taxRegimeId: string;
|
||||
}
|
||||
|
||||
export function disableTaxRegime(
|
||||
dataSource: IDataSource,
|
||||
params: DisableTaxRegimeParams
|
||||
): Promise<DisableTaxRegimeByIdResponseDTO> {
|
||||
return dataSource.custom<undefined, DisableTaxRegimeByIdResponseDTO>({
|
||||
method: "patch",
|
||||
path: `catalogs/tax-regimes/${params.taxRegimeId}/disable`,
|
||||
data: undefined,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { EnableTaxRegimeByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface EnableTaxRegimeParams {
|
||||
taxRegimeId: string;
|
||||
}
|
||||
|
||||
export function enableTaxRegime(
|
||||
dataSource: IDataSource,
|
||||
params: EnableTaxRegimeParams
|
||||
): Promise<EnableTaxRegimeByIdResponseDTO> {
|
||||
return dataSource.custom<undefined, EnableTaxRegimeByIdResponseDTO>({
|
||||
method: "patch",
|
||||
path: `catalogs/tax-regimes/${params.taxRegimeId}/enable`,
|
||||
data: undefined,
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { GetTaxRegimeByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface GetTaxRegimeByIdParams {
|
||||
taxRegimeId: string;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export function getTaxRegimeById(
|
||||
dataSource: IDataSource,
|
||||
params: GetTaxRegimeByIdParams
|
||||
): Promise<GetTaxRegimeByIdResponseDTO> {
|
||||
return dataSource.getOne<GetTaxRegimeByIdResponseDTO>("catalogs/tax-regimes", params.taxRegimeId, {
|
||||
signal: params.signal,
|
||||
});
|
||||
}
|
||||
@ -1 +1,7 @@
|
||||
export * from "./list-tax-regimes-by-criteria.api";
|
||||
export * from "./get-tax-regime-by-id.api";
|
||||
export * from "./create-tax-regime.api";
|
||||
export * from "./update-tax-regime.api";
|
||||
export * from "./delete-tax-regime.api";
|
||||
export * from "./enable-tax-regime.api";
|
||||
export * from "./disable-tax-regime.api";
|
||||
|
||||
@ -0,0 +1,19 @@
|
||||
import type { IDataSource } from "@erp/core/client";
|
||||
|
||||
import type { UpdateTaxRegimeByIdRequestDTO, UpdateTaxRegimeByIdResponseDTO } from "../../../../common";
|
||||
|
||||
export interface UpdateTaxRegimeParams {
|
||||
taxRegimeId: string;
|
||||
data: UpdateTaxRegimeByIdRequestDTO;
|
||||
}
|
||||
|
||||
export function updateTaxRegime(
|
||||
dataSource: IDataSource,
|
||||
params: UpdateTaxRegimeParams
|
||||
): Promise<UpdateTaxRegimeByIdResponseDTO> {
|
||||
return dataSource.updateOne<UpdateTaxRegimeByIdRequestDTO, UpdateTaxRegimeByIdResponseDTO>(
|
||||
"catalogs/tax-regimes",
|
||||
params.taxRegimeId,
|
||||
params.data
|
||||
);
|
||||
}
|
||||
@ -1 +1,7 @@
|
||||
export * from "./use-tax-regime-list-query";
|
||||
export * from "./use-tax-regime-detail-query";
|
||||
export * from "./use-tax-regime-create-mutation";
|
||||
export * from "./use-tax-regime-update-mutation";
|
||||
export * from "./use-tax-regime-delete-mutation";
|
||||
export * from "./use-tax-regime-enable-mutation";
|
||||
export * from "./use-tax-regime-disable-mutation";
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { INITIAL_PAGE_INDEX, INITIAL_PAGE_SIZE } from "@repo/rdx-criteria";
|
||||
import type { QueryKey } from "@tanstack/react-query";
|
||||
|
||||
import type { ListTaxRegimesRequestDTO } from "../../../../common";
|
||||
@ -14,8 +15,8 @@ export const LIST_TAX_REGIMES_QUERY_KEY = (criteria?: ListTaxRegimesRequestDTO):
|
||||
[
|
||||
...LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
{
|
||||
pageNumber: criteria?.pageNumber ?? 1,
|
||||
pageSize: criteria?.pageSize ?? 5,
|
||||
pageNumber: criteria?.pageNumber ?? INITIAL_PAGE_INDEX,
|
||||
pageSize: criteria?.pageSize ?? INITIAL_PAGE_SIZE,
|
||||
q: criteria?.q ?? "",
|
||||
filters: criteria?.filters ?? [],
|
||||
orderBy: criteria?.orderBy ?? "",
|
||||
@ -38,7 +39,14 @@ export const TAX_REGIME_QUERY_KEY = (taxRegimeId?: string): QueryKey => [
|
||||
export const CREATE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:create"] as const;
|
||||
export const UPDATE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:update"] as const;
|
||||
export const DELETE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:delete"] as const;
|
||||
export const ENABLE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:enable"] as const;
|
||||
export const DISABLE_TAX_REGIME_MUTATION_KEY = ["tax-regimes:disable"] as const;
|
||||
|
||||
/**
|
||||
* Operaciones de dominio
|
||||
*/
|
||||
export const taxRegimeKeys = {
|
||||
all: LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
list: (criteria?: ListTaxRegimesRequestDTO) => LIST_TAX_REGIMES_QUERY_KEY(criteria),
|
||||
detail: (taxRegimeId?: string) => TAX_REGIME_QUERY_KEY(taxRegimeId),
|
||||
};
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import type { ValidationErrorDetail } from "@repo/rdx-ddd";
|
||||
import type { ZodError } from "zod";
|
||||
|
||||
export const toValidationErrors = (error: ZodError<unknown>): ValidationErrorDetail[] =>
|
||||
error.issues.map((issue) => ({
|
||||
path: issue.path.join("."),
|
||||
message: issue.message,
|
||||
value: issue.input,
|
||||
}));
|
||||
@ -0,0 +1,44 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { CreateTaxRegimeRequestSchema } from "../../../../common";
|
||||
import { GetTaxRegimeByIdAdapter } from "../adapters";
|
||||
import { createTaxRegime, type CreateTaxRegimeParams } from "../api";
|
||||
import type { TaxRegime } from "../entities";
|
||||
|
||||
import {
|
||||
CREATE_TAX_REGIME_MUTATION_KEY,
|
||||
LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
TAX_REGIME_QUERY_KEY,
|
||||
} from "./keys";
|
||||
import { toValidationErrors } from "./to-validation-errors";
|
||||
|
||||
export const useTaxRegimeCreateMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
|
||||
return useMutation<TaxRegime, DefaultError, CreateTaxRegimeParams>({
|
||||
mutationKey: CREATE_TAX_REGIME_MUTATION_KEY,
|
||||
mutationFn: async (params) => {
|
||||
if (!params.id) {
|
||||
throw new Error("taxRegimeId is required");
|
||||
}
|
||||
|
||||
const result = CreateTaxRegimeRequestSchema.safeParse(params.data);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
|
||||
}
|
||||
|
||||
const dto = await createTaxRegime(dataSource, params);
|
||||
return GetTaxRegimeByIdAdapter.fromDto(dto);
|
||||
},
|
||||
onSuccess: async (taxRegime) => {
|
||||
queryClient.setQueryData(TAX_REGIME_QUERY_KEY(taxRegime.id), taxRegime);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,28 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { deleteTaxRegime, type DeleteTaxRegimeParams } from "../api";
|
||||
|
||||
import {
|
||||
DELETE_TAX_REGIME_MUTATION_KEY,
|
||||
LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
TAX_REGIMES_DETAIL_QUERY_KEY_PREFIX,
|
||||
} from "./keys";
|
||||
|
||||
export const useTaxRegimeDeleteMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
|
||||
return useMutation<void, DefaultError, DeleteTaxRegimeParams>({
|
||||
mutationKey: DELETE_TAX_REGIME_MUTATION_KEY,
|
||||
mutationFn: async (params) => {
|
||||
await deleteTaxRegime(dataSource, params);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: LIST_TAX_REGIMES_QUERY_KEY_PREFIX }),
|
||||
queryClient.invalidateQueries({ queryKey: TAX_REGIMES_DETAIL_QUERY_KEY_PREFIX }),
|
||||
]);
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { GetTaxRegimeByIdAdapter } from "../adapters";
|
||||
import { getTaxRegimeById } from "../api";
|
||||
import type { TaxRegime } from "../entities";
|
||||
|
||||
import { TAX_REGIME_QUERY_KEY } from "./keys";
|
||||
|
||||
export const useTaxRegimeDetailQuery = (
|
||||
taxRegimeId?: string,
|
||||
options?: { enabled?: boolean }
|
||||
): UseQueryResult<TaxRegime, DefaultError> => {
|
||||
const dataSource = useDataSource();
|
||||
const enabled = (options?.enabled ?? true) && Boolean(taxRegimeId);
|
||||
|
||||
return useQuery<TaxRegime, DefaultError>({
|
||||
queryKey: TAX_REGIME_QUERY_KEY(taxRegimeId),
|
||||
enabled,
|
||||
queryFn: async ({ signal }) => {
|
||||
const dto = await getTaxRegimeById(dataSource, {
|
||||
taxRegimeId: taxRegimeId as string,
|
||||
signal,
|
||||
});
|
||||
|
||||
return GetTaxRegimeByIdAdapter.fromDto(dto);
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { GetTaxRegimeByIdAdapter } from "../adapters";
|
||||
import { disableTaxRegime, type DisableTaxRegimeParams } from "../api";
|
||||
import type { TaxRegime } from "../entities";
|
||||
|
||||
import {
|
||||
DISABLE_TAX_REGIME_MUTATION_KEY,
|
||||
LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
TAX_REGIME_QUERY_KEY,
|
||||
} from "./keys";
|
||||
|
||||
export const useTaxRegimeDisableMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
|
||||
return useMutation<TaxRegime, DefaultError, DisableTaxRegimeParams>({
|
||||
mutationKey: DISABLE_TAX_REGIME_MUTATION_KEY,
|
||||
mutationFn: async (params) => {
|
||||
const dto = await disableTaxRegime(dataSource, params);
|
||||
return GetTaxRegimeByIdAdapter.fromDto(dto);
|
||||
},
|
||||
onSuccess: async (taxRegime) => {
|
||||
queryClient.setQueryData(TAX_REGIME_QUERY_KEY(taxRegime.id), taxRegime);
|
||||
await queryClient.invalidateQueries({ queryKey: LIST_TAX_REGIMES_QUERY_KEY_PREFIX });
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,29 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { GetTaxRegimeByIdAdapter } from "../adapters";
|
||||
import { enableTaxRegime, type EnableTaxRegimeParams } from "../api";
|
||||
import type { TaxRegime } from "../entities";
|
||||
|
||||
import {
|
||||
ENABLE_TAX_REGIME_MUTATION_KEY,
|
||||
LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
TAX_REGIME_QUERY_KEY,
|
||||
} from "./keys";
|
||||
|
||||
export const useTaxRegimeEnableMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
|
||||
return useMutation<TaxRegime, DefaultError, EnableTaxRegimeParams>({
|
||||
mutationKey: ENABLE_TAX_REGIME_MUTATION_KEY,
|
||||
mutationFn: async (params) => {
|
||||
const dto = await enableTaxRegime(dataSource, params);
|
||||
return GetTaxRegimeByIdAdapter.fromDto(dto);
|
||||
},
|
||||
onSuccess: async (taxRegime) => {
|
||||
queryClient.setQueryData(TAX_REGIME_QUERY_KEY(taxRegime.id), taxRegime);
|
||||
await queryClient.invalidateQueries({ queryKey: LIST_TAX_REGIMES_QUERY_KEY_PREFIX });
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -0,0 +1,44 @@
|
||||
import { useDataSource } from "@erp/core/hooks";
|
||||
import { ValidationErrorCollection } from "@repo/rdx-ddd";
|
||||
import { type DefaultError, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { UpdateTaxRegimeByIdRequestSchema } from "../../../../common";
|
||||
import { GetTaxRegimeByIdAdapter } from "../adapters";
|
||||
import { type UpdateTaxRegimeParams, updateTaxRegime } from "../api";
|
||||
import type { TaxRegime } from "../entities";
|
||||
|
||||
import {
|
||||
LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
TAX_REGIME_QUERY_KEY,
|
||||
UPDATE_TAX_REGIME_MUTATION_KEY,
|
||||
} from "./keys";
|
||||
import { toValidationErrors } from "./to-validation-errors";
|
||||
|
||||
export const useTaxRegimeUpdateMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const dataSource = useDataSource();
|
||||
|
||||
return useMutation<TaxRegime, DefaultError, UpdateTaxRegimeParams>({
|
||||
mutationKey: UPDATE_TAX_REGIME_MUTATION_KEY,
|
||||
mutationFn: async (params) => {
|
||||
if (!params.taxRegimeId) {
|
||||
throw new Error("taxRegimeId is required");
|
||||
}
|
||||
|
||||
const result = UpdateTaxRegimeByIdRequestSchema.safeParse(params.data);
|
||||
|
||||
if (!result.success) {
|
||||
throw new ValidationErrorCollection("Validation failed", toValidationErrors(result.error));
|
||||
}
|
||||
|
||||
const dto = await updateTaxRegime(dataSource, params);
|
||||
return GetTaxRegimeByIdAdapter.fromDto(dto);
|
||||
},
|
||||
onSuccess: async (taxRegime) => {
|
||||
queryClient.setQueryData(TAX_REGIME_QUERY_KEY(taxRegime.id), taxRegime);
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: LIST_TAX_REGIMES_QUERY_KEY_PREFIX,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -1,2 +1,4 @@
|
||||
export * from "./entities";
|
||||
export * from "./hooks";
|
||||
export * from "./api";
|
||||
export * from "./adapters";
|
||||
|
||||
Loading…
Reference in New Issue
Block a user