Skip to content

Commit

Permalink
Implemented Recycle Bin feature (#392)
Browse files Browse the repository at this point in the history
  • Loading branch information
mlhaufe authored Oct 10, 2024
1 parent b1f2d6f commit a2b9c75
Show file tree
Hide file tree
Showing 23 changed files with 271 additions and 145 deletions.
71 changes: 60 additions & 11 deletions components/XDataTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ export type FormFieldType = 'text' | 'textarea' | { type: 'number', min: number,
const props = defineProps<{
datasource: RowType[] | null,
entityName: string,
showRecycleBin: boolean,
viewModel: { [K in keyof RowType]?: ViewFieldType },
createModel: { [K in keyof RowType]?: FormFieldType },
editModel: { [K in keyof RowType]?: FormFieldType },
loading: boolean,
showHistory: boolean,
organizationSlug: string,
onCreate: (data: RowType) => Promise<void>,
onDelete: (id: string) => Promise<void>,
Expand All @@ -43,7 +44,12 @@ const dataTable = ref<DataTable>(),
historyDialogVisible = ref(false),
historyItems = ref<{ date: string, entity: Record<string, any> }[]>([]),
selectedHistoryItem = ref<{ date: string, entity: Record<string, any> }>({ date: '', entity: {} }),
historyDialogLoading = ref(false)
historyDialogLoading = ref(false),
recycleDialog = ref<Dialog>(),
recycleDialogVisible = ref(false),
recycleDialogLoading = ref(false),
recycleItems = ref<{ date: string, entity: Record<string, any> }[]>([]),
recycleBin = ref<DataTable>()
const filters = ref<Record<string, { value: any, matchMode: string }>>({
'global': { value: null, matchMode: FilterMatchMode.CONTAINS }
Expand All @@ -57,23 +63,41 @@ const openEditDialog = (item: RowType) => {
editDialogItem.value = { ...item }
}
const openRecycleDialog = async () => {
recycleDialogLoading.value = true
recycleDialogVisible.value = true
const recycleBinItems = (await $fetch<AuditLog[]>(`/api/audit-log/deleted`, {
method: 'GET',
query: {
entityName: props.entityName,
organizationSlug: props.organizationSlug
}
})).map(log => ({
date: new Date(log.createdAt).toLocaleString(),
entity: JSON.parse(log.entity)
}))
recycleItems.value = recycleBinItems
recycleDialogLoading.value = false
}
const openHistoryDialog = async (item: RowType) => {
historyDialogLoading.value = true
historyDialogVisible.value = true
const auditLog = await $fetch<AuditLog[]>(`/api/audit-log`, {
const auditLog = (await $fetch<AuditLog[]>(`/api/audit-log`, {
method: 'GET',
query: {
entityId: item.id,
organizationSlug: props.organizationSlug
}
})
})).map(log => ({
date: new Date(log.createdAt).toLocaleString(),
entity: JSON.parse(log.entity)
}))
historyItems.value = [
{ date: 'Current', entity: item },
...auditLog.map(log => ({
date: new Date(log.createdAt).toLocaleString(),
entity: JSON.parse(log.entity)
}))
...auditLog
]
selectedHistoryItem.value = historyItems.value[0]
Expand Down Expand Up @@ -155,7 +179,9 @@ const onEditDialogCancel = () => {
<ConfirmDialog></ConfirmDialog>
<Toolbar>
<template #start>
<Button label="'Create" severity="info" @click="openCreateDialog" :disabled="createDisabled" />
<Button label="Create" severity="info" class="mr-2" @click="openCreateDialog"
:disabled="createDisabled" />
<Button v-if="props.showRecycleBin" label="Recycle Bin" severity="help" @click="openRecycleDialog" />
</template>
<template #end>
<InputText v-model="filters['global'].value" placeholder="Keyword Search" />
Expand Down Expand Up @@ -188,8 +214,7 @@ const onEditDialogCancel = () => {
<Column frozen align-frozen="right">
<template #body="{ data }">
<Button icon="pi pi-pencil" text rounded @click="openEditDialog(data)" title="Edit" />
<Button v-if="props.showHistory" icon="pi pi-clock" text rounded @click="openHistoryDialog(data)"
title="History" />
<Button icon="pi pi-clock" text rounded @click="openHistoryDialog(data)" title="History" />
<Button icon="pi pi-trash" text rounded severity="danger" @click="onDelete(data)" title="Delete" />
</template>
</Column>
Expand Down Expand Up @@ -301,6 +326,30 @@ const onEditDialogCancel = () => {
</template>
</Dialog>
<Dialog ref="recycleDialog" v-model:visible="recycleDialogVisible" :modal="true" class="p-fluid">
<template #header>Recycle Bin</template>
<section>
<DataTable ref="recycleBin" :value="recycleItems" dataKey="date" :loading="recycleDialogLoading">
<Column field="date" header="Date" sortable />
<Column v-for="key of Object.keys(recycleItems?.[0]?.entity ?? {})" :key="key" :field="key"
:header="camelCaseToTitle(key)">
<template #body="{ data, field }">
<span v-if="data.entity[field] instanceof Date">{{ data.entity[field].toLocaleString() }}</span>
<span v-else-if="typeof data.entity[field] === 'object'">
{{ data.entity[field]?.name ?? JSON.stringify(data.entity[field]) }}
</span>
<span v-else>{{ data.entity[field] }}</span>
</template>
</Column>
<template #empty>The Recycle Bin is empty</template>
<template #loading>Loading data...</template>
</DataTable>
</section>
<template #footer>
<Button label="Close" icon="pi pi-times" class="p-button-text" @click="recycleDialogVisible = false" />
</template>
</Dialog>
<Dialog ref="historyDialog" v-model:visible="historyDialogVisible" :modal="true" class="p-fluid">
<template #header>
<div class="flex flex-row gap-5 w-full">
Expand Down
180 changes: 84 additions & 96 deletions migrations/.snapshot-cathedral.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,90 @@
"foreignKeys": {},
"nativeEnums": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"entity_id": {
"name": "entity_id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"entity_name": {
"name": "entity_name",
"type": "varchar(255)",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 255,
"mappedType": "string"
},
"type": {
"name": "type",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": [
"create",
"update",
"delete",
"update_early",
"delete_early"
],
"mappedType": "enum"
},
"entity": {
"name": "entity",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"mappedType": "datetime"
}
},
"name": "audit_log",
"schema": "public",
"indexes": [
{
"keyName": "audit_log_pkey",
"columnNames": [
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {},
"nativeEnums": {}
},
{
"columns": {
"id": {
Expand Down Expand Up @@ -2872,102 +2956,6 @@
},
"nativeEnums": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"solution_id": {
"name": "solution_id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"entity_id": {
"name": "entity_id",
"type": "uuid",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "uuid"
},
"type": {
"name": "type",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": [
"create",
"update",
"delete",
"update_early",
"delete_early"
],
"mappedType": "enum"
},
"entity": {
"name": "entity",
"type": "jsonb",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"mappedType": "json"
},
"created_at": {
"name": "created_at",
"type": "timestamptz",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"mappedType": "datetime"
}
},
"name": "audit_log",
"schema": "public",
"indexes": [
{
"keyName": "audit_log_pkey",
"columnNames": [
"id"
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"audit_log_solution_id_foreign": {
"constraintName": "audit_log_solution_id_foreign",
"columnNames": [
"solution_id"
],
"localTableName": "public.audit_log",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.solution",
"updateRule": "cascade"
}
},
"nativeEnums": {}
},
{
"columns": {
"id": {
Expand Down
20 changes: 20 additions & 0 deletions migrations/Migration20241010185435.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Migration } from '@mikro-orm/migrations';

export class Migration20241010185435 extends Migration {

override async up(): Promise<void> {
this.addSql(`alter table "audit_log" drop constraint "audit_log_solution_id_foreign";`);

this.addSql(`alter table "audit_log" drop column "solution_id";`);

this.addSql(`alter table "audit_log" add column "entity_name" varchar(255) not null;`);
}

override async down(): Promise<void> {
this.addSql(`alter table "audit_log" drop column "entity_name";`);

this.addSql(`alter table "audit_log" add column "solution_id" uuid not null;`);
this.addSql(`alter table "audit_log" add constraint "audit_log_solution_id_foreign" foreign key ("solution_id") references "solution" ("id") on update cascade;`);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const onUpdate = async (data: Assumption) => {
</p>
<XDataTable :viewModel="{ name: 'text', statement: 'text' }" :createModel="{ name: 'text', statement: 'text' }"
:editModel="{ id: 'hidden', name: 'text', statement: 'text' }" :datasource="assumptions" :on-create="onCreate"
:on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :show-history="true"
:organizationSlug="organizationslug">
:on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :organizationSlug="organizationslug"
entityName="Assumption" :showRecycleBin="true">
</XDataTable>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ const onUpdate = async (data: EnvironmentComponent) => {
<XDataTable :viewModel="{ name: 'text', statement: 'text' }" :createModel="{ name: 'text', statement: 'text' }"
:editModel="{ id: 'hidden', name: 'text', statement: 'text' }" :datasource="environmentComponents"
:on-create="onCreate" :on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'"
:show-history="true" :organizationSlug="organizationslug">
:organizationSlug="organizationslug" entityName="EnvironmentComponent" :showRecycleBin="true">
</XDataTable>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const onUpdate = async (data: Constraint) => {
:createModel="{ name: 'text', category: Object.values(ConstraintCategory), statement: 'text' }"
:editModel="{ id: 'hidden', name: 'text', category: Object.values(ConstraintCategory), statement: 'text' }"
:datasource="constraints" :on-create="onCreate" :on-delete="onDelete" :on-update="onUpdate"
:loading="status === 'pending'" :show-history="true" :organizationSlug="organizationslug">
:loading="status === 'pending'" :organizationSlug="organizationslug" entityName="Constraint"
:showRecycleBin="true">
</XDataTable>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const onDelete = async (id: string) => {
</p>
<XDataTable :viewModel="{ name: 'text', statement: 'text' }" :createModel="{ name: 'text', statement: 'text' }"
:editModel="{ id: 'hidden', name: 'text', statement: 'text' }" :datasource="effects" :on-create="onCreate"
:on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :show-history="true"
:organizationSlug="organizationslug">
:on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :organizationSlug="organizationslug"
entityName="Effect" :showRecycleBin="true">
</XDataTable>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const onDelete = async (id: string) => {
</p>
<XDataTable :viewModel="{ name: 'text', statement: 'text' }" :createModel="{ name: 'text', statement: 'text' }"
:editModel="{ id: 'hidden', name: 'text', statement: 'text' }" :datasource="glossaryTerms" :on-create="onCreate"
:on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :show-history="true"
:organizationSlug="organizationslug">
:on-delete="onDelete" :on-update="onUpdate" :loading="status === 'pending'" :organizationSlug="organizationslug"
entityName="GlossaryTerm" :showRecycleBin="true">
</XDataTable>
</template>
Loading

0 comments on commit a2b9c75

Please sign in to comment.