Updated A LOT
This commit is contained in:
187
src/components/PmCategorySettings.vue
Normal file
187
src/components/PmCategorySettings.vue
Normal file
@@ -0,0 +1,187 @@
|
||||
<template>
|
||||
<v-card class="span-12 panel-pad">
|
||||
<div class="toolbar-row mb-3">
|
||||
<div>
|
||||
<h2 class="section-title">PM categories</h2>
|
||||
<p class="quiet text-body-2">
|
||||
Manage the categories offered in the preventative-maintenance plan form. Separate from asset categories.
|
||||
Renaming a category updates every PM plan that uses it.
|
||||
</p>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-btn color="primary" prepend-icon="mdi-plus" @click="openNew">New category</v-btn>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
:columns="columns"
|
||||
:rows="categories"
|
||||
row-key="id"
|
||||
:page-size="10"
|
||||
has-actions
|
||||
searchable
|
||||
search-placeholder="Filter categories"
|
||||
empty-text="No PM categories yet."
|
||||
>
|
||||
<template #cell-name="{ row }">
|
||||
<span class="font-weight-bold">{{ row.name }}</span>
|
||||
</template>
|
||||
<template #cell-code="{ row }">{{ row.code || '—' }}</template>
|
||||
<template #actions="{ row }">
|
||||
<v-btn icon="mdi-pencil" size="small" variant="text" @click="openEdit(row)" />
|
||||
<v-btn icon="mdi-delete-outline" size="small" variant="text" color="error" @click="confirmDelete(row)" />
|
||||
</template>
|
||||
</DataTable>
|
||||
<v-alert v-if="error" type="error" density="compact" class="mt-3">{{ error }}</v-alert>
|
||||
|
||||
<!-- Create / edit -->
|
||||
<v-dialog v-model="dialog" max-width="460">
|
||||
<v-card>
|
||||
<v-card-title>{{ form.id ? 'Edit PM category' : 'New PM category' }}</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field v-model="form.name" label="Category name" autofocus @keyup.enter="save" />
|
||||
<v-text-field v-model="form.code" label="Short code (optional)" />
|
||||
<v-alert v-if="dialogError" type="error" density="compact" class="mt-2">{{ dialogError }}</v-alert>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="dialog = false">Cancel</v-btn>
|
||||
<v-btn color="primary" prepend-icon="mdi-content-save" :loading="saving" :disabled="!form.name.trim()" @click="save">Save</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Delete confirmation -->
|
||||
<v-dialog v-model="deleteDialog" max-width="500">
|
||||
<v-card>
|
||||
<v-card-title class="text-error">Delete PM category</v-card-title>
|
||||
<v-card-text>
|
||||
<p class="mb-3">Delete the PM category <strong>“{{ deleteTarget?.name }}”</strong>?</p>
|
||||
<v-alert
|
||||
:type="deleteTarget && deleteTarget.plan_count ? 'warning' : 'info'"
|
||||
variant="tonal"
|
||||
density="comfortable"
|
||||
class="mb-3"
|
||||
>
|
||||
<template v-if="deleteTarget && deleteTarget.plan_count">
|
||||
<strong>{{ deleteTarget.plan_count }}</strong> PM plan{{ deleteTarget.plan_count === 1 ? '' : 's' }} currently
|
||||
use this category.
|
||||
</template>
|
||||
<template v-else>No PM plans currently use this category.</template>
|
||||
</v-alert>
|
||||
<ul class="delete-impact">
|
||||
<li>Those plans <strong>keep their current category value</strong> — their data is not changed.</li>
|
||||
<li>The category will <strong>no longer appear</strong> in the dropdown for new or edited PM plans.</li>
|
||||
</ul>
|
||||
<p class="quiet text-body-2 mt-3">Tip: to merge categories instead, <strong>rename</strong> this one to match another.</p>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn variant="text" @click="deleteDialog = false">Cancel</v-btn>
|
||||
<v-btn color="error" prepend-icon="mdi-delete-outline" @click="performDelete">Delete category</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataTable from './DataTable.vue';
|
||||
import { apiRequest } from '../services/api';
|
||||
|
||||
export default {
|
||||
components: { DataTable },
|
||||
props: {
|
||||
token: { type: String, required: true }
|
||||
},
|
||||
emits: ['updated'],
|
||||
data() {
|
||||
return {
|
||||
categories: [],
|
||||
dialog: false,
|
||||
deleteDialog: false,
|
||||
deleteTarget: null,
|
||||
form: { id: null, name: '', code: '' },
|
||||
saving: false,
|
||||
error: '',
|
||||
dialogError: '',
|
||||
columns: [
|
||||
{ key: 'name', label: 'Category' },
|
||||
{ key: 'code', label: 'Code' },
|
||||
{ key: 'plan_count', label: 'Plans', format: 'number' }
|
||||
]
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
async api(path, options = {}) {
|
||||
return apiRequest(path, this.token, options);
|
||||
},
|
||||
async load() {
|
||||
this.error = '';
|
||||
try {
|
||||
const data = await (await this.api('/api/pm-categories')).json();
|
||||
this.categories = data.categories;
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
}
|
||||
},
|
||||
openNew() {
|
||||
this.form = { id: null, name: '', code: '' };
|
||||
this.dialogError = '';
|
||||
this.dialog = true;
|
||||
},
|
||||
openEdit(row) {
|
||||
this.form = { id: row.id, name: row.name, code: row.code || '' };
|
||||
this.dialogError = '';
|
||||
this.dialog = true;
|
||||
},
|
||||
async save() {
|
||||
const name = this.form.name.trim();
|
||||
if (!name) return;
|
||||
this.saving = true;
|
||||
this.dialogError = '';
|
||||
try {
|
||||
const method = this.form.id ? 'PUT' : 'POST';
|
||||
const url = this.form.id ? `/api/pm-categories/${this.form.id}` : '/api/pm-categories';
|
||||
await this.api(url, { method, body: JSON.stringify({ name, code: this.form.code || null }) });
|
||||
this.dialog = false;
|
||||
await this.load();
|
||||
this.$emit('updated');
|
||||
} catch (error) {
|
||||
this.dialogError = error.message;
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
},
|
||||
confirmDelete(row) {
|
||||
this.deleteTarget = row;
|
||||
this.deleteDialog = true;
|
||||
},
|
||||
async performDelete() {
|
||||
const target = this.deleteTarget;
|
||||
this.deleteDialog = false;
|
||||
this.deleteTarget = null;
|
||||
if (!target) return;
|
||||
try {
|
||||
await this.api(`/api/pm-categories/${target.id}`, { method: 'DELETE' });
|
||||
await this.load();
|
||||
this.$emit('updated');
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.delete-impact {
|
||||
padding-left: 20px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.delete-impact li {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user