chore: Nuxt 4 upgrade (#7426)

This commit is contained in:
Kuchenpirat
2026-04-08 17:25:41 +02:00
committed by GitHub
parent 70a251a331
commit d3e41582ae
561 changed files with 1840 additions and 2750 deletions

View File

@@ -0,0 +1,230 @@
<template>
<!-- Create Dialog -->
<BaseDialog
v-model="createDialog"
:title="createTitle || $t('general.create')"
:icon="icon"
color="primary"
max-width="600px"
width="100%"
:submit-disabled="!createFormValid"
can-confirm
@confirm="emit('create-one', createForm.data)"
>
<div class="mx-2 mt-2">
<slot name="create-dialog-top" />
<AutoForm
v-model="createForm.data"
v-model:is-valid="createFormValid"
:items="createForm.items"
class="py-2"
/>
</div>
</BaseDialog>
<!-- Edit Dialog -->
<BaseDialog
v-model="editDialog"
:title="editTitle || $t('general.edit')"
:icon="icon"
color="primary"
max-width="600px"
width="100%"
:submit-disabled="!editFormValid"
can-confirm
@confirm="emit('edit-one', editForm.data)"
>
<div class="mx-2 mt-2">
<slot name="edit-dialog-top" />
<AutoForm
v-model="editForm.data"
v-model:is-valid="editFormValid"
:items="editForm.items"
class="py-2"
/>
</div>
<template #custom-card-action>
<slot name="edit-dialog-custom-action" />
</template>
</BaseDialog>
<!-- Delete Dialog -->
<BaseDialog
v-model="deleteDialog"
:title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error"
can-confirm
@confirm="$emit('deleteOne', deleteTarget.id)"
>
<v-card-text>
{{ $t("general.confirm-delete-generic") }}
<p v-if="deleteTarget" class="mt-4 ml-4">
{{ deleteTarget.name || deleteTarget.title || deleteTarget.id }}
</p>
</v-card-text>
</BaseDialog>
<!-- Bulk Delete Dialog -->
<BaseDialog
v-model="bulkDeleteDialog"
width="650px"
:title="$t('general.confirm')"
:icon="$globals.icons.alertCircle"
color="error"
can-confirm
@confirm="$emit('bulk-action', 'delete-selected', bulkDeleteTarget)"
>
<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 || item.title || item.id }}</v-list-item-title>
</v-list-item>
</template>
</v-virtual-scroll>
</v-card>
</v-card-text>
</BaseDialog>
<BaseCardSectionTitle
:icon="icon"
section
:title="title"
/>
<CrudTable
:headers="tableHeaders"
:table-config="tableConfig"
:data="data || []"
:bulk-actions="bulkActions"
:initial-sort="initialSort"
@edit-one="editEventHandler"
@delete-one="deleteEventHandler"
@bulk-action="handleBulkAction"
>
<template
v-for="slotName in itemSlotNames"
#[slotName]="slotProps"
>
<slot
:name="slotName"
v-bind="slotProps"
/>
</template>
<template #button-row>
<BaseButton
create
@click="createDialog = true"
>
{{ $t("general.create") }}
</BaseButton>
<slot name="table-button-row" />
</template>
<template #button-bottom>
<slot name="table-button-bottom" />
</template>
</CrudTable>
</template>
<script setup lang="ts">
import type { TableHeaders, TableConfig, BulkAction } from "~/components/global/CrudTable.vue";
import type { AutoFormItems } from "~/types/auto-forms";
const slots = useSlots();
const emit = defineEmits<{
(e: "deleteOne", id: string): void;
(e: "deleteMany", ids: string[]): void;
(e: "create-one" | "edit-one", data: any): void;
(e: "bulk-action", event: string, items: any[]): void;
}>();
const tableHeaders = defineModel<TableHeaders[]>("tableHeaders", { required: true });
const createForm = defineModel<{ items: AutoFormItems; data: Record<string, any> }>("createForm", { required: true });
const createDialog = defineModel("createDialog", { type: Boolean, default: false });
const editForm = defineModel<{ items: AutoFormItems; data: Record<string, any> }>("editForm", { required: true });
const editDialog = defineModel("editDialog", { type: Boolean, default: false });
defineProps({
icon: {
type: String,
required: true,
},
title: {
type: String,
required: true,
},
createTitle: {
type: String,
},
editTitle: {
type: String,
},
tableConfig: {
type: Object as PropType<TableConfig>,
default: () => ({
hideColumns: false,
canExport: true,
}),
},
data: {
type: Array as PropType<Array<any>>,
required: true,
},
bulkActions: {
type: Array as PropType<BulkAction[]>,
required: true,
},
initialSort: {
type: String,
default: "name",
},
});
// ============================================================
// Bulk Action Handler
function handleBulkAction(event: string, items: any[]) {
if (event === "delete-selected") {
bulkDeleteEventHandler(items);
return;
}
emit("bulk-action", event, items);
}
// ============================================================
// Create & Edit
const createFormValid = ref(false);
const editFormValid = ref(false);
const itemSlotNames = computed(() => Object.keys(slots).filter(slotName => slotName.startsWith("item.")));
const editEventHandler = (item: any) => {
editForm.value.data = { ...item };
editDialog.value = true;
};
// ============================================================
// Delete Logic
const deleteTarget = ref<any>(null);
const deleteDialog = ref(false);
function deleteEventHandler(item: any) {
deleteTarget.value = item;
deleteDialog.value = true;
}
// ============================================================
// Bulk Delete Logic
const bulkDeleteTarget = ref<Array<any>>([]);
const bulkDeleteDialog = ref(false);
function bulkDeleteEventHandler(items: Array<any>) {
bulkDeleteTarget.value = items;
bulkDeleteDialog.value = true;
console.log("Bulk Delete Event Handler", items);
}
</script>

View File

@@ -0,0 +1,52 @@
<template>
<v-data-table
item-key="id"
:headers="headers"
:items="exports"
:items-per-page="15"
class="elevation-0"
@click:row="downloadData"
>
<template #[`item.expires`]="{ item }">
{{ getTimeToExpire(item.expires) }}
</template>
<template #[`item.actions`]="{ item }">
<BaseButton
download
size="small"
:download-url="`/api/recipes/bulk-actions/export/${item.id}/download`"
/>
</template>
</v-data-table>
</template>
<script setup lang="ts">
import { parseISO, formatDistanceToNow } from "date-fns";
import type { GroupDataExport } from "~/lib/api/types/group";
defineProps<{
exports: GroupDataExport[];
}>();
const i18n = useI18n();
const headers = [
{ title: i18n.t("export.export"), value: "name" },
{ title: i18n.t("export.file-name"), value: "filename" },
{ title: i18n.t("export.size"), value: "size" },
{ title: i18n.t("export.link-expires"), value: "expires" },
{ title: "", value: "actions" },
];
function getTimeToExpire(timeString: string) {
const expiresAt = parseISO(timeString);
return formatDistanceToNow(expiresAt, {
addSuffix: false,
});
}
function downloadData(_: any) {
console.log("Downloading data...");
}
</script>

View File

@@ -0,0 +1,18 @@
<template>
<div v-if="preferences">
<BaseCardSectionTitle :title="$t('group.general-preferences')" />
<v-checkbox
v-model="preferences.privateGroup"
class="mt-n4"
:label="$t('group.private-group')"
/>
</div>
</template>
<script setup lang="ts">
import type { ReadGroupPreferences } from "~/lib/api/types/user";
const preferences = defineModel<ReadGroupPreferences>({ required: true });
</script>
<style lang="scss" scoped></style>