211 lines
6.9 KiB
Vue
211 lines
6.9 KiB
Vue
<script setup>
|
|
import { FwbButton, FwbTable, FwbTableBody, FwbTableCell, FwbTableHead, FwbTableHeadCell, FwbTableRow } from 'flowbite-vue';
|
|
import DialogModal from './DialogModal.vue';
|
|
import { useForm } from '@inertiajs/vue3';
|
|
import { ref } from 'vue';
|
|
import TextInput from './TextInput.vue';
|
|
import InputLabel from './InputLabel.vue';
|
|
import ActionMessage from './ActionMessage.vue';
|
|
import PrimaryButton from './PrimaryButton.vue';
|
|
import Modal from './Modal.vue';
|
|
import SecondaryButton from './SecondaryButton.vue';
|
|
|
|
|
|
const props = defineProps({
|
|
title: String,
|
|
description: String,
|
|
header: Array,
|
|
body: Array,
|
|
editor: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
options: {
|
|
type: Object,
|
|
default: {}
|
|
},
|
|
// Deprecated: fixed height. Prefer bodyMaxHeight (e.g., 'max-h-96').
|
|
bodyHeight: {
|
|
type: String,
|
|
default: 'h-96'
|
|
},
|
|
// Preferred: control scrollable body max-height (Tailwind class), e.g., 'max-h-96', 'max-h-[600px]'
|
|
bodyMaxHeight: {
|
|
type: String,
|
|
default: 'max-h-96'
|
|
}
|
|
});
|
|
|
|
const drawerUpdateForm = ref(false);
|
|
const modalRemove = ref(false);
|
|
const formUpdate = (props.editor) ? useForm(props.options.editor_data.form.values) : false;
|
|
const formRemove = (props.editor) ? useForm({type: 'soft', key: null}) : false;
|
|
|
|
const openEditor = (ref, data) => {
|
|
formUpdate[ref.key] = ref.val;
|
|
for(const [key, value] of Object.entries(data)) {
|
|
formUpdate[key] = value;
|
|
}
|
|
drawerUpdateForm.value = true;
|
|
}
|
|
|
|
const closeEditor = () => {
|
|
drawerUpdateForm.value = false;
|
|
}
|
|
|
|
const setUpdateRoute = (name, params = false, index) => {
|
|
if(!params){
|
|
return route(name, index)
|
|
}
|
|
|
|
return route(name, [params, index]);
|
|
}
|
|
|
|
const update = () => {
|
|
|
|
const putRoute = setUpdateRoute(
|
|
props.options.editor_data.form.route.name,
|
|
props.options.editor_data.form.route.params,
|
|
formUpdate[props.options.editor_data.form.key]
|
|
)
|
|
|
|
formUpdate.put(putRoute, {
|
|
onSuccess: () => {
|
|
closeEditor();
|
|
formUpdate.reset();
|
|
},
|
|
preserveScroll: true
|
|
});
|
|
}
|
|
|
|
const modalRemoveTitle = ref('');
|
|
|
|
const closeModal = () => {
|
|
modalRemove.value = false;
|
|
}
|
|
|
|
const showModal = (key, title) => {
|
|
modalRemoveTitle.value = title;
|
|
formRemove.key = key;
|
|
modalRemove.value = true;
|
|
}
|
|
|
|
const remove = () => {
|
|
const removeRoute = setUpdateRoute(
|
|
props.options.editor_data.form.route_remove.name,
|
|
props.options.editor_data.form.route_remove.params,
|
|
formRemove.key
|
|
)
|
|
|
|
formRemove.delete(removeRoute, {
|
|
onSuccess: () => {
|
|
closeModal();
|
|
formRemove.reset();
|
|
},
|
|
preserveScroll: true
|
|
});
|
|
}
|
|
|
|
</script>
|
|
<template>
|
|
<div>
|
|
<!-- Header -->
|
|
<div v-if="title || description" class="mb-4">
|
|
<h2 v-if="title" class="text-lg font-semibold text-gray-900">{{ title }}</h2>
|
|
<p v-if="description" class="mt-1 text-sm text-gray-600">{{ description }}</p>
|
|
</div>
|
|
|
|
<div :class="['relative rounded-lg border border-gray-200 bg-white shadow-sm overflow-x-auto overflow-y-auto', bodyMaxHeight]">
|
|
<FwbTable hoverable striped class="text-sm">
|
|
<FwbTableHead class="sticky top-0 z-10 bg-gray-50/90 backdrop-blur supports-[backdrop-filter]:bg-gray-50/80 border-b border-gray-200 shadow-sm">
|
|
<FwbTableHeadCell v-for="(h, hIndex) in header" :key="hIndex" class="uppercase text-xs font-semibold tracking-wide text-gray-700 py-3 first:pl-6 last:pr-6">{{ h.data }}</FwbTableHeadCell>
|
|
<FwbTableHeadCell v-if="editor" class="w-px text-gray-700 py-3"></FwbTableHeadCell>
|
|
<FwbTableHeadCell v-else class="w-px text-gray-700 py-3" />
|
|
</FwbTableHead>
|
|
<FwbTableBody>
|
|
<FwbTableRow v-for="(row, key, parent_index) in body" :key="key" :class="row.options.class">
|
|
<FwbTableCell v-for="(col, colIndex) in row.cols" :key="colIndex" class="align-middle">
|
|
<a v-if="col.link !== undefined" :class="col.link.css" :href="route(col.link.route, col.link.options)">{{ col.data }}</a>
|
|
<span v-else>{{ col.data }}</span>
|
|
</FwbTableCell>
|
|
<FwbTableCell v-if="editor" class="text-right whitespace-nowrap">
|
|
<fwb-button class="mr-2" size="sm" color="default" @click="openEditor(row.options.ref, row.options.editable)" outline>Edit</fwb-button>
|
|
<fwb-button size="sm" color="red" @click="showModal(row.options.ref.val, row.options.title)" outline>Remove</fwb-button>
|
|
</FwbTableCell>
|
|
<FwbTableCell v-else />
|
|
</FwbTableRow>
|
|
</FwbTableBody>
|
|
</FwbTable>
|
|
<div v-if="!body || body.length === 0" class="p-6 text-center text-sm text-gray-500">No records found.</div>
|
|
</div>
|
|
</div>
|
|
<DialogModal
|
|
v-if="editor"
|
|
:show="drawerUpdateForm"
|
|
@close="drawerUpdateForm = false"
|
|
maxWidth="xl"
|
|
>
|
|
|
|
<template #title>Update {{ options.editor_data.title }}</template>
|
|
<template #content>
|
|
<form @submit.prevent="update" class="pt-2">
|
|
<div v-for="(e, eIndex) in options.editor_data.form.el" :key="eIndex" class="col-span-6 sm:col-span-4 mb-4">
|
|
<InputLabel :for="e.id" :value="e.label"/>
|
|
<TextInput
|
|
v-if="e.type === 'text'"
|
|
:id="e.id"
|
|
:ref="e.ref"
|
|
type="text"
|
|
:autocomplete="e.autocomplete"
|
|
class="mt-1 block w-full text-sm"
|
|
v-model="formUpdate[e.bind]"
|
|
/>
|
|
<select
|
|
v-else-if="e.type === 'select'"
|
|
class="block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm text-sm"
|
|
:id="e.id"
|
|
:ref="e.ref"
|
|
v-model="formUpdate[e.bind]"
|
|
>
|
|
<option v-for="(op, opIndex) in e.selectOptions" :key="opIndex" :value="op.val">{{ op.desc }}</option>
|
|
</select>
|
|
</div>
|
|
<div class="flex justify-end mt-6 gap-3">
|
|
<ActionMessage :on="formUpdate.recentlySuccessful" class="me-3">
|
|
Saved.
|
|
</ActionMessage>
|
|
<PrimaryButton :class="{ 'opacity-25': formUpdate.processing }" :disabled="formUpdate.processing">
|
|
Save
|
|
</PrimaryButton>
|
|
</div>
|
|
</form>
|
|
</template>
|
|
</DialogModal>
|
|
|
|
<Modal
|
|
v-if="editor"
|
|
:show="modalRemove"
|
|
@close="closeModal"
|
|
maxWidth="sm"
|
|
>
|
|
<form @submit.prevent="remove">
|
|
<div class="p-6">
|
|
<div class="text-base font-medium text-center py-2 mb-4 text-gray-900">
|
|
Remove {{ options.editor_data.title }} <b>{{ modalRemoveTitle }}</b>?
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center">
|
|
<SecondaryButton type="button" @click="closeModal">
|
|
Cancel
|
|
</SecondaryButton>
|
|
<ActionMessage :on="formRemove.recentlySuccessful" class="me-3">
|
|
Deleted.
|
|
</ActionMessage>
|
|
<PrimaryButton class="bg-red-700" :class="{ 'opacity-25': formRemove.processing }" :disabled="formRemove.processing">
|
|
Delete
|
|
</PrimaryButton>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</Modal>
|
|
</template> |