Updated A LOT

This commit is contained in:
2026-06-05 04:15:24 -05:00
parent 8335f1af1b
commit 4072695432
92 changed files with 16139 additions and 570 deletions

View File

@@ -0,0 +1,184 @@
<template>
<v-card class="span-12 panel-pad">
<div class="toolbar-row mb-3">
<div>
<h2 class="section-title">Depreciation zones</h2>
<p class="quiet text-body-2">
Special-allowance zones (e.g. New York Liberty Zone). Assign one to an asset and the engine applies the zones §168
allowance to the federal book for assets placed in service within the date window.
</p>
</div>
<v-spacer />
<v-btn color="primary" prepend-icon="mdi-plus" @click="openNew">New zone</v-btn>
</div>
<DataTable
:columns="columns"
:rows="zones"
row-key="code"
:page-size="10"
has-actions
searchable
search-placeholder="Filter zones"
empty-text="No depreciation zones."
>
<template #cell-name="{ row }">
<span class="font-weight-bold">{{ row.name }}</span>
<v-chip v-if="!row.enabled" size="x-small" variant="tonal" class="ml-2">Disabled</v-chip>
</template>
<template #cell-allowance_percent="{ row }">{{ row.allowance_percent }}%</template>
<template #cell-window="{ row }">{{ row.pis_start || '—' }} {{ row.pis_end || '—' }}</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>
<v-dialog v-model="dialog" max-width="640" scrollable>
<v-card>
<v-card-title>{{ form.exists ? `Edit zone · ${form.name}` : 'New depreciation zone' }}</v-card-title>
<v-card-text>
<div class="form-grid">
<v-text-field v-model="form.name" label="Zone name *" />
<v-text-field v-if="!form.exists" v-model="form.code" label="Code (optional)" placeholder="auto from name" />
<v-text-field v-model.number="form.allowance_percent" type="number" label="§168 special allowance %" suffix="%" />
<v-switch v-model="form.enabled" label="Enabled" color="primary" density="compact" hide-details />
<v-text-field v-model="form.pis_start" type="date" label="Placed-in-service start" />
<v-text-field v-model="form.pis_end" type="date" label="Placed-in-service end" />
<v-text-field v-model="form.real_property_end" type="date" label="Real-property PIS end (optional)" />
<v-text-field v-model.number="form.max_recovery_years" type="number" label="Max recovery years (optional)" />
<v-text-field v-model.number="form.leasehold_life_years" type="number" label="Leasehold improvement life (yrs, optional)" />
</div>
<v-textarea v-model="form.notes" class="mt-2" label="Notes" rows="3" />
<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 zone</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="deleteDialog" max-width="460">
<v-card>
<v-card-title class="text-error">Delete zone</v-card-title>
<v-card-text>
<p>Delete <strong>{{ deleteTarget?.name }}</strong>?</p>
<v-alert v-if="deleteTarget && deleteTarget.asset_count" type="warning" variant="tonal" density="comfortable" class="mt-2">
{{ deleteTarget.asset_count }} asset(s) reference this zone they keep their value, but the special allowance stops applying.
</v-alert>
</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</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card>
</template>
<script>
import DataTable from './DataTable.vue';
import { apiRequest } from '../services/api';
function blankForm() {
return {
exists: false, code: '', name: '', allowance_percent: 0, enabled: true,
pis_start: '', pis_end: '', real_property_end: '', max_recovery_years: null, leasehold_life_years: null, notes: ''
};
}
export default {
components: { DataTable },
props: {
token: { type: String, required: true }
},
emits: ['updated'],
data() {
return {
zones: [],
dialog: false,
deleteDialog: false,
deleteTarget: null,
form: blankForm(),
saving: false,
error: '',
dialogError: '',
columns: [
{ key: 'name', label: 'Zone' },
{ key: 'allowance_percent', label: 'Allowance' },
{ key: 'window', label: 'Placed-in-service window', sortable: false },
{ key: 'asset_count', label: 'Assets', 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/depreciation-zones')).json();
this.zones = data.zones;
} catch (error) {
this.error = error.message;
}
},
openNew() {
this.form = blankForm();
this.dialogError = '';
this.dialog = true;
},
openEdit(row) {
this.form = {
exists: true, code: row.code, name: row.name, allowance_percent: row.allowance_percent, enabled: row.enabled,
pis_start: row.pis_start || '', pis_end: row.pis_end || '', real_property_end: row.real_property_end || '',
max_recovery_years: row.max_recovery_years, leasehold_life_years: row.leasehold_life_years, notes: row.notes || ''
};
this.dialogError = '';
this.dialog = true;
},
async save() {
if (!this.form.name.trim()) return;
this.saving = true;
this.dialogError = '';
try {
const method = this.form.exists ? 'PUT' : 'POST';
const url = this.form.exists ? `/api/depreciation-zones/${this.form.code}` : '/api/depreciation-zones';
await this.api(url, { method, body: JSON.stringify(this.form) });
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/depreciation-zones/${target.code}`, { method: 'DELETE' });
await this.load();
this.$emit('updated');
} catch (error) {
this.error = error.message;
}
}
}
};
</script>