Files
bssapp/ui/src/pages/RoleDepartmentPermissionPage.vue
MEHMETKECECI 03d6c61587 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	ui/src/pages/OrderList.vue
2026-02-13 15:17:23 +03:00

475 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-if="canUpdateUser && !lookupsLoaded" class="q-pa-xl flex flex-center">
<q-spinner
color="primary"
size="48px"
/>
</div>
<q-page v-if="canUpdateUser" class="permissions-page">
<!-- ================= STICKY STACK ================= -->
<div class="sticky-stack">
<!-- FILTER BAR -->
<div
v-if="lookupsLoaded"
class="filter-bar row q-col-gutter-md"
>
<div class="col-4">
<q-select
v-model="roleId"
:options="roles"
option-value="id"
option-label="title"
emit-value
map-options
label="Rol"
dense
outlined
@update:model-value="loadMatrix"
/>
</div>
<div class="col-4">
<q-select
v-model="deptCode"
:options="departments"
option-value="id"
option-label="title"
emit-value
map-options
label="Departman"
dense
outlined
@update:model-value="loadMatrix"
/>
</div>
</div>
<!-- SAVE TOOLBAR -->
<div class="save-toolbar">
<div class="label">
Rol + Departman Yetkilendirme
</div>
<q-btn
flat
icon="list"
label="Liste"
@click="goList"
/>
<q-btn
v-if="canUpdateUser"
color="primary"
icon="save"
label="Kaydet"
:disable="!dirty"
@click="save"
/>
</div>
</div>
<!-- ================= TABLE SCROLL ================= -->
<div
v-if="lookupsLoaded"
class="permissions-table-scroll"
>
<q-table
class="permissions-table"
:rows="rows"
:columns="columns"
row-key="module"
flat
bordered
dense
:loading="loading"
:rows-per-page-options="[0]"
:pagination="{ rowsPerPage: 0 }"
>
<!-- ========== HEADER ========== -->
<template v-slot:header-cell="props">
<q-th :props="props">
<!-- Module başlığı -->
<span v-if="props.col.name === 'module'">
{{ props.col.label }}
</span>
<!-- Checkbox kolon başlığı -->
<div v-else class="column items-center">
<span class="text-caption">
{{ props.col.label }}
</span>
<q-checkbox
dense
:model-value="isColumnChecked(props.col.name)"
@update:model-value="toggleColumn(props.col.name, $event)"
/>
</div>
</q-th>
</template>
<!-- ========== BODY ========== -->
<template v-slot:body-cell="props">
<q-td
:props="props"
:class="props.col.name === 'module'
? 'permissions-sticky-col'
: ''"
>
<!-- Module adı -->
<span v-if="props.col.name === 'module'">
{{ props.row.label }}
</span>
<!-- Checkbox -->
<q-checkbox
v-else
v-model="props.row[props.col.name]"
dense
@update:model-value="dirty = true"
/>
</q-td>
</template>
</q-table>
</div>
</q-page>
<q-page v-else class="q-pa-md flex flex-center">
<div class="text-negative text-subtitle1">
Bu module erisim yetkiniz yok.
</div>
</q-page>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Notify } from 'quasar'
import api from 'src/services/api'
import { usePermission } from 'src/composables/usePermission'
const { canUpdate } = usePermission()
const canUpdateUser = canUpdate('user')
const route = useRoute()
const router = useRouter()
/* ================= STATE ================= */
const roles = ref([])
const departments = ref([])
const roleId = ref(null)
const deptCode = ref(null)
const rows = ref([])
const loading = ref(false)
const dirty = ref(false)
const lookupsLoaded = ref(false)
/* ================= ACTIONS ================= */
const actions = [
{ key: 'write', label: 'Ekleme' },
{ key: 'read', label: 'Görüntüleme' },
{ key: 'delete', label: 'Silme' },
{ key: 'update', label: 'Güncelleme' },
{ key: 'export', label: ıktı' }
]
/* ================= MODULES ================= */
const modules = ref([])
/* ================= TABLE ================= */
const columns = [
{
name: 'module',
label: 'Modül',
field: 'label',
align: 'left'
},
...actions.map(a => ({
name: a.key,
label: a.label,
align: 'center'
}))
]
let matrixLoading = false
function goList () {
router.push({ name: 'role-dept-permissions-list' })
}
function applyRouteSelection () {
const qRole = String(route.query.roleId || '').trim()
const qDept = String(route.query.deptCode || '').trim()
if (/^\d+$/.test(qRole) && Number(qRole) > 0) {
roleId.value = qRole
}
if (qDept) {
deptCode.value = qDept
}
if (roleId.value && deptCode.value) {
loadMatrix()
}
}
/* ================= LOOKUPS ================= */
async function loadLookups () {
const [r, d, m] = await Promise.all([
api.get('/lookups/roles-perm'),
api.get('/lookups/departments-perm'),
api.get('/lookups/modules')
])
roles.value = r.data || []
departments.value = d.data || []
modules.value = m.data || []
lookupsLoaded.value = true
}
/* ================= INIT MATRIX ================= */
function initMatrix () {
rows.value = modules.value.map(m => {
const row = {
module: String(m.value).toLowerCase().trim(),
label: m.label
}
actions.forEach(a => {
row[a.key] = false
})
return row
})
}
/* ================= LOAD ================= */
async function loadMatrix () {
if (!roleId.value || !deptCode.value) return
if (matrixLoading) return
matrixLoading = true
loading.value = true
try {
if (!modules.value.length) {
await loadLookups()
}
initMatrix()
const res = await api.get(
`/roles/${roleId.value}/departments/${deptCode.value}/permissions`
)
const list = Array.isArray(res.data) ? res.data : []
console.log('PERM LIST:', list.slice(0, 10))
// ✅ BACKEND → UI ACTION MAP
const actionMap = {
insert: 'write',
view: 'read',
delete: 'delete',
update: 'update',
export: 'export'
}
list.forEach(p => {
const code = String(p.module_code || p.module)
.toLowerCase()
.trim()
const rawAction = String(p.action)
.toLowerCase()
.trim()
const mappedAction = actionMap[rawAction] || rawAction
const row = rows.value.find(r => r.module === code)
if (row && row.hasOwnProperty(mappedAction)) {
row[mappedAction] = Boolean(p.allowed)
}
})
dirty.value = false
} catch (err) {
console.error('PERM LOAD ERROR:', err)
Notify.create({
type: 'negative',
message: 'Yetkiler yüklenemedi'
})
} finally {
loading.value = false
matrixLoading = false
}
}
/* ================= SAVE ================= */
async function save () {
try {
loading.value = true
const payload = []
rows.value.forEach(r => {
actions.forEach(a => {
payload.push({
module: r.module,
action: a.key,
allowed: r[a.key]
})
})
})
await api.post(
`/roles/${roleId.value}/departments/${deptCode.value}/permissions`,
payload
)
Notify.create({
type: 'positive',
message: 'Kaydedildi'
})
dirty.value = false
} catch {
Notify.create({
type: 'negative',
message: 'Kayıt hatası'
})
} finally {
loading.value = false
}
}
/* ================= COLUMN ================= */
function isColumnChecked (key) {
if (!rows.value.length) return false
return rows.value.every(r => r[key] === true)
}
function toggleColumn (key, val) {
rows.value.forEach(r => {
r[key] = val
})
dirty.value = true
}
/* ================= INIT ================= */
onMounted(async () => {
await loadLookups()
applyRouteSelection()
})
watch(roleId, v => console.log('ROLE_ID >>>', v))
watch(deptCode, v => console.log('DEPT >>>', v))
watch(
() => [route.query.roleId, route.query.deptCode],
() => {
if (!lookupsLoaded.value) return
applyRouteSelection()
}
)
</script>