chore: refactor data management pages (#7107)

This commit is contained in:
Kuchenpirat
2026-02-24 18:23:33 +01:00
committed by GitHub
parent 03f849f20f
commit 282eedfe2b
19 changed files with 1457 additions and 2586 deletions

View File

@@ -1,299 +1,133 @@
<template>
<div>
<!-- Create Dialog -->
<BaseDialog
v-model="state.createDialog"
:title="$t('data-pages.tools.new-tool')"
:icon="$globals.icons.potSteam"
can-submit
@submit="createTool"
>
<v-card-text>
<v-form ref="domNewToolForm">
<v-text-field
v-model="createTarget.name"
autofocus
:label="$t('general.name')"
:rules="[validators.required]"
/>
<v-checkbox
v-model="createTarget.onHand"
:label="$t('tool.on-hand')"
/>
</v-form>
</v-card-text>
</BaseDialog>
<!-- Edit Dialog -->
<BaseDialog
v-model="state.editDialog"
:icon="$globals.icons.potSteam"
:title="$t('data-pages.tools.edit-tool')"
:submit-text="$t('general.save')"
can-submit
@submit="editSaveTool"
>
<v-card-text v-if="editTarget">
<div class="mt-4">
<v-text-field
v-model="editTarget.name"
:label="$t('general.name')"
/>
<v-checkbox
v-model="editTarget.onHand"
:label="$t('tool.on-hand')"
hide-details
/>
</div>
</v-card-text>
</BaseDialog>
<!-- Delete Dialog -->
<BaseDialog
v-model="state.deleteDialog"
:title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error"
can-confirm
@confirm="deleteTool"
>
<v-card-text>
{{ $t("general.confirm-delete-generic") }}
<p
v-if="deleteTarget"
class="mt-4 ml-4"
>
{{ deleteTarget.name }}
</p>
</v-card-text>
</BaseDialog>
<!-- Bulk Delete Dialog -->
<BaseDialog
v-model="state.bulkDeleteDialog"
width="650px"
:title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error"
can-confirm
@confirm="deleteSelected"
>
<v-card-text>
<p class="h4">
{{ $t('general.confirm-delete-generic-items') }}
</p>
<v-card variant="outlined">
<v-virtual-scroll
height="400"
item-height="25"
:items="bulkDeleteTarget"
>
<template #default="{ item }">
<v-list-item class="pb-2">
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item>
</template>
</v-virtual-scroll>
</v-card>
</v-card-text>
</BaseDialog>
<!-- Data Table -->
<BaseCardSectionTitle
:icon="$globals.icons.potSteam"
section
<GroupDataPage
:icon="$globals.icons.tools"
:title="$t('data-pages.tools.tool-data')"
/>
<CrudTable
v-model:headers="tableHeaders"
:table-headers="tableHeaders"
:table-config="tableConfig"
:data="tools || []"
:bulk-actions="[{ icon: $globals.icons.delete, text: $t('general.delete'), event: 'delete-selected' }]"
initial-sort="name"
@delete-one="deleteEventHandler"
@edit-one="editEventHandler"
@delete-selected="bulkDeleteEventHandler"
:create-form="createForm"
:edit-form="editForm"
@create-one="handleCreate"
@edit-one="handleEdit"
@delete-one="toolStore.actions.deleteOne"
@bulk-action="handleBulkAction"
>
<template #button-row>
<BaseButton
create
@click="state.createDialog = true"
>
{{ $t("general.create") }}
</BaseButton>
</template>
<template #[`item.onHand`]="{ item }">
<v-icon :color="item.onHand ? 'success' : undefined">
{{ item.onHand ? $globals.icons.check : $globals.icons.close }}
</v-icon>
</template>
</CrudTable>
</GroupDataPage>
</div>
</template>
<script lang="ts">
<script setup lang="ts">
import { validators } from "~/composables/use-validators";
import { useToolStore, useToolData } from "~/composables/store";
import type { RecipeTool } from "~/lib/api/types/recipe";
import { fieldTypes } from "~/composables/forms";
import type { AutoFormItems } from "~/types/auto-forms";
import { useToolStore } from "~/composables/store";
import type { RecipeTool, RecipeToolCreate } from "~/lib/api/types/recipe";
import type { TableHeaders, TableConfig } from "~/components/global/CrudTable.vue";
interface RecipeToolWithOnHand extends RecipeTool {
onHand: boolean;
}
export default defineNuxtComponent({
setup() {
const i18n = useI18n();
const auth = useMealieAuth();
const tableConfig = {
hideColumns: true,
canExport: true,
};
const tableHeaders = [
{
text: i18n.t("general.id"),
value: "id",
show: false,
},
{
text: i18n.t("general.name"),
value: "name",
show: true,
sortable: true,
},
{
text: i18n.t("tool.on-hand"),
value: "onHand",
show: true,
sortable: true,
},
];
const state = reactive({
createDialog: false,
editDialog: false,
deleteDialog: false,
bulkDeleteDialog: false,
});
const userHousehold = computed(() => auth.user.value?.householdSlug || "");
const toolData = useToolData();
const toolStore = useToolStore();
const tools = computed(() => toolStore.store.value.map((tools) => {
const onHand = tools.householdsWithTool?.includes(userHousehold.value) || false;
return { ...tools, onHand } as RecipeToolWithOnHand;
}));
// ============================================================
// Create Tool
async function createTool() {
if (toolData.data.onHand) {
toolData.data.householdsWithTool = [userHousehold.value];
}
else {
toolData.data.householdsWithTool = [];
}
await toolStore.actions.createOne({
name: toolData.data.name, householdsWithTool: toolData.data.householdsWithTool,
id: "",
slug: "",
});
toolData.reset();
state.createDialog = false;
}
// ============================================================
// Edit Tool
const editTarget = ref<RecipeToolWithOnHand | null>(null);
function editEventHandler(item: RecipeToolWithOnHand) {
state.editDialog = true;
editTarget.value = item;
}
async function editSaveTool() {
if (!editTarget.value) {
return;
}
if (editTarget.value.onHand && !editTarget.value.householdsWithTool?.includes(userHousehold.value)) {
if (!editTarget.value.householdsWithTool) {
editTarget.value.householdsWithTool = [userHousehold.value];
}
else {
editTarget.value.householdsWithTool.push(userHousehold.value);
}
}
else if (!editTarget.value.onHand && editTarget.value.householdsWithTool?.includes(userHousehold.value)) {
editTarget.value.householdsWithTool = editTarget.value.householdsWithTool.filter(
household => household !== userHousehold.value,
);
}
await toolStore.actions.updateOne(editTarget.value);
state.editDialog = false;
}
// ============================================================
// Delete Tool
const deleteTarget = ref<RecipeToolWithOnHand | null>(null);
function deleteEventHandler(item: RecipeToolWithOnHand) {
state.deleteDialog = true;
deleteTarget.value = item;
}
async function deleteTool() {
if (!deleteTarget.value || deleteTarget.value.id === undefined) {
return;
}
await toolStore.actions.deleteOne(deleteTarget.value.id);
state.deleteDialog = false;
}
// ============================================================
// Bulk Delete Tool
const bulkDeleteTarget = ref<RecipeToolWithOnHand[]>([]);
function bulkDeleteEventHandler(selection: RecipeToolWithOnHand[]) {
bulkDeleteTarget.value = selection;
state.bulkDeleteDialog = true;
}
async function deleteSelected() {
const ids = bulkDeleteTarget.value.map(item => item.id);
await toolStore.actions.deleteMany(ids);
bulkDeleteTarget.value = [];
}
return {
state,
tableConfig,
tableHeaders,
tools,
validators,
// create
createTarget: toolData.data,
createTool,
// edit
editTarget,
editEventHandler,
editSaveTool,
// delete
deleteTarget,
deleteEventHandler,
deleteTool,
// bulk delete
bulkDeleteTarget,
bulkDeleteEventHandler,
deleteSelected,
};
const i18n = useI18n();
const tableConfig: TableConfig = {
hideColumns: true,
canExport: true,
};
const tableHeaders: TableHeaders[] = [
{
text: i18n.t("general.id"),
value: "id",
show: false,
},
{
text: i18n.t("general.name"),
value: "name",
show: true,
sortable: true,
},
{
text: i18n.t("tool.on-hand"),
value: "onHand",
show: true,
sortable: true,
},
];
const auth = useMealieAuth();
const userHousehold = computed(() => auth.user.value?.householdSlug || "");
const toolStore = useToolStore();
const tools = computed(() => toolStore.store.value.map((tools) => {
const onHand = tools.householdsWithTool?.includes(userHousehold.value) || false;
return { ...tools, onHand } as RecipeToolWithOnHand;
}));
// ============================================================
// Form items (shared)
const formItems = [
{
label: i18n.t("general.name"),
varName: "name",
type: fieldTypes.TEXT,
rules: [validators.required],
},
{
label: i18n.t("tool.on-hand"),
varName: "onHand",
type: fieldTypes.BOOLEAN,
},
] as AutoFormItems;
// ============================================================
// Create
const createForm = reactive({
items: formItems,
data: { name: "", onHand: false } as RecipeToolCreate,
});
async function handleCreate(createFormData: RecipeToolCreate) {
// @ts-expect-error createOne eroniusly expects id and slug which are not preset at time of creation
await toolStore.actions.createOne({ name: createFormData.name, householdsWithTool: createFormData.onHand ? [userHousehold.value] : [] } as RecipeToolCreate);
createForm.data = { name: "", onHand: false } as RecipeToolCreate;
}
// ============================================================
// Edit
const editForm = reactive({
items: formItems,
data: {} as RecipeToolWithOnHand,
});
async function handleEdit(editFormData: RecipeToolWithOnHand) {
// if list of households is undefined default to empty array
if (!editFormData.householdsWithTool) {
editFormData.householdsWithTool = [];
}
if (editFormData.onHand && !editFormData.householdsWithTool.includes(userHousehold.value)) {
editFormData.householdsWithTool.push(userHousehold.value);
}
else if (!editFormData.onHand && editFormData.householdsWithTool.includes(userHousehold.value)) {
const idx = editFormData.householdsWithTool.indexOf(userHousehold.value);
if (idx !== -1) editFormData.householdsWithTool.splice(idx, 1);
}
await toolStore.actions.updateOne({ ...editFormData, id: editFormData.id } as RecipeTool);
editForm.data = {} as RecipeToolWithOnHand;
}
// ============================================================
// Bulk Actions
async function handleBulkAction(event: string, items: RecipeToolWithOnHand[]) {
if (event === "delete-selected") {
const ids = items.filter(item => item.id != null).map(item => item.id!);
await toolStore.actions.deleteMany(ids);
}
}
</script>