257 lines
8.2 KiB
Vue
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('&', '&')
|
|
.replaceAll('<', '<')
|
|
.replaceAll('>', '>')
|
|
.replaceAll('"', '"')
|
|
.replaceAll("'", ''')
|
|
}
|
|
|
|
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>
|