Files
homebox/frontend/components/Maintenance/EditModal.vue
Tonya 6cd9e2779f Use Tanstack table for Selectable Table, quick actions (#998)
* feat: implement example of data table

* feat: load item data into table

* chore: begin switching dialogs

* feat: implement old dialog for controlling headers and page size

* feat: get table into relatively usable state

* feat: enhance dropdown actions for multi-selection and CSV download

* feat: enhance table cell and dropdown button styles for better usability

* feat: json download for table

* feat: add expanded row component for item details in data table

* chore: add translation support

* feat: restore table on home page

* fix: oops need ids

* feat: move card view to use tanstack to allow for pagination

* feat: switch the items search to use ItemViewSelectable

* fix: update pagination handling and improve button click logic

* feat: improve selectable table

* feat: add indeterminate to checkbox

* feat: overhaul maintenance dialog to use new system and add maintenance options to table

* feat: add label ids and location id to item patch api

* feat: change location and labels in table view

* feat: add quick actions preference and enable toggle in table settings

* fix: lint

* fix: remove sized 1 pages

* fix: attempt to fix type error

* fix: various issues

* fix: remove

* fix: refactor item fetching logic to use useAsyncData for improved reactivity and improve use confirm

* fix: sort backend issues

* fix: enhance CSV export functionality by escaping fields to prevent formula injection

* fix: put aria sort on th not button

* chore: update api types
2025-09-24 02:37:38 +01:00

146 lines
4.6 KiB
Vue

<template>
<Dialog :dialog-id="DialogID.EditMaintenance">
<DialogContent>
<DialogHeader>
<DialogTitle>
{{ entry.id ? $t("maintenance.modal.edit_title") : $t("maintenance.modal.new_title") }}
</DialogTitle>
</DialogHeader>
<form class="flex flex-col gap-2" @submit.prevent="dispatchFormSubmit">
<FormTextField v-model="entry.name" autofocus :label="$t('maintenance.modal.entry_name')" />
<DatePicker v-model="entry.completedDate" :label="$t('maintenance.modal.completed_date')" />
<DatePicker v-model="entry.scheduledDate" :label="$t('maintenance.modal.scheduled_date')" />
<FormTextArea v-model="entry.description" :label="$t('maintenance.modal.notes')" />
<FormTextField v-model="entry.cost" autofocus :label="$t('maintenance.modal.cost')" />
<DialogFooter>
<Button type="submit">
<MdiPost />
{{ entry.id ? $t("maintenance.modal.edit_action") : $t("maintenance.modal.new_action") }}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</template>
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import { DialogID } from "@/components/ui/dialog-provider/utils";
import { toast } from "@/components/ui/sonner";
import MdiPost from "~icons/mdi/post";
import DatePicker from "~~/components/Form/DatePicker.vue";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { useDialog } from "@/components/ui/dialog-provider";
import FormTextField from "~/components/Form/TextField.vue";
import FormTextArea from "~/components/Form/TextArea.vue";
import Button from "@/components/ui/button/Button.vue";
const { closeDialog, registerOpenDialogCallback } = useDialog();
const { t } = useI18n();
const api = useUserApi();
const entry = reactive({
id: null as string | null,
name: "",
completedDate: null as Date | null,
scheduledDate: null as Date | null,
description: "",
cost: "",
itemIds: null as string[] | null,
});
async function dispatchFormSubmit() {
if (entry.id) {
await editEntry();
return;
}
await createEntry();
}
async function createEntry() {
if (!entry.itemIds) {
return;
}
await Promise.allSettled(
entry.itemIds.map(async itemId => {
const { error } = await api.items.maintenance.create(itemId, {
name: entry.name,
completedDate: entry.completedDate ?? "",
scheduledDate: entry.scheduledDate ?? "",
description: entry.description,
cost: parseFloat(entry.cost) ? entry.cost : "0",
});
if (error) {
toast.error(t("maintenance.toast.failed_to_create"));
return;
}
})
);
closeDialog(DialogID.EditMaintenance, true);
}
async function editEntry() {
if (!entry.id) {
return;
}
const { error } = await api.maintenance.update(entry.id, {
name: entry.name,
completedDate: entry.completedDate ?? "null",
scheduledDate: entry.scheduledDate ?? "null",
description: entry.description,
cost: entry.cost,
});
if (error) {
toast.error(t("maintenance.toast.failed_to_update"));
return;
}
closeDialog(DialogID.EditMaintenance, true);
}
onMounted(() => {
const cleanup = registerOpenDialogCallback(DialogID.EditMaintenance, params => {
switch (params.type) {
case "create":
entry.id = null;
entry.name = "";
entry.completedDate = null;
entry.scheduledDate = null;
entry.description = "";
entry.cost = "";
entry.itemIds = typeof params.itemId === "string" ? [params.itemId] : params.itemId;
break;
case "update":
entry.id = params.maintenanceEntry.id;
entry.name = params.maintenanceEntry.name;
entry.completedDate = new Date(params.maintenanceEntry.completedDate);
entry.scheduledDate = new Date(params.maintenanceEntry.scheduledDate);
entry.description = params.maintenanceEntry.description;
entry.cost = params.maintenanceEntry.cost;
entry.itemIds = null;
break;
case "duplicate":
entry.id = null;
entry.name = params.maintenanceEntry.name;
entry.completedDate = null;
entry.scheduledDate = null;
entry.description = params.maintenanceEntry.description;
entry.cost = params.maintenanceEntry.cost;
entry.itemIds = [params.itemId];
break;
}
});
onUnmounted(cleanup);
});
</script>