Añadir funcionalidad para eliminar procesos y mejorar la gestión de procesos en la aplicación
This commit is contained in:
parent
a622eacfe7
commit
564b666fcc
@ -19,6 +19,7 @@ export interface IProcessRepository {
|
||||
action: (context: ProcessCatalogContext) => Result<ProcessTemplate, ProcessError>
|
||||
): Promise<Result<{ template: ProcessTemplate; context: ProcessCatalogContext }, ProcessError>>;
|
||||
get(company: UniqueID): Promise<Result<ProcessContext, ProcessError>>;
|
||||
remove(company: UniqueID, process: UniqueID): Promise<Result<void, ProcessError>>;
|
||||
save(
|
||||
company: UniqueID,
|
||||
action: (context: ProcessContext) => Result<PersonProcess, ProcessError>
|
||||
|
||||
@ -105,6 +105,12 @@ export class ProcessUseCases {
|
||||
: values;
|
||||
});
|
||||
}
|
||||
remove(companyId: string, id: string) {
|
||||
const company = UniqueID.create(companyId);
|
||||
const process = UniqueID.create(id);
|
||||
if (!(company.isSuccess && process.isSuccess)) return Promise.resolve(Result.fail(new ProcessError("INVALID_REQUEST")));
|
||||
return this.repository.remove(company.data, process.data);
|
||||
}
|
||||
update(companyId: string, id: string, input: UpdateProcessInput, actorId: string) {
|
||||
const company = UniqueID.create(companyId);
|
||||
const actor = UniqueID.create(actorId);
|
||||
|
||||
@ -146,6 +146,15 @@ export class SequelizeProcessRepository implements IProcessRepository {
|
||||
return Result.ok({ template: candidate.data, context });
|
||||
});
|
||||
}
|
||||
remove(company: UniqueID, process: UniqueID) {
|
||||
return this.database.transaction(async (transaction) => {
|
||||
const rows = await this.database.query<{ id: string }>("SELECT id FROM person_processes WHERE id=:id AND companyId=:companyId FOR UPDATE", { replacements: { id: process.toPrimitive(), companyId: company.toPrimitive() }, transaction, type: QueryTypes.SELECT });
|
||||
if (!rows.length) return Result.fail(new ProcessError("PROCESS_NOT_FOUND"));
|
||||
await this.database.query("DELETE FROM person_process_revisions WHERE itemId=:id", { replacements: { id: process.toPrimitive() }, transaction });
|
||||
await this.database.query("DELETE FROM person_processes WHERE id=:id AND companyId=:companyId", { replacements: { id: process.toPrimitive(), companyId: company.toPrimitive() }, transaction });
|
||||
return Result.ok(undefined);
|
||||
});
|
||||
}
|
||||
save(company: UniqueID, action: Parameters<IProcessRepository["save"]>[1]) {
|
||||
return this.database.transaction(async (transaction) => {
|
||||
const loaded = await this.load(company, transaction, true);
|
||||
|
||||
@ -327,7 +327,9 @@
|
||||
"INTERNAL_ERROR": "The operation could not be completed. Try again.",
|
||||
"FORBIDDEN": "Sign in again.",
|
||||
"UNAUTHENTICATED": "Sign in again."
|
||||
}
|
||||
},
|
||||
"delete": "Delete process",
|
||||
"deleteConfirm": "Do you want to delete the process “{{name}}”? This also deletes its history."
|
||||
},
|
||||
"remote": {
|
||||
"title": "Remote access",
|
||||
|
||||
@ -327,7 +327,9 @@
|
||||
"INTERNAL_ERROR": "No se ha podido completar la operación. Inténtalo de nuevo.",
|
||||
"FORBIDDEN": "Vuelve a iniciar sesión.",
|
||||
"UNAUTHENTICATED": "Vuelve a iniciar sesión."
|
||||
}
|
||||
},
|
||||
"delete": "Eliminar proceso",
|
||||
"deleteConfirm": "¿Quieres eliminar el proceso «{{name}}»? Esta acción también elimina su historial."
|
||||
},
|
||||
"remote": {
|
||||
"title": "Accesos remotos",
|
||||
|
||||
@ -4,4 +4,6 @@ export type ProcessDetailProps = {
|
||||
process: PersonProcessDTO;
|
||||
person: string;
|
||||
csrfToken: string;
|
||||
onDelete?: (id: string) => void;
|
||||
onDeleted?: () => void;
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Button } from "@repo/shadcn-ui/components/button";
|
||||
import { useState } from "react";
|
||||
import { Card, CardContent } from "@repo/shadcn-ui/components/card";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
@ -9,9 +10,10 @@ import { useProcessDetailController } from "../controllers";
|
||||
import type { ProcessDetailProps } from "../types";
|
||||
|
||||
export function ProcessDetail(props: ProcessDetailProps) {
|
||||
const { process, person, csrfToken } = props;
|
||||
const { process, person, csrfToken, onDelete, onDeleted } = props;
|
||||
const { edit, setEdit, refresh, open, resolved } = useProcessDetailController(props);
|
||||
const { t, i18n } = useTranslation("companies");
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<header className="flex flex-wrap items-start justify-between gap-3">
|
||||
@ -39,6 +41,18 @@ export function ProcessDetail(props: ProcessDetailProps) {
|
||||
<Button disabled={refresh.isPending} onClick={() => setEdit(true)}>
|
||||
{t("processes.update")}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={deleting}
|
||||
variant="destructive"
|
||||
onClick={() => {
|
||||
if (!window.confirm(t("processes.deleteConfirm", { name: process.values.name }))) return;
|
||||
setDeleting(true);
|
||||
onDelete?.(process.id);
|
||||
onDeleted?.();
|
||||
}}
|
||||
>
|
||||
{t("processes.delete")}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
@ -4,3 +4,7 @@ import { processRequest } from "../../shared/api/process-request";
|
||||
export async function getProcesses(company: string) {
|
||||
return ProcessesResponseSchema.parse(await processRequest(company));
|
||||
}
|
||||
|
||||
export async function deleteProcess(company: string, id: string, token: string) {
|
||||
await processRequest(`${company}/${id}/delete`, {}, token);
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
export { getProcesses } from "./get-processes.api";
|
||||
export { deleteProcess, getProcesses } from "./get-processes.api";
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { useBrowserSearchParams, useEntityNavigation } from "@metis/core/client";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { processKeys } from "../../shared/queries";
|
||||
import { getProcesses } from "../api";
|
||||
import { deleteProcess, getProcesses } from "../api";
|
||||
import type { ProcessesPageProps } from "../types";
|
||||
|
||||
export function useProcessesPageController({ company }: ProcessesPageProps) {
|
||||
const { t } = useTranslation("companies");
|
||||
const client = useQueryClient();
|
||||
const [create, setCreate] = useState(false);
|
||||
const { selectedId: selected, selectEntity: setSelected } = useEntityNavigation(
|
||||
`/companies/${company.id}/processes`
|
||||
@ -32,6 +33,7 @@ export function useProcessesPageController({ company }: ProcessesPageProps) {
|
||||
params.delete("closed");
|
||||
}
|
||||
});
|
||||
const remove = useMutation({ mutationFn: ({ id, token }: { id: string; token: string }) => deleteProcess(company.id, id, token), onSuccess: async () => { await client.invalidateQueries({ queryKey: processKeys.company(company.id) }); setSelected(undefined); } });
|
||||
const query = useQuery({
|
||||
queryKey: processKeys.company(company.id),
|
||||
queryFn: () => getProcesses(company.id),
|
||||
@ -56,6 +58,7 @@ export function useProcessesPageController({ company }: ProcessesPageProps) {
|
||||
closed,
|
||||
setClosed,
|
||||
query,
|
||||
remove,
|
||||
person,
|
||||
current,
|
||||
missingView: Boolean(selected && query.isSuccess && !current),
|
||||
|
||||
@ -28,6 +28,7 @@ export function ProcessesPage(props: ProcessesPageProps) {
|
||||
closed,
|
||||
setClosed,
|
||||
query,
|
||||
remove,
|
||||
person,
|
||||
current,
|
||||
missingView,
|
||||
@ -66,6 +67,8 @@ export function ProcessesPage(props: ProcessesPageProps) {
|
||||
csrfToken={csrfToken}
|
||||
person={person(current.values.personId)}
|
||||
process={current}
|
||||
onDeleted={() => setSelected(undefined)}
|
||||
onDelete={(id) => remove.mutate({ id, token: csrfToken })}
|
||||
/>
|
||||
) : (
|
||||
!selected && (
|
||||
@ -150,10 +153,16 @@ export function ProcessesPage(props: ProcessesPageProps) {
|
||||
hideable: false,
|
||||
render: (p) => (
|
||||
<EntityRowActions label={t("actions")}>
|
||||
{" "}
|
||||
<EntityAction onClick={() => setSelected(p.id)}>
|
||||
{t("processes.openDetail")}
|
||||
</EntityAction>{" "}
|
||||
<EntityAction onClick={() => setSelected(p.id)}>{t("processes.openDetail")}</EntityAction>
|
||||
<EntityAction
|
||||
onClick={() => {
|
||||
if (window.confirm(t("processes.deleteConfirm", { name: p.values.name }))) {
|
||||
remove.mutate({ id: p.id, token: csrfToken });
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("processes.delete")}
|
||||
</EntityAction>
|
||||
</EntityRowActions>
|
||||
),
|
||||
},
|
||||
|
||||
@ -10,7 +10,7 @@ export async function processRequest(path: string, body?: unknown, token?: strin
|
||||
body === undefined ? {} : { "content-type": "application/json", "x-csrf-token": token ?? "" },
|
||||
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
||||
});
|
||||
const data: unknown = await response.json();
|
||||
const data: unknown = response.status === 204 ? undefined : await response.json();
|
||||
if (!response.ok) {
|
||||
const error = z.object({ code: z.string() }).safeParse(data);
|
||||
throw new ProcessApiError(error.success ? error.data.code : "INTERNAL_ERROR");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user