From 6c6eee8f931a954b8c4e82eea8b5bd2bb602d320 Mon Sep 17 00:00:00 2001 From: David Date: Sat, 10 Oct 2026 00:19:35 +0200 Subject: [PATCH] feat: implement record deletion functionality across various configurations - Added remove methods to repositories for application, certificate, folder, mail, and printer configurations. - Implemented use cases for removing configurations, including validation for company and identity. - Created HTTP routes for deleting configurations with appropriate request handling and error responses. - Introduced a DeleteRecordDialog component for user confirmation before deletion. - Updated UI pages to include delete actions for configurations with modal confirmation. - Added integration tests to verify record deletion functionality and ensure data integrity. - Documented the record deletion process and its implications in the project documentation. --- apps/server/src/identity.test.ts | 2 + .../server/src/record-deletion.integration.ts | 139 ++++++++++++++++++ docs/componentes-de-interfaz.md | 6 + docs/eliminacion-de-registros.md | 11 ++ .../asset-relationship.use-cases.ts | 8 + .../src/api/application/asset.repository.ts | 1 + .../src/api/application/support.repository.ts | 1 + .../src/api/application/support.use-cases.ts | 9 ++ .../http/asset-relationship.routes.ts | 23 +++ .../api/infrastructure/http/support.routes.ts | 23 +++ .../persistence/sequelize-asset.repository.ts | 19 +++ .../sequelize-support.repository.ts | 30 ++++ modules/assets/src/common/locales/en.json | 6 + modules/assets/src/common/locales/es.json | 6 + .../list/ui/asset-relationships.tsx | 13 ++ .../src/web/shared/api/delete-record.api.ts | 13 ++ .../web/shared/ui/delete-record-dialog.tsx | 48 ++++++ .../list/ui/asset-support.tsx | 15 ++ .../application-configuration.repository.ts | 1 + .../application-configuration.use-cases.ts | 8 + .../certificate-configuration.repository.ts | 1 + .../certificate-configuration.use-cases.ts | 8 + .../application/folder-mapping.repository.ts | 1 + .../application/folder-mapping.use-cases.ts | 8 + .../mail-configuration.repository.ts | 1 + .../mail-configuration.use-cases.ts | 8 + .../printer-configuration.repository.ts | 1 + .../printer-configuration.use-cases.ts | 8 + .../http/application-configuration.routes.ts | 23 +++ .../http/certificate-configuration.routes.ts | 23 +++ .../http/folder-mapping.routes.ts | 23 +++ .../http/mail-configuration.routes.ts | 23 +++ .../http/printer-configuration.routes.ts | 23 +++ ...ze-application-configuration.repository.ts | 28 ++++ ...ze-certificate-configuration.repository.ts | 28 ++++ .../sequelize-folder-mapping.repository.ts | 26 ++++ ...sequelize-mail-configuration.repository.ts | 26 ++++ ...uelize-printer-configuration.repository.ts | 26 ++++ modules/companies/src/common/locales/en.json | 8 +- modules/companies/src/common/locales/es.json | 8 +- .../ui/application-configurations-page.tsx | 15 ++ .../ui/certificate-configurations-page.tsx | 15 ++ .../list/ui/folder-mappings-page.tsx | 13 ++ .../list/ui/mail-configurations-page.tsx | 13 ++ .../list/ui/printer-configurations-page.tsx | 13 ++ .../src/web/shared/api/delete-record.api.ts | 13 ++ .../web/shared/ui/delete-record-dialog.tsx | 48 ++++++ .../src/components/confirm-delete-dialog.tsx | 72 +++++++++ 48 files changed, 883 insertions(+), 2 deletions(-) create mode 100644 apps/server/src/record-deletion.integration.ts create mode 100644 docs/eliminacion-de-registros.md create mode 100644 modules/assets/src/web/shared/api/delete-record.api.ts create mode 100644 modules/assets/src/web/shared/ui/delete-record-dialog.tsx create mode 100644 modules/companies/src/web/shared/api/delete-record.api.ts create mode 100644 modules/companies/src/web/shared/ui/delete-record-dialog.tsx create mode 100644 packages/rdx-ui/src/components/confirm-delete-dialog.tsx diff --git a/apps/server/src/identity.test.ts b/apps/server/src/identity.test.ts index ced56f5..2da826d 100644 --- a/apps/server/src/identity.test.ts +++ b/apps/server/src/identity.test.ts @@ -42,6 +42,7 @@ import { verifyInventory } from "./inventory.integration"; import { verifyMail } from "./mail.integration"; import { verifyPrinters } from "./printers.integration"; import { verifyProcesses } from "./processes.integration"; +import { verifyRecordDeletion } from "./record-deletion.integration"; import { verifyRelationships } from "./relationships.integration"; import { verifyRemoteConnections } from "./remote.integration"; import { verifyResourceMigration } from "./resource-migration.integration"; @@ -910,6 +911,7 @@ test("HTTP y MariaDB: configuración única, usuarios, sesiones y protección de migratedServer.close((error) => (error ? reject(error) : resolve())) ); } + await verifyRecordDeletion((path, body) => operator.request(path, body), database); const countAfterAccess = await database.query<{ total: number }>( "SELECT COUNT(*) AS total FROM users", { type: QueryTypes.SELECT } diff --git a/apps/server/src/record-deletion.integration.ts b/apps/server/src/record-deletion.integration.ts new file mode 100644 index 0000000..4ac0644 --- /dev/null +++ b/apps/server/src/record-deletion.integration.ts @@ -0,0 +1,139 @@ +import assert from "node:assert/strict"; +import { randomUUID } from "node:crypto"; + +import type { Sequelize } from "sequelize"; +import { QueryTypes } from "sequelize"; + +type Request = (path: string, body?: unknown) => Promise; + +export async function verifyRecordDeletion(request: Request, database: Sequelize) { + const counts = async () => { + const result: number[] = []; + for (const table of [ + "company_contacts", + "assets", + "resources", + "work_profiles", + "access_items", + "mailbox_uses", + ]) { + const rows = await database.query<{ count: number }>( + `SELECT COUNT(*) AS count FROM ${table}`, + { type: QueryTypes.SELECT } + ); + result.push(Number(rows[0]?.count)); + } + return result; + }; + const folderSubjects = await database.query<{ + profileId: string; + companyId: string; + needId: string; + actorId: string; + }>( + "SELECT p.id AS profileId,p.companyId,i.id AS needId,u.id AS actorId FROM work_profiles p JOIN access_workspaces w ON w.companyId=p.companyId JOIN access_items i ON i.workspaceId=w.id CROSS JOIN users u WHERE JSON_UNQUOTE(JSON_EXTRACT(i.`values`, '$.kind'))='need' LIMIT 1", + { type: QueryTypes.SELECT } + ); + const subject = folderSubjects[0]; + assert.ok(subject, "Debe existir un perfil y una necesidad de recurso para la prueba de carpeta"); + const mappingId = randomUUID(); + const values = JSON.stringify({ + profileId: subject.profileId, + needId: subject.needId, + path: "\\\\SERVER\\Test", + driveLetter: null, + state: "pending", + notes: null, + }); + await database.query( + "INSERT INTO folder_mappings (id,companyId,profileId,needId,`values`,currentKey,driveKey) VALUES (:id,:companyId,:profileId,:needId,:values,:currentKey,NULL)", + { + replacements: { + ...subject, + id: mappingId, + values, + currentKey: `${subject.profileId}:${subject.needId}`, + }, + } + ); + await database.query( + "INSERT INTO folder_mapping_revisions (id,mappingId,position,`values`,actorId,recordedAt) VALUES (:id,:mappingId,0,:values,:actorId,:recordedAt)", + { + replacements: { + id: randomUUID(), + mappingId, + values, + actorId: subject.actorId, + recordedAt: new Date().toISOString(), + }, + } + ); + const before = await counts(); + const specs = [ + [ + "application_configurations", + "application_configuration_revisions", + "configurationId", + "application-configurations", + ], + [ + "certificate_configurations", + "certificate_configuration_revisions", + "configurationId", + "certificate-configurations", + ], + [ + "printer_configurations", + "printer_configuration_revisions", + "configurationId", + "printer-configurations", + ], + [ + "mail_configurations", + "mail_configuration_revisions", + "configurationId", + "mail-configurations", + ], + ["folder_mappings", "folder_mapping_revisions", "mappingId", "folder-mappings"], + ["asset_support", "asset_support_revisions", "connectionId", "asset-support"], + ["asset_relationships", null, null, "assets"], + ] as const; + for (const [table, history, foreignKey, endpoint] of specs) { + const rows = await database.query<{ id: string; companyId: string; assetId?: string }>( + `SELECT * FROM ${table}`, + { type: QueryTypes.SELECT } + ); + assert.ok(rows.length > 0, `No existen datos para probar ${table}`); + // Todos los estados, incluidos los cerrados, admiten corregir un alta equivocada. + for (const row of rows) { + const suffix = + endpoint === "asset-support" + ? `${row.assetId}/${row.id}` + : endpoint === "assets" + ? `relationships/${row.id}` + : row.id; + const path = `/${endpoint}/${row.companyId}/${suffix}/delete`; + assert.equal( + (await request(`/${endpoint}/${randomUUID()}/${suffix}/delete`, {})).status, + 404 + ); + assert.equal((await request(path.replace(row.companyId, "invalid"), {})).status, 400); + assert.equal((await request(path, { unexpected: true })).status, 400); + assert.equal((await request(path, {})).status, 200); + assert.equal((await request(path, {})).status, 404); + const remaining = await database.query(`SELECT id FROM ${table} WHERE id=:id`, { + replacements: { id: row.id }, + type: QueryTypes.SELECT, + }); + assert.equal(remaining.length, 0); + if (history && foreignKey) { + const revisions = await database.query( + `SELECT id FROM ${history} WHERE ${foreignKey}=:id`, + { replacements: { id: row.id }, type: QueryTypes.SELECT } + ); + assert.equal(revisions.length, 0); + } + } + } + assert.deepEqual(await counts(), before, "La eliminación debe conservar las entidades de origen"); +} diff --git a/docs/componentes-de-interfaz.md b/docs/componentes-de-interfaz.md index c646a62..d0b3d07 100644 --- a/docs/componentes-de-interfaz.md +++ b/docs/componentes-de-interfaz.md @@ -319,3 +319,9 @@ En el menú de secciones de una compañía, los elementos utilizan el color norm El cuerpo desplazable `FormBody` reserva 4 px interiores en ambos laterales y al inicio para el anillo exterior de foco (3 px). No debe eliminarse ese espacio: `overflow-y-auto` también recorta horizontalmente y produciría un foco asimétrico en campos pegados al borde. Se conserva el scroll y las acciones fijas. Los listados comparten la preferencia entity-page-size en localStorage: el número de elementos por página elegido en un listado se restaura en todos los demás. Solo se aceptan los tamaños disponibles en el selector; si el almacenamiento no está disponible o contiene otro valor, se usa el tamaño inicial. + +### Confirmación de eliminación de registros + +`ConfirmDeleteDialog` de `@repo/rdx-ui` presenta una confirmación modal con descripción de consecuencias, Cancelar y Eliminar. El consumidor aporta los textos traducidos y una operación asíncrona. Mientras se ejecuta, evita envíos repetidos y el cierre; si falla, mantiene el diálogo abierto y muestra un error. Solo se cierra tras completar la operación. + +Los módulos conectan este componente mediante `DeleteRecordDialog`, separado de los menús de fila para que la confirmación permanezca montada cuando se cierre el menú. La operación invalida la consulta de la entidad y las revisiones de procesos de la compañía. diff --git a/docs/eliminacion-de-registros.md b/docs/eliminacion-de-registros.md new file mode 100644 index 0000000..7a2ec6b --- /dev/null +++ b/docs/eliminacion-de-registros.md @@ -0,0 +1,11 @@ +# Eliminación de registros creados por error + +Se permite eliminar configuraciones de aplicaciones, correo, carpetas, impresoras y certificados de un perfil, métodos de asistencia técnica de un activo y relaciones entre activos. Está disponible para cualquier estado del registro, incluidos los retirados y cerrados. No se elimina el catálogo de tipos de relación ni el catálogo de herramientas de asistencia. + +La acción se ofrece en el menú de fila de las configuraciones y en las fichas de asistencia y relaciones. Siempre requiere confirmación modal e informa de que la eliminación es definitiva e incluye el historial propio. Una configuración representa lo documentado en Metis Desk: borrarla no desinstala programas, elimina certificados del dispositivo, revoca permisos ni retira herramientas reales. + +Cada operación valida los UUID, el cuerpo vacío y la consulta vacía, exige autenticación y la protección CSRF general del servidor, y restringe la escritura a la compañía indicada. Para asistencia se comprueba también el activo. La persistencia bloquea la compañía y el registro, elimina primero las revisiones propias y luego el registro, dentro de una transacción. No modifica activos, recursos, cuentas, personas, perfiles ni permisos. Un identificador inexistente o de otra compañía devuelve 404 sin modificar datos. + +Las tareas de procesos que ya copiaron una referencia mantienen su descripción e historial: la eliminación del registro de origen no elimina el proceso. Al refrescar su revisión actual, ese registro deja de aparecer. + +Las entidades con condiciones de eliminación (perfiles, puestos, activos, recursos y catálogos utilizados) quedan fuera de esta ampliación. Las eliminaciones de asignaciones a departamentos y procesos de personas ya estaban disponibles. diff --git a/modules/assets/src/api/application/asset-relationship.use-cases.ts b/modules/assets/src/api/application/asset-relationship.use-cases.ts index edd8d0f..676ee6c 100644 --- a/modules/assets/src/api/application/asset-relationship.use-cases.ts +++ b/modules/assets/src/api/application/asset-relationship.use-cases.ts @@ -37,6 +37,14 @@ export class AssetRelationshipUseCases { ); }); } + remove(companyId: string, id: string) { + const company = UniqueID.create(companyId); + const identity = UniqueID.create(id); + if (!(company.isSuccess && identity.isSuccess)) { + return Promise.resolve(Result.fail(new AssetCatalogError("INVALID_REQUEST"))); + } + return this.repository.removeRelationship(company.data, identity.data); + } end(companyId: string, id: string, actorId: string) { const company = UniqueID.create(companyId); const identity = UniqueID.create(id); diff --git a/modules/assets/src/api/application/asset.repository.ts b/modules/assets/src/api/application/asset.repository.ts index 7d432b0..c41acf7 100644 --- a/modules/assets/src/api/application/asset.repository.ts +++ b/modules/assets/src/api/application/asset.repository.ts @@ -20,6 +20,7 @@ export type InventoryContext = Readonly<{ actorNames: ReadonlyMap; }>; export interface IAssetRepository { + removeRelationship(company: UniqueID, id: UniqueID): Promise>; saveWorkstation( companyId: UniqueID, action: (context: InventoryContext) => Result diff --git a/modules/assets/src/api/application/support.repository.ts b/modules/assets/src/api/application/support.repository.ts index 7743332..e45bcc6 100644 --- a/modules/assets/src/api/application/support.repository.ts +++ b/modules/assets/src/api/application/support.repository.ts @@ -11,6 +11,7 @@ export type SupportContext = Readonly<{ actorNames: ReadonlyMap; }>; export interface ISupportRepository { + remove(company: UniqueID, asset: UniqueID, id: UniqueID): Promise>; get(company: UniqueID, asset: UniqueID): Promise>; save( company: UniqueID, diff --git a/modules/assets/src/api/application/support.use-cases.ts b/modules/assets/src/api/application/support.use-cases.ts index e5c7431..ceee4eb 100644 --- a/modules/assets/src/api/application/support.use-cases.ts +++ b/modules/assets/src/api/application/support.use-cases.ts @@ -18,6 +18,15 @@ export class SupportUseCases { } return this.repository.get(company.data, asset.data); } + remove(companyId: string, assetId: string, id: string) { + const company = UniqueID.create(companyId); + const asset = UniqueID.create(assetId); + const identity = UniqueID.create(id); + if (!(company.isSuccess && asset.isSuccess && identity.isSuccess)) { + return Promise.resolve(Result.fail(new SupportError("INVALID_REQUEST"))); + } + return this.repository.remove(company.data, asset.data, identity.data); + } tools() { return this.repository.tools(); } diff --git a/modules/assets/src/api/infrastructure/http/asset-relationship.routes.ts b/modules/assets/src/api/infrastructure/http/asset-relationship.routes.ts index b2add85..e4216c3 100644 --- a/modules/assets/src/api/infrastructure/http/asset-relationship.routes.ts +++ b/modules/assets/src/api/infrastructure/http/asset-relationship.routes.ts @@ -77,5 +77,28 @@ export function registerRelationshipRoutes( }) .catch(next); }); + router.post("/:companyId/relationships/:id/delete", (request, response, next) => { + const params = AssetParamsSchema.safeParse(request.params); + if ( + !( + params.success && + z.strictObject({}).safeParse(request.body).success && + z.strictObject({}).safeParse(request.query).success + ) + ) { + fail(response, "INVALID_REQUEST"); + return; + } + useCases + .remove(params.data.companyId, params.data.id) + .then((result) => { + if (!result.isSuccess) { + fail(response, result.error.code); + return; + } + response.json({ deleted: true }); + }) + .catch(next); + }); app.use("/api/assets", authenticate, router); } diff --git a/modules/assets/src/api/infrastructure/http/support.routes.ts b/modules/assets/src/api/infrastructure/http/support.routes.ts index dabbfe9..8fcbb2f 100644 --- a/modules/assets/src/api/infrastructure/http/support.routes.ts +++ b/modules/assets/src/api/infrastructure/http/support.routes.ts @@ -106,5 +106,28 @@ export function registerSupportRoutes( .catch(next); }); } + router.post("/:companyId/:assetId/:id/delete", (request, response, next) => { + const params = Params.extend({ id: z.uuid() }).safeParse(request.params); + if ( + !( + params.success && + Empty.safeParse(request.body).success && + Empty.safeParse(request.query).success + ) + ) { + fail(response, "INVALID_REQUEST"); + return; + } + useCases + .remove(params.data.companyId, params.data.assetId, params.data.id) + .then((result) => { + if (!result.isSuccess) { + fail(response, result.error.code); + return; + } + response.json({ deleted: true }); + }) + .catch(next); + }); app.use("/api/asset-support", authenticate, router); } diff --git a/modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository.ts b/modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository.ts index 72ba1cb..0668f17 100644 --- a/modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository.ts +++ b/modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository.ts @@ -190,6 +190,25 @@ export class SequelizeAssetRepository implements IAssetRepository { await WorkstationAssetModel.upsert(reference, { transaction }); } } + removeRelationship(company: UniqueID, id: UniqueID): Promise> { + return this.database.transaction(async (transaction) => { + await this.database.query("SELECT id FROM companies WHERE id=:companyId FOR UPDATE", { + replacements: { companyId: company.toPrimitive() }, + transaction, + type: QueryTypes.SELECT, + }); + const row = await RelationshipModel.findOne({ + where: { companyId: company.toPrimitive(), id: id.toPrimitive() }, + transaction, + lock: transaction.LOCK.UPDATE, + }); + if (!row) { + return Result.fail(new AssetCatalogError("RELATIONSHIP_NOT_FOUND")); + } + await row.destroy({ transaction }); + return Result.ok(undefined); + }); + } saveRelationship( companyId: UniqueID, action: Parameters[1] diff --git a/modules/assets/src/api/infrastructure/persistence/sequelize-support.repository.ts b/modules/assets/src/api/infrastructure/persistence/sequelize-support.repository.ts index 4ecafad..6fffae9 100644 --- a/modules/assets/src/api/infrastructure/persistence/sequelize-support.repository.ts +++ b/modules/assets/src/api/infrastructure/persistence/sequelize-support.repository.ts @@ -66,6 +66,36 @@ export class SequelizeSupportRepository implements ISupportRepository { actorNames: new Map(users.map((item) => [item.id, item.name])), }); } + remove(company: UniqueID, asset: UniqueID, id: UniqueID): Promise> { + return this.database.transaction(async (transaction) => { + const replacements = { + companyId: company.toPrimitive(), + assetId: asset.toPrimitive(), + id: id.toPrimitive(), + }; + await this.database.query("SELECT id FROM companies WHERE id=:companyId FOR UPDATE", { + replacements, + transaction, + type: QueryTypes.SELECT, + }); + const rows = await this.database.query<{ id: string }>( + "SELECT id FROM asset_support WHERE companyId=:companyId AND assetId=:assetId AND id=:id FOR UPDATE", + { replacements, transaction, type: QueryTypes.SELECT } + ); + if (!rows.length) { + return Result.fail(new SupportError("CONNECTION_NOT_FOUND")); + } + await this.database.query("DELETE FROM asset_support_revisions WHERE connectionId=:id", { + replacements, + transaction, + }); + await this.database.query( + "DELETE FROM asset_support WHERE companyId=:companyId AND assetId=:assetId AND id=:id", + { replacements, transaction } + ); + return Result.ok(undefined); + }); + } async save( company: UniqueID, asset: UniqueID, diff --git a/modules/assets/src/common/locales/en.json b/modules/assets/src/common/locales/en.json index f1c148b..5abf438 100644 --- a/modules/assets/src/common/locales/en.json +++ b/modules/assets/src/common/locales/en.json @@ -308,5 +308,11 @@ "identification": "Identification", "assignment": "Location and assignment", "notes": "Notes" + }, + "deleteRecord": { + "title": "Delete record", + "description": "This record and its history will be deleted. Related assets, resources, accounts and people will be preserved. This action cannot be undone.", + "pending": "Deleting…", + "error": "The record could not be deleted. Please try again." } } diff --git a/modules/assets/src/common/locales/es.json b/modules/assets/src/common/locales/es.json index 55efe09..1d1ed6c 100644 --- a/modules/assets/src/common/locales/es.json +++ b/modules/assets/src/common/locales/es.json @@ -308,5 +308,11 @@ "identification": "Identificación", "assignment": "Ubicación y asignación", "notes": "Observaciones" + }, + "deleteRecord": { + "title": "Eliminar registro", + "description": "Se eliminarán este registro y su historial. Los activos, recursos, cuentas y personas relacionados se conservarán. Esta acción no se puede deshacer.", + "pending": "Eliminando…", + "error": "No se ha podido eliminar el registro. Inténtalo de nuevo." } } diff --git a/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx b/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx index 392636d..ee3d981 100644 --- a/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx +++ b/modules/assets/src/web/asset-relationships/list/ui/asset-relationships.tsx @@ -1,8 +1,10 @@ import { BrowserLink } from "@metis/core/client"; import { Button } from "@repo/shadcn-ui/components/button"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import type { RelationshipDTO } from "../../../../common"; +import { DeleteRecordDialog } from "../../../shared/ui/delete-record-dialog"; import { EndRelationshipDialog } from "../../end"; import { RelationshipEditorDialog } from "../../shared"; import { useAssetRelationshipsController } from "../controllers"; @@ -13,6 +15,7 @@ export function AssetRelationships(props: AssetRelationshipsProps) { const { query, adding, setAdding, setEndingId, ending, active, ended, dateOf } = useAssetRelationshipsController(props); const { t } = useTranslation("assets"); + const [deleteId, setDeleteId] = useState(); const nameOf = (id: string) => query.data?.assets.find((asset) => asset.id === id)?.values.name ?? id; const render = (link: RelationshipDTO) => { @@ -34,6 +37,9 @@ export function AssetRelationships(props: AssetRelationshipsProps) {

{t("relationCreated", { name: link.createdByName, date: dateOf(link.createdAt) })}

+ {link.endedAt ? (

{t("relationEnded", { name: link.endedByName, date: dateOf(link.endedAt) })} @@ -73,6 +79,13 @@ export function AssetRelationships(props: AssetRelationshipsProps) {

    {ended.map(render)}
)} + setDeleteId(undefined)} + path={deleteId ? `/api/assets/${companyId}/relationships/${deleteId}/delete` : undefined} + queryKey={["inventory", companyId]} + /> {adding && ( void; + companyId: string; +}) { + const client = useQueryClient(); + const { t } = useTranslation("assets"); + return ( + { + if (!path) { + return; + } + await deleteRecord(path, csrfToken); + await Promise.all([ + client.invalidateQueries({ queryKey }), + client.invalidateQueries({ queryKey: ["person-processes", companyId] }), + client.invalidateQueries({ queryKey: ["process-review", companyId] }), + ]); + }} + onOpenChange={(open) => { + if (!open) { + onClose(); + } + }} + open={Boolean(path)} + pendingLabel={t("deleteRecord.pending")} + title={t("deleteRecord.title")} + /> + ); +} diff --git a/modules/assets/src/web/support-connections/list/ui/asset-support.tsx b/modules/assets/src/web/support-connections/list/ui/asset-support.tsx index c13af15..85e6417 100644 --- a/modules/assets/src/web/support-connections/list/ui/asset-support.tsx +++ b/modules/assets/src/web/support-connections/list/ui/asset-support.tsx @@ -1,6 +1,8 @@ import { Button } from "@repo/shadcn-ui/components/button"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; +import { DeleteRecordDialog } from "../../../shared/ui/delete-record-dialog"; import { SupportToolsDialog } from "../../../support-tools"; import { SupportSummary } from "../../detail"; import { SupportHistoryDialog } from "../../history"; @@ -26,6 +28,7 @@ export function AssetSupport(props: AssetSupportProps) { tools, } = useAssetSupportController(props); const { t } = useTranslation("assets"); + const [deleteId, setDeleteId] = useState(); return (
@@ -82,6 +85,9 @@ export function AssetSupport(props: AssetSupportProps) {

{t("support.review")}

)}
+ + + + ); +}