Files
bssapp/ui/src/pages/ProductSeriesDefinitions.vue
T

257 lines
8.2 KiB
Vue

<template>
<q-page class="q-pa-md">
<div class="row items-center justify-between q-mb-md">
<div>
<div class="text-h6">Urun Seri Tanimlamalari</div>
<div class="text-caption text-grey-7">Seri kodu ve beden seri basliklari burada yonetilir.</div>
</div>
<div class="row q-gutter-sm">
<q-btn color="primary" outline icon="grid_on" label="Excel" :disable="!rows.length" @click="exportExcel" />
<q-btn color="secondary" outline icon="refresh" label="Yenile" :loading="loading" @click="reload" />
<q-btn color="primary" icon="add" label="Yeni Seri" :disable="!canUpdate" @click="newRow" />
</div>
</div>
<q-table
flat
bordered
row-key="id"
:rows="rows"
:columns="columns"
:loading="loading"
:pagination="{ rowsPerPage: 0 }"
hide-bottom
class="bg-white"
>
<template #body-cell-is_active="props">
<q-td :props="props">
<q-toggle v-model="props.row.is_active" dense :disable="!canUpdate || saving" @update:model-value="() => markDirty(props.row)" />
</q-td>
</template>
<template #body-cell-actions="props">
<q-td :props="props" class="text-right">
<q-btn dense flat round icon="edit" color="primary" :disable="!canUpdate" @click="editRow(props.row)" />
<q-btn dense flat round icon="delete" color="negative" :disable="!canUpdate" @click="removeRow(props.row)" />
</q-td>
</template>
</q-table>
<q-dialog v-model="dialogOpen" persistent>
<q-card style="min-width: 520px">
<q-card-section class="row items-center justify-between">
<div class="text-subtitle1">{{ editing.id ? 'Seri Duzenle' : 'Yeni Seri' }}</div>
<q-btn flat round dense icon="close" v-close-popup />
</q-card-section>
<q-separator />
<q-card-section class="q-gutter-md">
<q-input v-model="editing.code" dense outlined label="Seri Kodu" autofocus />
<q-input v-model="editing.title" dense outlined label="Seri Basligi" />
<q-input v-model="editing.parent_filter" dense outlined label="Parent Filter" />
<q-input v-model.number="editing.sort_order" dense outlined type="number" label="Sira" />
<q-input v-model="editing.notes" dense outlined type="textarea" label="Not" />
<q-toggle v-model="editing.is_active" label="Aktif" />
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="Vazgec" v-close-popup />
<q-btn color="primary" icon="save" label="Kaydet" :loading="saving" @click="saveDialog" />
</q-card-actions>
</q-card>
</q-dialog>
</q-page>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { Dialog, Notify } from 'quasar'
import api from 'src/services/api'
import { usePermissionStore } from 'stores/permissionStore'
const perm = usePermissionStore()
const canUpdate = computed(() => perm.hasApiPermission('pricing:update'))
const loading = ref(false)
const saving = ref(false)
const rows = ref([])
const dialogOpen = ref(false)
const editing = ref(emptyRow())
const columns = [
{ name: 'code', label: 'Seri Kodu', field: 'code', align: 'left', sortable: true },
{ name: 'title', label: 'Seri Basligi', field: 'title', align: 'left', sortable: true },
{ name: 'parent_filter', label: 'Parent Filter', field: 'parent_filter', align: 'left', sortable: true },
{ name: 'sort_order', label: 'Sira', field: 'sort_order', align: 'right', sortable: true },
{ name: 'is_active', label: 'Aktif', field: 'is_active', align: 'center', sortable: true },
{ name: 'actions', label: '', field: 'actions', align: 'right' }
]
function emptyRow () {
return {
id: 0,
code: '',
title: '',
is_active: true,
parent_filter: '',
sort_order: 0,
notes: ''
}
}
function normalizeRow (row) {
return {
id: Number(row.id || 0),
code: String(row.code || '').trim(),
title: String(row.title || '').trim(),
is_active: row.is_active !== false,
parent_filter: String(row.parent_filter || '').trim(),
sort_order: Number(row.sort_order || 0),
notes: String(row.notes || '').trim()
}
}
async function reload () {
loading.value = true
try {
const res = await api.get('/pricing/product-series/definitions')
rows.value = (Array.isArray(res.data) ? res.data : []).map(normalizeRow)
} catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Seri tanimlari alinamadi' })
} finally {
loading.value = false
}
}
function newRow () {
editing.value = emptyRow()
dialogOpen.value = true
}
function editRow (row) {
editing.value = { ...normalizeRow(row) }
dialogOpen.value = true
}
function markDirty (row) {
saveInline(row)
}
async function saveInline (row) {
if (!canUpdate.value || !row.id) return
saving.value = true
try {
await api.put(`/pricing/product-series/definitions/${row.id}`, normalizeRow(row))
Notify.create({ type: 'positive', message: 'Kaydedildi' })
} catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Seri tanimi kaydedilemedi' })
await reload()
} finally {
saving.value = false
}
}
async function saveDialog () {
const payload = normalizeRow(editing.value)
if (!payload.code) {
Notify.create({ type: 'warning', message: 'Seri kodu zorunludur' })
return
}
saving.value = true
try {
if (payload.id) {
await api.put(`/pricing/product-series/definitions/${payload.id}`, payload)
} else {
await api.post('/pricing/product-series/definitions', payload)
}
dialogOpen.value = false
await reload()
Notify.create({ type: 'positive', message: 'Kaydedildi' })
} catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Seri tanimi kaydedilemedi' })
} finally {
saving.value = false
}
}
function removeRow (row) {
Dialog.create({
title: 'Seri pasife alinsin mi?',
message: `${row.code} - ${row.title}`,
cancel: true,
persistent: true
}).onOk(async () => {
saving.value = true
try {
await api.delete(`/pricing/product-series/definitions/${row.id}`)
await reload()
Notify.create({ type: 'positive', message: 'Pasife alindi' })
} catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Seri tanimi silinemedi' })
} finally {
saving.value = false
}
})
}
function escapeHtml (value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;')
}
function exportExcel () {
const headers = [
'ID',
'Seri Kodu',
'Seri Basligi',
'Aktif',
'Parent Filter',
'Sira',
'Not'
]
const headerHtml = headers.map(h => `<th>${escapeHtml(h)}</th>`).join('')
const rowsHtml = rows.value.map(row => `
<tr>
<td>${escapeHtml(row.id)}</td>
<td>${escapeHtml(row.code)}</td>
<td>${escapeHtml(row.title)}</td>
<td>${escapeHtml(row.is_active ? 'Evet' : 'Hayir')}</td>
<td>${escapeHtml(row.parent_filter)}</td>
<td>${escapeHtml(row.sort_order)}</td>
<td>${escapeHtml(row.notes)}</td>
</tr>
`).join('')
const html = `<!doctype html>
<html xmlns:x="urn:schemas-microsoft-com:office:excel">
<head>
<meta charset="utf-8">
<style>
table { border-collapse: collapse; font-family: Arial, sans-serif; font-size: 12px; }
th { background: #fff4bf; font-weight: 700; text-align: center; }
td, th { border: 1px solid #999; padding: 5px 7px; mso-number-format: '\\@'; }
</style>
</head>
<body>
<table border="1">
<thead><tr>${headerHtml}</tr></thead>
<tbody>${rowsHtml}</tbody>
</table>
</body>
</html>`
const blob = new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
const stamp = new Date().toISOString().slice(0, 10).replaceAll('-', '')
link.href = url
link.download = `urun_seri_tanimlari_${stamp}.xls`
document.body.appendChild(link)
link.click()
link.remove()
URL.revokeObjectURL(url)
}
onMounted(reload)
</script>