1448 lines
38 KiB
Svelte
1448 lines
38 KiB
Svelte
<script>
|
||
import { onMount } from 'svelte';
|
||
import {
|
||
listInventoryManagers,
|
||
createInventoryManager,
|
||
updateInventoryManager,
|
||
deleteInventoryManager,
|
||
listVehicles,
|
||
createVehicle,
|
||
updateVehicle,
|
||
deleteVehicle,
|
||
listUnits,
|
||
createUnit,
|
||
updateUnit,
|
||
deleteUnit,
|
||
listFuelPersonnel,
|
||
createFuelPersonnel,
|
||
updateFuelPersonnel,
|
||
deleteFuelPersonnel
|
||
} from '../api';
|
||
|
||
export let token;
|
||
|
||
const sections = [
|
||
{
|
||
id: 'inventory',
|
||
title: 'Mal sorumluları',
|
||
description: 'Depo süreçleri için yetkilendirilmiş kullanıcıları yönetin.',
|
||
icon: 'fa-solid fa-user-gear'
|
||
},
|
||
{
|
||
id: 'vehicles',
|
||
title: 'Araçlar',
|
||
description: 'Yakıt sevkiyatı yapan araçların kayıtlarını oluşturun.',
|
||
icon: 'fa-solid fa-truck-front'
|
||
},
|
||
{
|
||
id: 'units',
|
||
title: 'Birlikler',
|
||
description: 'Birlik bilgilerini ve sorumlu kişileri güncel tutun.',
|
||
icon: 'fa-solid fa-building'
|
||
},
|
||
{
|
||
id: 'personnel',
|
||
title: 'Yakıt personeli',
|
||
description: 'Yakıt veren personeli tanımlayın ve takip edin.',
|
||
icon: 'fa-solid fa-gas-pump'
|
||
}
|
||
];
|
||
|
||
let activeSection = 'inventory';
|
||
|
||
let lists = {
|
||
inventory: [],
|
||
vehicles: [],
|
||
units: [],
|
||
personnel: []
|
||
};
|
||
|
||
let loading = {
|
||
inventory: false,
|
||
vehicles: false,
|
||
units: false,
|
||
personnel: false
|
||
};
|
||
|
||
let submitting = {
|
||
inventory: false,
|
||
vehicles: false,
|
||
units: false,
|
||
personnel: false
|
||
};
|
||
|
||
let removing = {
|
||
inventory: false,
|
||
vehicles: false,
|
||
units: false,
|
||
personnel: false
|
||
};
|
||
|
||
let loaded = {
|
||
inventory: false,
|
||
vehicles: false,
|
||
units: false,
|
||
personnel: false
|
||
};
|
||
|
||
let feedback = {
|
||
inventory: null,
|
||
vehicles: null,
|
||
units: null,
|
||
personnel: null
|
||
};
|
||
|
||
let inventoryForm = {
|
||
username: '',
|
||
password: '',
|
||
displayName: ''
|
||
};
|
||
|
||
let vehicleForm = {
|
||
brand: '',
|
||
model: '',
|
||
year: '',
|
||
plate: ''
|
||
};
|
||
|
||
let unitForm = {
|
||
name: '',
|
||
address: '',
|
||
stk: '',
|
||
btk: '',
|
||
contactName: '',
|
||
contactRank: '',
|
||
contactRegistry: '',
|
||
contactIdentity: '',
|
||
contactPhone: ''
|
||
};
|
||
|
||
let personnelForm = {
|
||
fullName: '',
|
||
rank: '',
|
||
registryNumber: '',
|
||
identityNumber: '',
|
||
phone: ''
|
||
};
|
||
|
||
let editing = {
|
||
inventory: null,
|
||
vehicles: null,
|
||
units: null,
|
||
personnel: null
|
||
};
|
||
|
||
function resetInventoryForm() {
|
||
inventoryForm = {
|
||
username: '',
|
||
password: '',
|
||
displayName: ''
|
||
};
|
||
}
|
||
|
||
function resetVehicleForm() {
|
||
vehicleForm = {
|
||
brand: '',
|
||
model: '',
|
||
year: '',
|
||
plate: ''
|
||
};
|
||
}
|
||
|
||
function resetUnitForm() {
|
||
unitForm = {
|
||
name: '',
|
||
address: '',
|
||
stk: '',
|
||
btk: '',
|
||
contactName: '',
|
||
contactRank: '',
|
||
contactRegistry: '',
|
||
contactIdentity: '',
|
||
contactPhone: ''
|
||
};
|
||
}
|
||
|
||
function resetPersonnelForm() {
|
||
personnelForm = {
|
||
fullName: '',
|
||
rank: '',
|
||
registryNumber: '',
|
||
identityNumber: '',
|
||
phone: ''
|
||
};
|
||
}
|
||
|
||
onMount(() => {
|
||
loadSection('inventory', { force: true });
|
||
});
|
||
|
||
function setActiveSection(sectionId) {
|
||
activeSection = sectionId;
|
||
if (!loaded[sectionId]) {
|
||
loadSection(sectionId, { force: true });
|
||
}
|
||
}
|
||
|
||
function setFeedback(section, type = null, text = '') {
|
||
feedback = {
|
||
...feedback,
|
||
[section]: type ? { type, text } : null
|
||
};
|
||
}
|
||
|
||
async function loadSection(section, { force = false } = {}) {
|
||
if (!force && loaded[section]) {
|
||
return;
|
||
}
|
||
|
||
loading = { ...loading, [section]: true };
|
||
setFeedback(section);
|
||
|
||
try {
|
||
if (section === 'inventory') {
|
||
const response = await listInventoryManagers(token);
|
||
lists = { ...lists, inventory: response.managers };
|
||
} else if (section === 'vehicles') {
|
||
const response = await listVehicles(token);
|
||
lists = { ...lists, vehicles: response.vehicles };
|
||
} else if (section === 'units') {
|
||
const response = await listUnits(token);
|
||
lists = { ...lists, units: response.units };
|
||
} else if (section === 'personnel') {
|
||
const response = await listFuelPersonnel(token);
|
||
lists = { ...lists, personnel: response.personnel };
|
||
}
|
||
|
||
loaded = { ...loaded, [section]: true };
|
||
} catch (err) {
|
||
setFeedback(section, 'error', err.message);
|
||
} finally {
|
||
loading = { ...loading, [section]: false };
|
||
}
|
||
}
|
||
|
||
async function handleInventorySubmit(event) {
|
||
event.preventDefault();
|
||
setFeedback('inventory');
|
||
submitting = { ...submitting, inventory: true };
|
||
|
||
try {
|
||
if (editing.inventory) {
|
||
const displayName = inventoryForm.displayName.trim();
|
||
const password = inventoryForm.password ? inventoryForm.password.trim() : '';
|
||
|
||
if (!displayName && !password) {
|
||
throw new Error('En az bir alanı güncelleyin.');
|
||
}
|
||
|
||
const payload = {};
|
||
|
||
if (displayName) {
|
||
payload.displayName = displayName;
|
||
}
|
||
|
||
if (password) {
|
||
payload.password = password;
|
||
}
|
||
|
||
await updateInventoryManager(token, editing.inventory, payload);
|
||
setFeedback('inventory', 'success', 'Mal sorumlusu güncellendi.');
|
||
editing = { ...editing, inventory: null };
|
||
} else {
|
||
await createInventoryManager(token, {
|
||
username: inventoryForm.username.trim(),
|
||
displayName: inventoryForm.displayName.trim(),
|
||
password: inventoryForm.password
|
||
});
|
||
|
||
setFeedback('inventory', 'success', 'Yeni mal sorumlusu kaydedildi.');
|
||
}
|
||
|
||
resetInventoryForm();
|
||
await loadSection('inventory', { force: true });
|
||
} catch (err) {
|
||
setFeedback('inventory', 'error', err.message);
|
||
} finally {
|
||
submitting = { ...submitting, inventory: false };
|
||
}
|
||
}
|
||
|
||
async function handleVehicleSubmit(event) {
|
||
event.preventDefault();
|
||
setFeedback('vehicles');
|
||
submitting = { ...submitting, vehicles: true };
|
||
|
||
try {
|
||
const payload = {
|
||
brand: vehicleForm.brand.trim(),
|
||
model: vehicleForm.model.trim(),
|
||
year: vehicleForm.year ? Number(vehicleForm.year) : '',
|
||
plate: vehicleForm.plate.trim().toUpperCase()
|
||
};
|
||
|
||
if (!payload.brand || !payload.model || !payload.year || !payload.plate) {
|
||
throw new Error('Tüm alanları doldurun.');
|
||
}
|
||
|
||
if (editing.vehicles) {
|
||
await updateVehicle(token, editing.vehicles, payload);
|
||
setFeedback('vehicles', 'success', 'Araç kaydı güncellendi.');
|
||
editing = { ...editing, vehicles: null };
|
||
} else {
|
||
await createVehicle(token, payload);
|
||
setFeedback('vehicles', 'success', 'Araç kaydı oluşturuldu.');
|
||
}
|
||
|
||
resetVehicleForm();
|
||
await loadSection('vehicles', { force: true });
|
||
} catch (err) {
|
||
setFeedback('vehicles', 'error', err.message);
|
||
} finally {
|
||
submitting = { ...submitting, vehicles: false };
|
||
}
|
||
}
|
||
|
||
async function handleUnitSubmit(event) {
|
||
event.preventDefault();
|
||
setFeedback('units');
|
||
submitting = { ...submitting, units: true };
|
||
|
||
try {
|
||
const payload = {
|
||
name: unitForm.name.trim(),
|
||
address: unitForm.address.trim(),
|
||
stk: unitForm.stk.trim(),
|
||
btk: unitForm.btk.trim(),
|
||
contactName: unitForm.contactName.trim(),
|
||
contactRank: unitForm.contactRank.trim(),
|
||
contactRegistry: unitForm.contactRegistry.trim(),
|
||
contactIdentity: unitForm.contactIdentity.trim(),
|
||
contactPhone: unitForm.contactPhone.trim()
|
||
};
|
||
|
||
if (Object.values(payload).some((value) => !value)) {
|
||
throw new Error('Tüm alanları doldurun.');
|
||
}
|
||
|
||
if (editing.units) {
|
||
await updateUnit(token, editing.units, payload);
|
||
setFeedback('units', 'success', 'Birlik kaydı güncellendi.');
|
||
editing = { ...editing, units: null };
|
||
} else {
|
||
await createUnit(token, payload);
|
||
setFeedback('units', 'success', 'Birlik kaydı oluşturuldu.');
|
||
}
|
||
|
||
resetUnitForm();
|
||
await loadSection('units', { force: true });
|
||
} catch (err) {
|
||
setFeedback('units', 'error', err.message);
|
||
} finally {
|
||
submitting = { ...submitting, units: false };
|
||
}
|
||
}
|
||
|
||
async function handlePersonnelSubmit(event) {
|
||
event.preventDefault();
|
||
setFeedback('personnel');
|
||
submitting = { ...submitting, personnel: true };
|
||
|
||
try {
|
||
const payload = {
|
||
fullName: personnelForm.fullName.trim(),
|
||
rank: personnelForm.rank.trim(),
|
||
registryNumber: personnelForm.registryNumber.trim().toUpperCase(),
|
||
identityNumber: personnelForm.identityNumber.trim(),
|
||
phone: personnelForm.phone.trim()
|
||
};
|
||
|
||
if (Object.values(payload).some((value) => !value)) {
|
||
throw new Error('Tüm alanları doldurun.');
|
||
}
|
||
|
||
if (editing.personnel) {
|
||
await updateFuelPersonnel(token, editing.personnel, payload);
|
||
setFeedback('personnel', 'success', 'Personel kaydı güncellendi.');
|
||
editing = { ...editing, personnel: null };
|
||
} else {
|
||
await createFuelPersonnel(token, payload);
|
||
setFeedback('personnel', 'success', 'Personel kaydı oluşturuldu.');
|
||
}
|
||
|
||
resetPersonnelForm();
|
||
await loadSection('personnel', { force: true });
|
||
} catch (err) {
|
||
setFeedback('personnel', 'error', err.message);
|
||
} finally {
|
||
submitting = { ...submitting, personnel: false };
|
||
}
|
||
}
|
||
|
||
function startEdit(section, record) {
|
||
if (section === 'inventory') {
|
||
inventoryForm = {
|
||
username: record.username,
|
||
password: '',
|
||
displayName: record.displayName
|
||
};
|
||
} else if (section === 'vehicles') {
|
||
vehicleForm = {
|
||
brand: record.brand,
|
||
model: record.model,
|
||
year: String(record.year),
|
||
plate: record.plate
|
||
};
|
||
} else if (section === 'units') {
|
||
unitForm = {
|
||
name: record.name,
|
||
address: record.address,
|
||
stk: record.stk,
|
||
btk: record.btk,
|
||
contactName: record.contactName,
|
||
contactRank: record.contactRank,
|
||
contactRegistry: record.contactRegistry,
|
||
contactIdentity: record.contactIdentity,
|
||
contactPhone: record.contactPhone
|
||
};
|
||
} else if (section === 'personnel') {
|
||
personnelForm = {
|
||
fullName: record.fullName,
|
||
rank: record.rank,
|
||
registryNumber: record.registryNumber,
|
||
identityNumber: record.identityNumber,
|
||
phone: record.phone
|
||
};
|
||
}
|
||
|
||
editing = { ...editing, [section]: record.id };
|
||
setFeedback(section);
|
||
}
|
||
|
||
function cancelEdit(section) {
|
||
editing = { ...editing, [section]: null };
|
||
setFeedback(section);
|
||
|
||
if (section === 'inventory') {
|
||
resetInventoryForm();
|
||
} else if (section === 'vehicles') {
|
||
resetVehicleForm();
|
||
} else if (section === 'units') {
|
||
resetUnitForm();
|
||
} else if (section === 'personnel') {
|
||
resetPersonnelForm();
|
||
}
|
||
}
|
||
|
||
async function handleDelete(section, id) {
|
||
if (typeof window !== 'undefined') {
|
||
const confirmed = window.confirm('Kaydı silmek istediğinize emin misiniz?');
|
||
if (!confirmed) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
removing = { ...removing, [section]: id };
|
||
setFeedback(section);
|
||
|
||
try {
|
||
if (section === 'inventory') {
|
||
await deleteInventoryManager(token, id);
|
||
} else if (section === 'vehicles') {
|
||
await deleteVehicle(token, id);
|
||
} else if (section === 'units') {
|
||
await deleteUnit(token, id);
|
||
} else if (section === 'personnel') {
|
||
await deleteFuelPersonnel(token, id);
|
||
}
|
||
|
||
setFeedback(section, 'success', 'Kayıt silindi.');
|
||
|
||
if (editing[section] != null && editing[section] === id) {
|
||
editing = { ...editing, [section]: null };
|
||
|
||
if (section === 'inventory') {
|
||
resetInventoryForm();
|
||
} else if (section === 'vehicles') {
|
||
resetVehicleForm();
|
||
} else if (section === 'units') {
|
||
resetUnitForm();
|
||
} else if (section === 'personnel') {
|
||
resetPersonnelForm();
|
||
}
|
||
}
|
||
|
||
await loadSection(section, { force: true });
|
||
} catch (err) {
|
||
setFeedback(section, 'error', err.message);
|
||
} finally {
|
||
removing = { ...removing, [section]: false };
|
||
}
|
||
}
|
||
|
||
function formatDate(value) {
|
||
if (!value) {
|
||
return '-';
|
||
}
|
||
|
||
return value.replace('T', ' ');
|
||
}
|
||
</script>
|
||
|
||
<div class="admin">
|
||
<nav class="section-nav">
|
||
{#each sections as section}
|
||
<button
|
||
type="button"
|
||
class:selected={section.id === activeSection}
|
||
on:click={() => setActiveSection(section.id)}
|
||
>
|
||
<div class="icon-badge">
|
||
<i class={section.icon} aria-hidden="true"></i>
|
||
</div>
|
||
<span>{section.title}</span>
|
||
<small>{section.description}</small>
|
||
</button>
|
||
{/each}
|
||
</nav>
|
||
|
||
<section class="panel-area">
|
||
{#if activeSection === 'inventory'}
|
||
<div class="section-layout">
|
||
<form class="panel-card form-card" on:submit|preventDefault={handleInventorySubmit}>
|
||
<div class="form-header">
|
||
<h3>{editing.inventory ? 'Mal sorumlusu düzenle' : 'Mal sorumlusu ekle'}</h3>
|
||
{#if editing.inventory}
|
||
<button type="button" class="ghost" on:click={() => cancelEdit('inventory')}>
|
||
İptal
|
||
</button>
|
||
{/if}
|
||
</div>
|
||
<p class="hint">
|
||
{editing.inventory
|
||
? 'Var olan kaydı güncelleyebilir, yeni şifre vermek için alanı doldurabilirsiniz.'
|
||
: 'Depo sürecini yönetecek kullanıcıyı tanımlayın.'}
|
||
</p>
|
||
|
||
{#if feedback.inventory}
|
||
<div class="message {feedback.inventory.type}">{feedback.inventory.text}</div>
|
||
{/if}
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>Kullanıcı adı</span>
|
||
<input
|
||
placeholder="ornek: malsorum2"
|
||
bind:value={inventoryForm.username}
|
||
autocomplete="off"
|
||
required
|
||
readonly={Boolean(editing.inventory)}
|
||
/>
|
||
</label>
|
||
<label>
|
||
<span>Görünen ad</span>
|
||
<input
|
||
placeholder="ornek: Mal Sorumlusu 2"
|
||
bind:value={inventoryForm.displayName}
|
||
autocomplete="off"
|
||
required
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
<label>
|
||
<span>Geçici şifre</span>
|
||
<input
|
||
type="text"
|
||
placeholder={editing.inventory ? 'Boş bırakırsanız mevcut şifre korunur' : 'ornek: Mal@456'}
|
||
bind:value={inventoryForm.password}
|
||
autocomplete="off"
|
||
required={!editing.inventory}
|
||
/>
|
||
</label>
|
||
|
||
<button type="submit" class="primary" disabled={submitting.inventory}>
|
||
{#if submitting.inventory}
|
||
{editing.inventory ? 'Güncelleniyor...' : 'Kaydediliyor...'}
|
||
{:else}
|
||
{editing.inventory ? 'Güncelle' : 'Kaydet'}
|
||
{/if}
|
||
</button>
|
||
</form>
|
||
|
||
<div class="panel-card list-card">
|
||
<header class="list-header">
|
||
<div>
|
||
<h3>Kayıtlı mal sorumluları</h3>
|
||
<p>Oluşturma tarihi ve kullanıcı adı ile listelenir.</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
on:click={() => loadSection('inventory', { force: true })}
|
||
disabled={loading.inventory}
|
||
>
|
||
Yenile
|
||
</button>
|
||
</header>
|
||
|
||
{#if loading.inventory}
|
||
<p class="info">Liste yükleniyor...</p>
|
||
{:else if lists.inventory.length === 0}
|
||
<p class="info">Henüz mal sorumlusu oluşturulmadı.</p>
|
||
{:else}
|
||
<table class="simple-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Görünen ad</th>
|
||
<th>Kullanıcı adı</th>
|
||
<th>Oluşturma tarihi</th>
|
||
<th>İşlemler</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{#each lists.inventory as manager}
|
||
<tr>
|
||
<td>{manager.displayName}</td>
|
||
<td><code>{manager.username}</code></td>
|
||
<td>{formatDate(manager.createdAt)}</td>
|
||
<td class="actions">
|
||
<button type="button" class="ghost" on:click={() => startEdit('inventory', manager)}>
|
||
Düzenle
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="ghost danger"
|
||
on:click={() => handleDelete('inventory', manager.id)}
|
||
disabled={removing.inventory === manager.id}
|
||
>
|
||
{#if removing.inventory === manager.id}
|
||
Siliniyor...
|
||
{:else}
|
||
Sil
|
||
{/if}
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
{/each}
|
||
</tbody>
|
||
</table>
|
||
{/if}
|
||
|
||
</div>
|
||
</div>
|
||
{:else if activeSection === 'vehicles'}
|
||
<div class="section-layout">
|
||
<form class="panel-card form-card" on:submit|preventDefault={handleVehicleSubmit}>
|
||
<div class="form-header">
|
||
<h3>{editing.vehicles ? 'Araç kaydını düzenle' : 'Araç oluştur'}</h3>
|
||
{#if editing.vehicles}
|
||
<button type="button" class="ghost" on:click={() => cancelEdit('vehicles')}>
|
||
İptal
|
||
</button>
|
||
{/if}
|
||
</div>
|
||
<p class="hint">
|
||
{editing.vehicles
|
||
? 'Araç bilgilerini güncelleyebilir, plaka değişikliklerini kaydedebilirsiniz.'
|
||
: 'Yakıt transferinde kullanılacak araç bilgilerini girin.'}
|
||
</p>
|
||
|
||
{#if feedback.vehicles}
|
||
<div class="message {feedback.vehicles.type}">{feedback.vehicles.text}</div>
|
||
{/if}
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>Marka</span>
|
||
<input placeholder="Ford" bind:value={vehicleForm.brand} required />
|
||
</label>
|
||
<label>
|
||
<span>Model</span>
|
||
<input placeholder="Transit" bind:value={vehicleForm.model} required />
|
||
</label>
|
||
</div>
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>Model yılı</span>
|
||
<input
|
||
type="number"
|
||
min="1990"
|
||
max="2100"
|
||
placeholder="2024"
|
||
bind:value={vehicleForm.year}
|
||
required
|
||
/>
|
||
</label>
|
||
<label>
|
||
<span>Plaka</span>
|
||
<input placeholder="34 ABC 123" bind:value={vehicleForm.plate} required />
|
||
</label>
|
||
</div>
|
||
|
||
<button type="submit" class="primary" disabled={submitting.vehicles}>
|
||
{#if submitting.vehicles}
|
||
{editing.vehicles ? 'Güncelleniyor...' : 'Kaydediliyor...'}
|
||
{:else}
|
||
{editing.vehicles ? 'Güncelle' : 'Kaydet'}
|
||
{/if}
|
||
</button>
|
||
</form>
|
||
|
||
<div class="panel-card list-card">
|
||
<header class="list-header">
|
||
<div>
|
||
<h3>Araç listesi</h3>
|
||
<p>Plaka bazlı kayıtlarınızı buradan görüntüleyin.</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
on:click={() => loadSection('vehicles', { force: true })}
|
||
disabled={loading.vehicles}
|
||
>
|
||
Yenile
|
||
</button>
|
||
</header>
|
||
|
||
{#if loading.vehicles}
|
||
<p class="info">Liste yükleniyor...</p>
|
||
{:else if lists.vehicles.length === 0}
|
||
<p class="info">Kayıtlı aracınız bulunmuyor.</p>
|
||
{:else}
|
||
<table class="simple-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Araç</th>
|
||
<th>Model yılı</th>
|
||
<th>Plaka</th>
|
||
<th>Eklenme</th>
|
||
<th>İşlemler</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{#each lists.vehicles as vehicle}
|
||
<tr>
|
||
<td>{vehicle.brand} {vehicle.model}</td>
|
||
<td>{vehicle.year}</td>
|
||
<td><code>{vehicle.plate}</code></td>
|
||
<td>{formatDate(vehicle.createdAt)}</td>
|
||
<td class="actions">
|
||
<button type="button" class="ghost" on:click={() => startEdit('vehicles', vehicle)}>
|
||
Düzenle
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="ghost danger"
|
||
on:click={() => handleDelete('vehicles', vehicle.id)}
|
||
disabled={removing.vehicles === vehicle.id}
|
||
>
|
||
{#if removing.vehicles === vehicle.id}
|
||
Siliniyor...
|
||
{:else}
|
||
Sil
|
||
{/if}
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
{/each}
|
||
</tbody>
|
||
</table>
|
||
{/if}
|
||
|
||
</div>
|
||
</div>
|
||
{:else if activeSection === 'units'}
|
||
<div class="section-layout">
|
||
<form class="panel-card form-card" on:submit|preventDefault={handleUnitSubmit}>
|
||
<div class="form-header">
|
||
<h3>{editing.units ? 'Birlik kaydını düzenle' : 'Birlik ekle'}</h3>
|
||
{#if editing.units}
|
||
<button type="button" class="ghost" on:click={() => cancelEdit('units')}>
|
||
İptal
|
||
</button>
|
||
{/if}
|
||
</div>
|
||
<p class="hint">
|
||
{editing.units
|
||
? 'Birlik ve sorumlu bilgilerini güncelleyebilirsiniz.'
|
||
: 'Birlik ve sorumlularını ilişkilendirerek kayıt edin.'}
|
||
</p>
|
||
|
||
{#if feedback.units}
|
||
<div class="message {feedback.units.type}">{feedback.units.text}</div>
|
||
{/if}
|
||
|
||
<label>
|
||
<span>Birlik adı</span>
|
||
<input placeholder="Merkez Birlik" bind:value={unitForm.name} required />
|
||
</label>
|
||
|
||
<label>
|
||
<span>Adres</span>
|
||
<textarea
|
||
rows="2"
|
||
placeholder="Adres bilgisi"
|
||
bind:value={unitForm.address}
|
||
required
|
||
/>
|
||
</label>
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>STK</span>
|
||
<input placeholder="STK-1234" bind:value={unitForm.stk} required />
|
||
</label>
|
||
<label>
|
||
<span>BTK</span>
|
||
<input placeholder="BTK-5678" bind:value={unitForm.btk} required />
|
||
</label>
|
||
</div>
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>Sorumlu adı soyadı</span>
|
||
<input placeholder="Yzb. Murat Kaya" bind:value={unitForm.contactName} required />
|
||
</label>
|
||
<label>
|
||
<span>Rütbesi</span>
|
||
<input placeholder="Yüzbaşı" bind:value={unitForm.contactRank} required />
|
||
</label>
|
||
</div>
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>Sicil</span>
|
||
<input placeholder="MK4587" bind:value={unitForm.contactRegistry} required />
|
||
</label>
|
||
<label>
|
||
<span>TC Kimlik No</span>
|
||
<input
|
||
placeholder="25478963210"
|
||
bind:value={unitForm.contactIdentity}
|
||
required
|
||
/>
|
||
</label>
|
||
</div>
|
||
|
||
<label>
|
||
<span>İrtibat numarası</span>
|
||
<input placeholder="+90 5.." bind:value={unitForm.contactPhone} required />
|
||
</label>
|
||
|
||
<button type="submit" class="primary" disabled={submitting.units}>
|
||
{#if submitting.units}
|
||
{editing.units ? 'Güncelleniyor...' : 'Kaydediliyor...'}
|
||
{:else}
|
||
{editing.units ? 'Güncelle' : 'Kaydet'}
|
||
{/if}
|
||
</button>
|
||
</form>
|
||
|
||
<div class="panel-card list-card">
|
||
<header class="list-header">
|
||
<div>
|
||
<h3>Birlik listesi</h3>
|
||
<p>Sorumlu bilgileri ile birlikte görüntülenir.</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
on:click={() => loadSection('units', { force: true })}
|
||
disabled={loading.units}
|
||
>
|
||
Yenile
|
||
</button>
|
||
</header>
|
||
|
||
{#if loading.units}
|
||
<p class="info">Liste yükleniyor...</p>
|
||
{:else if lists.units.length === 0}
|
||
<p class="info">Kayıtlı birlik bulunmuyor.</p>
|
||
{:else}
|
||
<div class="unit-grid">
|
||
{#each lists.units as unit}
|
||
<article class="unit-card">
|
||
<header>
|
||
<h4>{unit.name}</h4>
|
||
<span>{formatDate(unit.createdAt)}</span>
|
||
</header>
|
||
<p class="meta">{unit.address}</p>
|
||
<div class="unit-tags">
|
||
<span>STK: {unit.stk}</span>
|
||
<span>BTK: {unit.btk}</span>
|
||
</div>
|
||
<div class="unit-contact">
|
||
<strong>{unit.contactName}</strong>
|
||
<span>{unit.contactRank} • {unit.contactRegistry}</span>
|
||
<span>{unit.contactPhone}</span>
|
||
<span>TC: {unit.contactIdentity}</span>
|
||
</div>
|
||
<div class="actions">
|
||
<button type="button" class="ghost" on:click={() => startEdit('units', unit)}>
|
||
Düzenle
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="ghost danger"
|
||
on:click={() => handleDelete('units', unit.id)}
|
||
disabled={removing.units === unit.id}
|
||
>
|
||
{#if removing.units === unit.id}
|
||
Siliniyor...
|
||
{:else}
|
||
Sil
|
||
{/if}
|
||
</button>
|
||
</div>
|
||
</article>
|
||
{/each}
|
||
</div>
|
||
{/if}
|
||
|
||
</div>
|
||
</div>
|
||
{:else if activeSection === 'personnel'}
|
||
<div class="section-layout">
|
||
<form class="panel-card form-card" on:submit|preventDefault={handlePersonnelSubmit}>
|
||
<div class="form-header">
|
||
<h3>{editing.personnel ? 'Personel kaydını düzenle' : 'Yakıt personeli ekle'}</h3>
|
||
{#if editing.personnel}
|
||
<button type="button" class="ghost" on:click={() => cancelEdit('personnel')}>
|
||
İptal
|
||
</button>
|
||
{/if}
|
||
</div>
|
||
<p class="hint">
|
||
{editing.personnel
|
||
? 'Personel kimlik ve irtibat bilgilerini güncelleyebilirsiniz.'
|
||
: 'Yakıt veren personelin kimlik ve iletişim bilgilerini kaydedin.'}
|
||
</p>
|
||
|
||
{#if feedback.personnel}
|
||
<div class="message {feedback.personnel.type}">{feedback.personnel.text}</div>
|
||
{/if}
|
||
|
||
<label>
|
||
<span>Adı soyadı</span>
|
||
<input placeholder="Astsb. Cahit Demir" bind:value={personnelForm.fullName} required />
|
||
</label>
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>Rütbesi</span>
|
||
<input placeholder="Astsubay" bind:value={personnelForm.rank} required />
|
||
</label>
|
||
<label>
|
||
<span>Sicili</span>
|
||
<input placeholder="CD5561" bind:value={personnelForm.registryNumber} required />
|
||
</label>
|
||
</div>
|
||
|
||
<div class="field-grid two">
|
||
<label>
|
||
<span>TC Kimlik No</span>
|
||
<input placeholder="14523698741" bind:value={personnelForm.identityNumber} required />
|
||
</label>
|
||
<label>
|
||
<span>İrtibat numarası</span>
|
||
<input placeholder="+90 5.." bind:value={personnelForm.phone} required />
|
||
</label>
|
||
</div>
|
||
|
||
<button type="submit" class="primary" disabled={submitting.personnel}>
|
||
{#if submitting.personnel}
|
||
{editing.personnel ? 'Güncelleniyor...' : 'Kaydediliyor...'}
|
||
{:else}
|
||
{editing.personnel ? 'Güncelle' : 'Kaydet'}
|
||
{/if}
|
||
</button>
|
||
</form>
|
||
|
||
<div class="panel-card list-card">
|
||
<header class="list-header">
|
||
<div>
|
||
<h3>Yakıt personeli listesi</h3>
|
||
<p>Sicil numarası ve iletişim bilgileriyle görüntülenir.</p>
|
||
</div>
|
||
<button
|
||
type="button"
|
||
on:click={() => loadSection('personnel', { force: true })}
|
||
disabled={loading.personnel}
|
||
>
|
||
Yenile
|
||
</button>
|
||
</header>
|
||
|
||
{#if loading.personnel}
|
||
<p class="info">Liste yükleniyor...</p>
|
||
{:else if lists.personnel.length === 0}
|
||
<p class="info">Kayıtlı yakıt personeli bulunmuyor.</p>
|
||
{:else}
|
||
<table class="simple-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Adı soyadı</th>
|
||
<th>Rütbesi</th>
|
||
<th>Sicil</th>
|
||
<th>İrtibat</th>
|
||
<th>İşlemler</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{#each lists.personnel as person}
|
||
<tr>
|
||
<td>{person.fullName}</td>
|
||
<td>{person.rank}</td>
|
||
<td><code>{person.registryNumber}</code></td>
|
||
<td>
|
||
<div>{person.phone}</div>
|
||
<div class="personnel-meta">TC: {person.identityNumber}</div>
|
||
</td>
|
||
<td class="actions">
|
||
<button type="button" class="ghost" on:click={() => startEdit('personnel', person)}>
|
||
Düzenle
|
||
</button>
|
||
<button
|
||
type="button"
|
||
class="ghost danger"
|
||
on:click={() => handleDelete('personnel', person.id)}
|
||
disabled={removing.personnel === person.id}
|
||
>
|
||
{#if removing.personnel === person.id}
|
||
Siliniyor...
|
||
{:else}
|
||
Sil
|
||
{/if}
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
{/each}
|
||
</tbody>
|
||
</table>
|
||
{/if}
|
||
|
||
</div>
|
||
</div>
|
||
{/if}
|
||
</section>
|
||
</div>
|
||
|
||
<style>
|
||
.admin {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1.5rem;
|
||
}
|
||
|
||
.section-nav {
|
||
display: grid;
|
||
gap: 0.9rem;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
}
|
||
|
||
.section-nav button {
|
||
position: relative;
|
||
text-align: left;
|
||
border: 1px solid #d4ddee;
|
||
background: linear-gradient(135deg, #ffffff, #f4f7ff);
|
||
border-radius: 16px;
|
||
padding: 1.1rem 1.15rem 1.05rem 1.15rem;
|
||
color: #27344d;
|
||
cursor: pointer;
|
||
display: grid;
|
||
gap: 0.45rem;
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||
}
|
||
|
||
.section-nav button:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 18px 32px rgba(88, 115, 173, 0.16);
|
||
}
|
||
|
||
.section-nav button.selected {
|
||
border-color: #4c6ef5;
|
||
box-shadow: 0 24px 46px rgba(76, 110, 245, 0.25);
|
||
background: linear-gradient(135deg, #4c6ef5, #6f8bff);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.section-nav button span {
|
||
display: block;
|
||
font-weight: 700;
|
||
color: inherit;
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.section-nav button small {
|
||
display: block;
|
||
margin-top: 0.15rem;
|
||
font-size: 0.85rem;
|
||
color: inherit;
|
||
line-height: 1.4;
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.section-nav button.selected small {
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.icon-badge {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 12px;
|
||
background: rgba(76, 110, 245, 0.12);
|
||
display: grid;
|
||
place-items: center;
|
||
color: inherit;
|
||
}
|
||
|
||
.section-nav button.selected .icon-badge {
|
||
background: rgba(255, 255, 255, 0.22);
|
||
color: #ffffff;
|
||
}
|
||
|
||
.panel-area {
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.section-layout {
|
||
display: grid;
|
||
gap: 1.5rem;
|
||
grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
|
||
align-items: start;
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
.section-layout {
|
||
grid-template-columns: minmax(0, 1fr);
|
||
}
|
||
}
|
||
|
||
.panel-card {
|
||
border: 1px solid #dde3f1;
|
||
border-radius: 20px;
|
||
background: #ffffff;
|
||
box-shadow: 0 24px 48px rgba(88, 115, 173, 0.18);
|
||
padding: 1.9rem;
|
||
display: grid;
|
||
gap: 1.25rem;
|
||
color: #2a3856;
|
||
}
|
||
|
||
.form-card h3,
|
||
.list-card h3 {
|
||
margin: 0;
|
||
font-size: 1.3rem;
|
||
color: #1d2943;
|
||
}
|
||
|
||
.form-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.form-header .ghost {
|
||
padding: 0.3rem 0.75rem;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.hint {
|
||
margin: -0.25rem 0 0.75rem;
|
||
color: #5d6a83;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.field-grid {
|
||
display: grid;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.field-grid.two {
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
}
|
||
|
||
label {
|
||
display: grid;
|
||
gap: 0.35rem;
|
||
color: #26324d;
|
||
font-weight: 600;
|
||
}
|
||
|
||
input,
|
||
textarea {
|
||
border: 1px solid rgba(121, 139, 189, 0.45);
|
||
border-radius: 12px;
|
||
padding: 0.85rem 1rem;
|
||
font-size: 1rem;
|
||
font-family: inherit;
|
||
background: #f9fbff;
|
||
color: #1f2d44;
|
||
transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||
}
|
||
|
||
textarea {
|
||
resize: vertical;
|
||
min-height: 70px;
|
||
}
|
||
|
||
input:focus,
|
||
textarea:focus {
|
||
outline: none;
|
||
border-color: #4c6ef5;
|
||
box-shadow: 0 0 0 3px rgba(76, 110, 245, 0.2);
|
||
background: #ffffff;
|
||
}
|
||
|
||
input:disabled {
|
||
background: #eef2fb;
|
||
color: #7a88a8;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
input[readonly] {
|
||
background: #f6f8ff;
|
||
color: #7a88a8;
|
||
}
|
||
|
||
.primary {
|
||
align-self: flex-start;
|
||
background: linear-gradient(135deg, #4c6ef5, #6f8bff);
|
||
color: #ffffff;
|
||
border: none;
|
||
border-radius: 12px;
|
||
padding: 0.85rem 1.4rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
box-shadow: 0 18px 32px rgba(76, 110, 245, 0.28);
|
||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||
}
|
||
|
||
.primary[disabled] {
|
||
background: #b5c0e3;
|
||
cursor: wait;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.primary:not([disabled]):hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 22px 40px rgba(76, 110, 245, 0.38);
|
||
}
|
||
|
||
.message {
|
||
border-radius: 12px;
|
||
padding: 0.8rem 1rem;
|
||
font-weight: 500;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.message.success {
|
||
background: rgba(76, 175, 80, 0.12);
|
||
color: #2e7d32;
|
||
border: 1px solid rgba(76, 175, 80, 0.3);
|
||
}
|
||
|
||
.message.error {
|
||
background: rgba(229, 57, 53, 0.12);
|
||
color: #c62828;
|
||
border: 1px solid rgba(229, 57, 53, 0.28);
|
||
}
|
||
|
||
.list-header {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.list-header h3 {
|
||
margin: 0;
|
||
}
|
||
|
||
.list-header p {
|
||
margin: 0.3rem 0 0;
|
||
color: #5d6a83;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.list-header button {
|
||
border: 1px solid #c9d6ed;
|
||
background: #edf2ff;
|
||
color: #3451a3;
|
||
padding: 0.45rem 0.9rem;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
transition: border-color 0.2s ease;
|
||
}
|
||
|
||
.list-header button[disabled] {
|
||
color: #a6b4d5;
|
||
border-color: #d8e1f3;
|
||
cursor: wait;
|
||
}
|
||
|
||
.info {
|
||
margin: 0;
|
||
color: #5d6a83;
|
||
font-size: 0.95rem;
|
||
}
|
||
|
||
.simple-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
border-radius: 16px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.simple-table th {
|
||
background: #f0f4ff;
|
||
color: #2a3856;
|
||
padding: 0.85rem 1rem;
|
||
font-size: 0.9rem;
|
||
text-align: left;
|
||
}
|
||
|
||
.simple-table td {
|
||
padding: 0.85rem 1rem;
|
||
border-bottom: 1px solid #e7ecf7;
|
||
color: #364564;
|
||
vertical-align: top;
|
||
}
|
||
|
||
.simple-table tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.ghost {
|
||
border: 1px solid #d3dcf0;
|
||
background: #f8faff;
|
||
color: #30426a;
|
||
border-radius: 10px;
|
||
padding: 0.35rem 0.85rem;
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: background 0.2s ease, border-color 0.2s ease;
|
||
}
|
||
|
||
.ghost:hover {
|
||
background: #e9f0ff;
|
||
border-color: #b8c6ea;
|
||
}
|
||
|
||
.ghost.danger {
|
||
border-color: #f5c7c5;
|
||
background: #fff0f0;
|
||
color: #c62828;
|
||
}
|
||
|
||
.ghost.danger:hover {
|
||
background: #ffe1df;
|
||
border-color: #f2a8a5;
|
||
}
|
||
|
||
code {
|
||
background: rgba(76, 110, 245, 0.1);
|
||
color: #2644a6;
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 8px;
|
||
font-family: 'Roboto Mono', monospace;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.unit-grid {
|
||
display: grid;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.unit-card {
|
||
border: 1px solid #dce4f5;
|
||
border-radius: 18px;
|
||
padding: 1.35rem;
|
||
background: #f6f9ff;
|
||
display: grid;
|
||
gap: 0.6rem;
|
||
}
|
||
|
||
.unit-card header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: baseline;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.unit-card header h4 {
|
||
margin: 0;
|
||
font-size: 1.05rem;
|
||
color: #1d2943;
|
||
}
|
||
|
||
.unit-card header span {
|
||
color: #7a88a8;
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
.unit-card .meta {
|
||
margin: 0;
|
||
color: #4e5d78;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.unit-tags {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.unit-tags span {
|
||
background: rgba(76, 110, 245, 0.12);
|
||
color: #354fa8;
|
||
padding: 0.25rem 0.7rem;
|
||
border-radius: 999px;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.unit-contact {
|
||
display: grid;
|
||
gap: 0.25rem;
|
||
color: #4e5d78;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.unit-contact strong {
|
||
color: #1d2943;
|
||
}
|
||
|
||
.unit-card .actions {
|
||
margin-top: 0.75rem;
|
||
}
|
||
|
||
.personnel-meta {
|
||
margin-top: 0.25rem;
|
||
font-size: 0.85rem;
|
||
color: #7a88a8;
|
||
}
|
||
|
||
@media (max-width: 640px) {
|
||
.panel-card {
|
||
padding: 1.5rem;
|
||
}
|
||
|
||
.section-nav button {
|
||
padding: 0.85rem 1rem;
|
||
}
|
||
|
||
.simple-table th,
|
||
.simple-table td {
|
||
padding: 0.75rem 0.85rem;
|
||
}
|
||
}
|
||
</style>
|