List pages kept the current page in local state, so clicking into an asset and hitting Back remounted the list at page 1. A shared useListQuery composable now mirrors the page (and search term) into the URL query via router.replace across all 18 list pages, so Back restores the page you were on and lists are deep-linkable. Page 1 with no search stays a bare path; changing a filter resets to page 1; unrelated query keys are preserved. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
192 lines
6.0 KiB
Vue
192 lines
6.0 KiB
Vue
<template>
|
|
<div>
|
|
<div class="page-header">
|
|
<h2>Business Units</h2>
|
|
<button class="btn btn-primary" @click="openModal()">+ Add Business Unit</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div v-if="loading" class="loading">Loading...</div>
|
|
|
|
<template v-else>
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Business Unit</th>
|
|
<th>Code</th>
|
|
<th>Description</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="bu in items" :key="bu.businessunitid">
|
|
<td>{{ bu.businessunit }}</td>
|
|
<td>{{ bu.code || '-' }}</td>
|
|
<td class="cell-truncate" :title="bu.description">{{ bu.description || '-' }}</td>
|
|
<td class="actions">
|
|
<button class="btn btn-secondary btn-sm" @click="openModal(bu)">Edit</button>
|
|
<button class="btn btn-danger btn-sm" @click="confirmDelete(bu)">Delete</button>
|
|
</td>
|
|
</tr>
|
|
<tr v-if="items.length === 0">
|
|
<td colspan="4" style="text-align: center; color: var(--text-light);">
|
|
No business units found
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<PaginationBar
|
|
:page="page"
|
|
:totalPages="totalPages"
|
|
:perPage="perPage"
|
|
@update:page="goToPage"
|
|
@update:perPage="changePerPage"
|
|
/>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Add/Edit Modal -->
|
|
<div v-if="showModal" class="modal-overlay" @click.self="closeModal">
|
|
<div class="modal">
|
|
<div class="modal-header">
|
|
<h3>{{ editing ? 'Edit Business Unit' : 'Add Business Unit' }}</h3>
|
|
</div>
|
|
<form @submit.prevent="save">
|
|
<div class="modal-body">
|
|
<div class="form-group">
|
|
<label for="businessunit">Business Unit Name *</label>
|
|
<input id="businessunit" v-model="form.businessunit" type="text" class="form-control" required />
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="code">Code</label>
|
|
<input id="code" v-model="form.code" type="text" class="form-control" placeholder="e.g., ENGR, MFG" />
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="description">Description</label>
|
|
<textarea id="description" v-model="form.description" class="form-control" rows="3"></textarea>
|
|
</div>
|
|
<div v-if="error" class="error-message">{{ error }}</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" @click="closeModal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary" :disabled="saving">
|
|
{{ saving ? 'Saving...' : 'Save' }}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Modal -->
|
|
<div v-if="showDeleteModal" class="modal-overlay" @click.self="showDeleteModal = false">
|
|
<div class="modal">
|
|
<div class="modal-header"><h3>Delete Business Unit</h3></div>
|
|
<div class="modal-body">
|
|
<p>Are you sure you want to delete <strong>{{ toDelete?.businessunit }}</strong>?</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button class="btn btn-secondary" @click="showDeleteModal = false">Cancel</button>
|
|
<button class="btn btn-danger" @click="deleteItem">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import { businessunitsApi } from '../../api'
|
|
import PaginationBar from '../../components/PaginationBar.vue'
|
|
import { useToast } from '../../composables/toast'
|
|
import { apiError } from '../../utils/apiError'
|
|
import { useListQuery } from '@/composables/listQuery'
|
|
const toast = useToast()
|
|
|
|
const items = ref([])
|
|
const loading = ref(true)
|
|
const { page, setPage } = useListQuery({ onChange: loadData })
|
|
const totalPages = ref(1)
|
|
const perPage = ref(20)
|
|
|
|
const showModal = ref(false)
|
|
const editing = ref(null)
|
|
const saving = ref(false)
|
|
const error = ref('')
|
|
|
|
const showDeleteModal = ref(false)
|
|
const toDelete = ref(null)
|
|
|
|
const form = ref({ businessunit: '', code: '', description: '' })
|
|
|
|
onMounted(() => loadData())
|
|
|
|
async function loadData() {
|
|
loading.value = true
|
|
try {
|
|
const response = await businessunitsApi.list({ page: page.value, perpage: perPage.value })
|
|
items.value = response.data.data || []
|
|
totalPages.value = response.data.meta?.pagination?.totalpages || response.data.meta?.pagination?.total_pages || 1
|
|
} catch (err) {
|
|
console.error('Error loading business units:', err)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function goToPage(p) { setPage(p); loadData() }
|
|
|
|
function changePerPage(newPerPage) {
|
|
perPage.value = newPerPage
|
|
setPage(1)
|
|
loadData()
|
|
}
|
|
|
|
function openModal(item = null) {
|
|
editing.value = item
|
|
form.value = item ? {
|
|
businessunit: item.businessunit || '',
|
|
code: item.code || '',
|
|
description: item.description || ''
|
|
} : { businessunit: '', code: '', description: '' }
|
|
error.value = ''
|
|
showModal.value = true
|
|
}
|
|
|
|
function closeModal() { showModal.value = false; editing.value = null }
|
|
|
|
async function save() {
|
|
error.value = ''
|
|
saving.value = true
|
|
try {
|
|
if (editing.value) {
|
|
await businessunitsApi.update(editing.value.businessunitid, form.value)
|
|
} else {
|
|
await businessunitsApi.create(form.value)
|
|
}
|
|
closeModal()
|
|
loadData()
|
|
} catch (err) {
|
|
error.value = apiError(err, 'Failed to save')
|
|
} finally {
|
|
saving.value = false
|
|
}
|
|
}
|
|
|
|
function confirmDelete(item) { toDelete.value = item; showDeleteModal.value = true }
|
|
|
|
async function deleteItem() {
|
|
try {
|
|
await businessunitsApi.delete(toDelete.value.businessunitid)
|
|
showDeleteModal.value = false
|
|
toDelete.value = null
|
|
loadData()
|
|
} catch (err) {
|
|
toast.error('Failed to delete')
|
|
}
|
|
}
|
|
</script>
|