Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-06-02 16:14:54 +03:00
parent 5f3e975b6d
commit b4e87cfd47
25 changed files with 4918 additions and 287 deletions

View File

@@ -86,6 +86,63 @@
</div>
<div
v-if="lookupsLoaded && roleId && deptCode"
class="group-members-toolbar"
>
<div class="group-members-toolbar__members">
<div class="text-caption text-weight-bold">
Grup Kullanicilari ({{ members.length }})
</div>
<div v-if="membersLoading" class="q-ml-sm">
<q-spinner color="primary" size="18px" />
</div>
<div v-else-if="members.length" class="group-members-toolbar__chips">
<q-chip
v-for="member in members"
:key="member.id"
dense
square
color="blue-1"
text-color="primary"
>
{{ member.id }} - {{ member.full_name || member.username }}
<q-tooltip>{{ member.username }}</q-tooltip>
</q-chip>
</div>
<div v-else class="text-caption text-grey-7 q-ml-sm">
Bu grupta aktif kullanici bulunmuyor.
</div>
</div>
<div class="group-members-toolbar__add">
<q-select
v-model="memberUserId"
:options="filteredUserOptions"
option-value="id"
option-label="title"
emit-value
map-options
dense
outlined
clearable
use-input
input-debounce="150"
label="Kullanici ekle"
class="group-members-toolbar__select"
@filter="filterUsers"
/>
<q-btn
color="primary"
icon="person_add"
label="Ekle"
:disable="!memberUserId || addingMember"
:loading="addingMember"
@click="addMember"
/>
</div>
</div>
</div>
@@ -184,7 +241,7 @@
<script setup>
import { ref, onMounted, watch } from 'vue'
import { computed, ref, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { Notify } from 'quasar'
import api from 'src/services/api'
@@ -201,13 +258,19 @@ const router = useRouter()
const roles = ref([])
const departments = ref([])
const users = ref([])
const filteredUserOptions = ref([])
const members = ref([])
const roleId = ref(null)
const deptCode = ref(null)
const memberUserId = ref(null)
const rows = ref([])
const loading = ref(false)
const membersLoading = ref(false)
const addingMember = ref(false)
const dirty = ref(false)
const lookupsLoaded = ref(false)
@@ -274,15 +337,18 @@ function applyRouteSelection () {
async function loadLookups () {
const [r, d, m] = await Promise.all([
const [r, d, m, u] = await Promise.all([
api.get('/lookups/roles-perm'),
api.get('/lookups/departments-perm'),
api.get('/lookups/modules')
api.get('/lookups/modules'),
api.get('/lookups/users-perm')
])
roles.value = r.data || []
departments.value = d.data || []
modules.value = m.data || []
users.value = u.data || []
filteredUserOptions.value = [...users.value]
lookupsLoaded.value = true
}
@@ -312,7 +378,10 @@ function initMatrix () {
async function loadMatrix () {
if (!roleId.value || !deptCode.value) return
if (!roleId.value || !deptCode.value) {
members.value = []
return
}
if (matrixLoading) return
matrixLoading = true
@@ -326,9 +395,10 @@ async function loadMatrix () {
initMatrix()
const res = await api.get(
`/roles/${roleId.value}/departments/${deptCode.value}/permissions`
)
const [res] = await Promise.all([
api.get(`/roles/${roleId.value}/departments/${deptCode.value}/permissions`),
loadMembers()
])
const list = Array.isArray(res.data) ? res.data : []
@@ -384,6 +454,70 @@ async function loadMatrix () {
}
}
const availableUserOptions = computed(() => {
const memberIDs = new Set(members.value.map(member => Number(member.id)))
return users.value.filter(user => !memberIDs.has(Number(user.id)))
})
function filterUsers (value, update) {
update(() => {
const needle = String(value || '').trim().toLocaleLowerCase('tr')
filteredUserOptions.value = needle
? availableUserOptions.value.filter(user => String(user.title || '').toLocaleLowerCase('tr').includes(needle))
: [...availableUserOptions.value]
})
}
async function loadMembers () {
if (!roleId.value || !deptCode.value) {
members.value = []
return
}
membersLoading.value = true
try {
const res = await api.get(
`/roles/${roleId.value}/departments/${encodeURIComponent(deptCode.value)}/members`
)
members.value = Array.isArray(res.data) ? res.data : []
filteredUserOptions.value = [...availableUserOptions.value]
} catch (err) {
console.error('GROUP MEMBERS LOAD ERROR:', err)
members.value = []
Notify.create({
type: 'negative',
message: 'Grup kullanicilari yuklenemedi'
})
} finally {
membersLoading.value = false
}
}
async function addMember () {
if (!roleId.value || !deptCode.value || !memberUserId.value) return
addingMember.value = true
try {
const res = await api.post(
`/roles/${roleId.value}/departments/${encodeURIComponent(deptCode.value)}/members`,
{ user_id: Number(memberUserId.value) }
)
members.value = Array.isArray(res.data) ? res.data : []
memberUserId.value = null
filteredUserOptions.value = [...availableUserOptions.value]
Notify.create({
type: 'positive',
message: 'Kullanici gruba eklendi'
})
} catch (err) {
console.error('GROUP MEMBER ADD ERROR:', err)
Notify.create({
type: 'negative',
message: 'Kullanici gruba eklenemedi'
})
} finally {
addingMember.value = false
}
}
/* ================= SAVE ================= */
@@ -482,3 +616,53 @@ watch(
)
</script>
<style scoped>
.group-members-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 12px;
background: #fff;
border-top: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
}
.group-members-toolbar__members {
min-width: 0;
display: flex;
align-items: center;
flex: 1 1 auto;
}
.group-members-toolbar__chips {
min-width: 0;
display: flex;
gap: 4px;
overflow-x: auto;
padding-left: 8px;
}
.group-members-toolbar__add {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
.group-members-toolbar__select {
width: 300px;
}
@media (max-width: 1100px) {
.group-members-toolbar {
align-items: stretch;
flex-direction: column;
}
.group-members-toolbar__select {
width: min(100%, 420px);
}
}
</style>