Mal Sorumlusu ekranına Personel İşlemleri eklendi.

This commit is contained in:
2025-11-09 00:15:29 +03:00
parent 82f95e217c
commit 176782f73c
4 changed files with 319 additions and 105 deletions

View File

@@ -2,44 +2,82 @@
import { onMount } from 'svelte';
export let user = null;
let personnel = [];
let units = [];
let personnel = [];
let units = [];
let loading = true;
let error = '';
let showAddModal = false;
let showEditModal = false;
let selectedPersonnel = null;
let isAdmin = false;
let isGoodsManager = false;
let allowedUnitId = null;
let allowedUnitName = '';
// Form değişkenleri
let formData = {
full_name: '',
rank: '',
registration_number: '',
tc_kimlik: '',
phone: '',
unit_id: '',
is_active: true
};
let formData = {
full_name: '',
rank: '',
registration_number: '',
tc_kimlik: '',
phone: '',
unit_id: '',
is_active: true
};
function resolveUser() {
if (user) return user;
const stored = localStorage.getItem('user');
return stored ? JSON.parse(stored) : null;
}
onMount(async () => {
const userData = localStorage.getItem('user');
if (!userData || JSON.parse(userData).role !== 'admin') {
const sessionUser = resolveUser();
if (!sessionUser) {
error = 'Kullanıcı oturumu bulunamadı.';
loading = false;
return;
}
user = sessionUser;
isAdmin = user.role === 'admin';
isGoodsManager = user.role === 'goods_manager';
if (!isAdmin && !isGoodsManager) {
error = 'Bu sayfaya erişim yetkiniz yok.';
loading = false;
return;
}
user = JSON.parse(userData);
if (isGoodsManager) {
if (!user.unit_id) {
error = 'Birlik bilginiz tanımlı değil. Lütfen sistem yöneticisine başvurun.';
loading = false;
return;
}
allowedUnitId = parseInt(user.unit_id);
allowedUnitName = user.unit_name || 'Birlik';
formData.unit_id = allowedUnitId;
}
await loadUnits();
await loadPersonnel();
});
async function loadPersonnel() {
try {
const response = await fetch('/api/unit-personnel');
let url = '/api/unit-personnel';
if (isGoodsManager && allowedUnitId) {
url += `?unit_id=${allowedUnitId}`;
}
const response = await fetch(url);
if (response.ok) {
const data = await response.json();
personnel = data.unitPersonnel || [];
personnel = data.unitPersonnel?.filter((person) => {
if (!isGoodsManager) return true;
return person.unit_id === allowedUnitId;
}) || [];
} else {
error = 'Personel bilgileri yüklenemedi.';
}
@@ -52,6 +90,14 @@ let formData = {
}
async function loadUnits() {
if (isGoodsManager) {
units = [{
id: allowedUnitId,
name: allowedUnitName
}];
return;
}
try {
const response = await fetch('/api/units');
if (response.ok) {
@@ -65,7 +111,12 @@ let formData = {
function getUnitName(unitId) {
const id = parseInt(unitId);
return units.find((unit) => unit.id === id)?.name || 'Belirtilmemiş';
const found = units.find((unit) => unit.id === id);
if (found) return found.name;
if (isGoodsManager && id === allowedUnitId) {
return allowedUnitName;
}
return 'Belirtilmemiş';
}
function resetForm() {
@@ -75,7 +126,7 @@ formData = {
registration_number: '',
tc_kimlik: '',
phone: '',
unit_id: '',
unit_id: isGoodsManager ? allowedUnitId : '',
is_active: true
};
selectedPersonnel = null;
@@ -83,6 +134,9 @@ formData = {
function openAddModal() {
resetForm();
if (isGoodsManager) {
formData.unit_id = allowedUnitId;
}
showAddModal = true;
}
@@ -94,7 +148,7 @@ formData = {
registration_number: person.registration_number,
tc_kimlik: person.tc_kimlik,
phone: person.phone,
unit_id: person.unit_id || '',
unit_id: person.unit_id || (isGoodsManager ? allowedUnitId : ''),
is_active: person.is_active
};
showEditModal = true;
@@ -436,17 +490,21 @@ formData = {
</div>
<div class="form-group">
<label for="unit_id">Birlik</label>
<select
id="unit_id"
class="form-input"
bind:value={formData.unit_id}
required
>
<option value="">Birlik Seçiniz</option>
{#each units as unit}
<option value={unit.id}>{unit.name}</option>
{/each}
</select>
{#if isAdmin}
<select
id="unit_id"
class="form-input"
bind:value={formData.unit_id}
required
>
<option value="">Birlik Seçiniz</option>
{#each units as unit}
<option value={unit.id}>{unit.name}</option>
{/each}
</select>
{:else}
<div class="unit-readonly">{allowedUnitName}</div>
{/if}
</div>
<div class="modal-actions">
<button type="button" class="btn btn-secondary" on:click={closeModal}>İptal</button>
@@ -524,17 +582,21 @@ formData = {
</div>
<div class="form-group">
<label for="edit-unit_id">Birlik</label>
<select
id="edit-unit_id"
class="form-input"
bind:value={formData.unit_id}
required
>
<option value="">Birlik Seçiniz</option>
{#each units as unit}
<option value={unit.id}>{unit.name}</option>
{/each}
</select>
{#if isAdmin}
<select
id="edit-unit_id"
class="form-input"
bind:value={formData.unit_id}
required
>
<option value="">Birlik Seçiniz</option>
{#each units as unit}
<option value={unit.id}>{unit.name}</option>
{/each}
</select>
{:else}
<div class="unit-readonly">{allowedUnitName}</div>
{/if}
</div>
<div class="form-group">
<label class="checkbox-label">
@@ -592,6 +654,15 @@ formData = {
border: 1px solid #FECACA;
}
.unit-readonly {
background: #F3F4F6;
border-radius: 8px;
padding: 0.75rem 1rem;
border: 1px solid #E5E7EB;
color: #111827;
font-weight: 600;
}
.loading-container {
display: flex;
flex-direction: column;

View File

@@ -5,6 +5,18 @@
let user = null;
const roleIconMap = {
admin: "fa-solid fa-user-shield",
fuel_manager: "fa-solid fa-gas-pump",
goods_manager: "fa-solid fa-truck s-jZJiUkwef1J0"
};
const roleLabelMap = {
admin: "Sistem Yöneticisi",
fuel_manager: "Yakıt Sorumlusu",
goods_manager: "Mal Sorumlusu"
};
function syncUserFromStorage() {
if (!browser) return;
try {
@@ -24,6 +36,17 @@
goto("/");
}
function getIconClass(role) {
return roleIconMap[role] || "fa-solid fa-user";
}
function getSubtitle(currentUser) {
if (currentUser?.rank) {
return currentUser.rank;
}
return roleLabelMap[currentUser?.role] || "Görev Bilinmiyor";
}
onMount(() => {
if (!browser) return;
syncUserFromStorage();
@@ -41,14 +64,14 @@
</div>
<div class="navbar-menu">
{#if user?.role === "goods_manager"}
<div class="goods-manager-info">
<div class="gm-avatar">
<i class="fa-solid fa-truck s-jZJiUkwef1J0"></i>
{#if user}
<div class="nav-user-info" data-role={user.role}>
<div class="nav-user-avatar">
<i class={getIconClass(user.role)}></i>
</div>
<div class="gm-details">
<div class="gm-name">{user.full_name || "Mal Sorumlusu"}</div>
<div class="gm-rank">{user.rank || "Rütbe Bilinmiyor"}</div>
<div class="nav-user-details">
<div class="nav-user-name">{user.full_name || "Kullanıcı"}</div>
<div class="nav-user-subtitle">{getSubtitle(user)}</div>
</div>
</div>
{/if}
@@ -150,19 +173,19 @@
transform: scale(1.1);
}
.goods-manager-info {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px 8px 10px;
background: rgba(255, 255, 255, 0.08);
border-radius: 999px;
color: #ffffff;
flex: 0 1 auto;
min-width: 0;
}
.nav-user-info {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 16px 8px 10px;
background: rgba(255, 255, 255, 0.08);
border-radius: 999px;
color: #ffffff;
flex: 0 1 auto;
min-width: 0;
}
.gm-avatar {
.nav-user-avatar {
width: 42px;
height: 42px;
border-radius: 50%;
@@ -174,25 +197,25 @@
color: #ffffff;
}
.gm-details {
display: flex;
flex-direction: column;
line-height: 1.2;
min-width: 0;
}
.nav-user-details {
display: flex;
flex-direction: column;
line-height: 1.2;
min-width: 0;
}
.gm-name {
font-size: 0.95rem;
font-weight: 600;
white-space: nowrap;
}
.nav-user-name {
font-size: 0.95rem;
font-weight: 600;
white-space: nowrap;
}
.gm-rank {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.7);
margin-top: 2px;
white-space: nowrap;
}
.nav-user-subtitle {
font-size: 0.8rem;
color: rgba(255, 255, 255, 0.7);
margin-top: 2px;
white-space: nowrap;
}
@media (max-width: 768px) {
.navbar-content {
@@ -226,23 +249,23 @@
padding: 6px;
}
.goods-manager-info {
padding: 6px 12px 6px 8px;
gap: 10px;
max-width: 70vw;
}
.nav-user-info {
padding: 6px 12px 6px 8px;
gap: 10px;
max-width: 70vw;
}
.gm-avatar {
.nav-user-avatar {
width: 36px;
height: 36px;
font-size: 16px;
}
.gm-name {
.nav-user-name {
font-size: 0.85rem;
}
.gm-rank {
.nav-user-subtitle {
font-size: 0.7rem;
}
}
@@ -273,7 +296,7 @@
padding: 4px;
}
.goods-manager-info {
.nav-user-info {
width: 100%;
justify-content: flex-start;
}

View File

@@ -22,8 +22,10 @@
let showUnits = false;
let showPersonnel = false;
let showGoodsManagers = false;
let showUnitPersonnel = false;
let showDevriçark = false;
let showMonthlyReport = false;
let showUnitJournal = false;
let socket = null;
// Admin state reset function
@@ -37,6 +39,8 @@
showGoodsManagerVehicles = false;
showDevriçark = false;
showMonthlyReport = false;
showUnitPersonnel = false;
showUnitJournal = false;
}
// Fuel Manager için veriler
@@ -465,6 +469,8 @@
showGoodsManagerVehicles = false;
showDevriçark = false;
showMonthlyReport = false;
showUnitPersonnel = false;
showUnitJournal = false;
showMobileMenu = false;
return;
}
@@ -475,6 +481,8 @@
showGoodsManagerVehicles = false;
showDevriçark = false;
showMonthlyReport = false;
showUnitPersonnel = false;
showUnitJournal = false;
showMobileMenu = false;
return;
}
@@ -485,6 +493,8 @@
showGoodsManagerVehicles = false;
showDevriçark = true;
showMonthlyReport = false;
showUnitPersonnel = false;
showUnitJournal = false;
showMobileMenu = false;
return;
}
@@ -495,6 +505,8 @@
showGoodsManagerVehicles = false;
showDevriçark = false;
showMonthlyReport = true;
showUnitPersonnel = false;
showUnitJournal = false;
showMobileMenu = false;
return;
}
@@ -504,6 +516,30 @@
showDevriçark = false;
showMonthlyReport = false;
showGoodsManagerVehicles = true;
showUnitPersonnel = false;
showUnitJournal = false;
showMobileMenu = false;
return;
}
if (page === "goods-manager-personnel" && user?.role === "goods_manager") {
showGoodsManager = false;
showDevriçark = false;
showMonthlyReport = false;
showGoodsManagerVehicles = false;
showUnitPersonnel = true;
showUnitJournal = false;
showMobileMenu = false;
return;
}
if (page === "unit-journal" && user?.role === "goods_manager") {
showGoodsManager = false;
showDevriçark = false;
showMonthlyReport = false;
showGoodsManagerVehicles = false;
showUnitPersonnel = false;
showUnitJournal = true;
showMobileMenu = false;
return;
}
@@ -793,7 +829,7 @@
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
Atanan Fişler
Atanan Senetler
</button>
</li>
<li class="nav-item">
@@ -820,6 +856,16 @@
Araç Yönetimi
</button>
</li>
<li class="nav-item">
<button
class="nav-btn"
on:click={() => navigateTo("goods-manager-personnel")}
class:active={showUnitPersonnel}
>
<i class="fa-solid fa-user-group"></i>
Personel İşlemleri
</button>
</li>
<li class="nav-item">
<button
class="nav-btn"
@@ -831,18 +877,28 @@
</button>
</li>
<li class="nav-item">
<button
class="nav-btn"
on:click={() => navigateTo("devriçark")}
class:active={showDevriçark}
>
<i class="fa-regular fa-file-lines"></i>
Devriçark İşlemleri
</button>
</li>
{/if}
</ul>
</nav>
<button
class="nav-btn"
on:click={() => navigateTo("devriçark")}
class:active={showDevriçark}
>
<i class="fa-regular fa-file-lines"></i>
Devriçark İşlemleri
</button>
</li>
<li class="nav-item">
<button
class="nav-btn"
on:click={() => navigateTo("unit-journal")}
class:active={showUnitJournal}
>
<i class="fa-solid fa-book-open"></i>
Birlik Akaryakıt Jurnali
</button>
</li>
{/if}
</ul>
</nav>
<!-- Ana İçerik Alanı -->
<div class="main-content">
@@ -976,12 +1032,30 @@
<GoodsManagerContent {user} />
{:else if user.role === "goods_manager" && showGoodsManagerVehicles}
<VehiclesContent {user} />
{:else if user.role === "goods_manager" && showUnitPersonnel}
<GoodsManagersContent {user} />
{:else if user.role === "goods_manager" && showMonthlyReport}
<!-- Monthly Fuel Report Content -->
<MonthlyFuelReportContent {user} />
{:else if user.role === "goods_manager" && showDevriçark}
<!-- Devriçark Content -->
<DevriçarkContent {user} />
{:else if user.role === "goods_manager" && showUnitJournal}
<div class="card">
<div class="card-header">
<div class="card-icon">
<i class="fa-solid fa-book-open"></i>
</div>
<h3>Birlik Akaryakıt Jurnali</h3>
</div>
<div class="card-content">
<p>
Birliklerin aylık yakıt giriş/çıkış hareketlerini bu alandan
görüntüleyebilir ve raporlayabilirsiniz. Özellik geliştirme
aşamasında, yakında detaylı veri görünümleri eklenecek.
</p>
</div>
</div>
{:else if user.role === "admin"}
<!-- Admin Dynamic Content -->

View File

@@ -1029,11 +1029,30 @@ app.delete('/api/fuel-personnel', (req, res) => {
// Unit Personnel API endpoint'leri (Teslim Alan personeller)
app.get('/api/unit-personnel', (req, res) => {
res.json({ unitPersonnel });
try {
const { isGoodsManager, unitId } = getGoodsManagerUnit(req);
let filteredPersonnel = unitPersonnel;
const requestedUnitId = req.query.unit_id ? parseInt(req.query.unit_id) : null;
if (isGoodsManager) {
if (!unitId) {
return res.status(400).json({ message: 'Birlik bilginiz tanımlı değil. Lütfen sistem yöneticisi ile iletişime geçin.' });
}
filteredPersonnel = filteredPersonnel.filter(person => person.unit_id === unitId);
} else if (requestedUnitId) {
filteredPersonnel = filteredPersonnel.filter(person => person.unit_id === requestedUnitId);
}
res.json({ unitPersonnel: filteredPersonnel });
} catch (error) {
console.error('GET /api/unit-personnel error:', error);
res.status(500).json({ message: 'Sunucu hatası.' });
}
});
app.post('/api/unit-personnel', (req, res) => {
try {
const { isGoodsManager, unitId: managerUnitId } = getGoodsManagerUnit(req);
const {
full_name,
rank,
@@ -1044,10 +1063,16 @@ app.post('/api/unit-personnel', (req, res) => {
is_active = true
} = req.body;
if (!full_name || !rank || !registration_number || !tc_kimlik || !phone || !unit_id) {
const normalizedUnitId = isGoodsManager ? managerUnitId : parseInt(unit_id);
if (!full_name || !rank || !registration_number || !tc_kimlik || !phone || !normalizedUnitId) {
return res.status(400).json({ message: 'Tüm alanlar zorunludur.' });
}
if (isGoodsManager && !managerUnitId) {
return res.status(400).json({ message: 'Birlik bilginiz tanımlı değil. Lütfen sistem yöneticisi ile iletişime geçin.' });
}
if (!/^[0-9]{11}$/.test(tc_kimlik)) {
return res.status(400).json({ message: 'TC Kimlik numarası 11 haneli olmalıdır.' });
}
@@ -1066,7 +1091,7 @@ app.post('/api/unit-personnel', (req, res) => {
registration_number: registration_number.trim(),
tc_kimlik: tc_kimlik.trim(),
phone: phone.trim(),
unit_id: parseInt(unit_id),
unit_id: parseInt(normalizedUnitId),
is_active: Boolean(is_active),
created_at: new Date().toISOString()
};
@@ -1085,6 +1110,7 @@ app.post('/api/unit-personnel', (req, res) => {
app.put('/api/unit-personnel', (req, res) => {
try {
const { isGoodsManager, unitId: managerUnitId } = getGoodsManagerUnit(req);
const {
id,
full_name,
@@ -1096,10 +1122,16 @@ app.put('/api/unit-personnel', (req, res) => {
is_active
} = req.body;
if (!id || !full_name || !rank || !registration_number || !tc_kimlik || !phone || !unit_id) {
const normalizedUnitId = isGoodsManager ? managerUnitId : parseInt(unit_id);
if (!id || !full_name || !rank || !registration_number || !tc_kimlik || !phone || !normalizedUnitId) {
return res.status(400).json({ message: 'Tüm alanlar zorunludur.' });
}
if (isGoodsManager && !managerUnitId) {
return res.status(400).json({ message: 'Birlik bilginiz tanımlı değil. Lütfen sistem yöneticisi ile iletişime geçin.' });
}
if (!/^[0-9]{11}$/.test(tc_kimlik)) {
return res.status(400).json({ message: 'TC Kimlik numarası 11 haneli olmalıdır.' });
}
@@ -1109,6 +1141,10 @@ app.put('/api/unit-personnel', (req, res) => {
return res.status(404).json({ message: 'Personel bulunamadı.' });
}
if (isGoodsManager && unitPersonnel[personnelIndex].unit_id !== managerUnitId) {
return res.status(403).json({ message: 'Bu personeli güncelleme yetkiniz yok.' });
}
const duplicate = unitPersonnel.find(p =>
p.id !== parseInt(id) && (p.registration_number === registration_number || p.tc_kimlik === tc_kimlik)
);
@@ -1123,7 +1159,7 @@ app.put('/api/unit-personnel', (req, res) => {
registration_number: registration_number.trim(),
tc_kimlik: tc_kimlik.trim(),
phone: phone.trim(),
unit_id: parseInt(unit_id),
unit_id: parseInt(normalizedUnitId),
is_active: Boolean(is_active)
};
@@ -1139,6 +1175,7 @@ app.put('/api/unit-personnel', (req, res) => {
app.delete('/api/unit-personnel', (req, res) => {
try {
const { isGoodsManager, unitId: managerUnitId } = getGoodsManagerUnit(req);
const { id } = req.body;
if (!id) {
@@ -1150,6 +1187,15 @@ app.delete('/api/unit-personnel', (req, res) => {
return res.status(404).json({ message: 'Personel bulunamadı.' });
}
if (isGoodsManager) {
if (!managerUnitId) {
return res.status(400).json({ message: 'Birlik bilginiz tanımlı değil. Lütfen sistem yöneticisi ile iletişime geçin.' });
}
if (unitPersonnel[personnelIndex].unit_id !== managerUnitId) {
return res.status(403).json({ message: 'Bu personeli silme yetkiniz yok.' });
}
}
const deletedPersonnel = unitPersonnel.splice(personnelIndex, 1)[0];
res.json({