Files
ytp-glm/src/routes/dashboard/units/+page.svelte

988 lines
21 KiB
Svelte
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.
<svelte:head>
<style>
body {
background: #F2F3F7 !important;
}
</style>
</svelte:head>
<script>
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
let user = null;
let units = [];
let loading = true;
let error = '';
let showAddModal = false;
let showEditModal = false;
let selectedUnit = null;
// Form değişkenleri
let formData = {
name: '',
address: '',
stk: '',
btk: '',
commander: {
full_name: '',
rank: '',
registration_number: '',
tc_kimlik: '',
phone: '',
username: '',
password: ''
}
};
onMount(async () => {
const userData = localStorage.getItem('user');
if (!userData || JSON.parse(userData).role !== 'admin') {
goto('/dashboard');
return;
}
user = JSON.parse(userData);
await loadUnits();
});
async function loadUnits() {
try {
const response = await fetch('/api/units');
if (response.ok) {
const data = await response.json();
units = data.units;
} else {
error = 'Birlikler yüklenemedi.';
}
} catch (err) {
error = 'Bağlantı hatası.';
console.error('Load units error:', err);
} finally {
loading = false;
}
}
function resetForm() {
formData = {
name: '',
address: '',
stk: '',
btk: '',
commander: {
full_name: '',
rank: '',
registration_number: '',
tc_kimlik: '',
phone: '',
username: '',
password: ''
}
};
selectedUnit = null;
}
function openAddModal() {
resetForm();
showAddModal = true;
}
function openEditModal(unit) {
selectedUnit = unit;
formData = {
name: unit.name,
address: unit.address,
stk: unit.stk,
btk: unit.btk,
commander: {
full_name: unit.commander.full_name,
rank: unit.commander.rank,
registration_number: unit.commander.registration_number,
tc_kimlik: unit.commander.tc_kimlik,
phone: unit.commander.phone,
username: unit.commander.username || '',
password: unit.commander.password || ''
}
};
showEditModal = true;
}
function closeModal() {
showAddModal = false;
showEditModal = false;
resetForm();
}
async function handleAddUnit() {
if (!formData.name || !formData.address || !formData.stk || !formData.btk) {
error = 'Tüm alanlar zorunludur.';
return;
}
const { commander } = formData;
if (!commander.full_name || !commander.rank || !commander.registration_number || !commander.tc_kimlik || !commander.phone || !commander.username || !commander.password) {
error = 'Mal sorumlusunun tüm bilgileri zorunludur.';
return;
}
if (!/^[0-9]{11}$/.test(commander.tc_kimlik)) {
error = 'TC Kimlik numarası 11 haneli olmalıdır.';
return;
}
try {
const response = await fetch('/api/units', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
const data = await response.json();
if (response.ok) {
await loadUnits();
closeModal();
error = '';
} else {
error = data.message || 'Birlik eklenemedi.';
}
} catch (err) {
error = 'Bağlantı hatası.';
console.error('Add unit error:', err);
}
}
async function handleUpdateUnit() {
if (!formData.name || !formData.address || !formData.stk || !formData.btk) {
error = 'Tüm alanlar zorunludur.';
return;
}
const { commander } = formData;
if (!commander.full_name || !commander.rank || !commander.registration_number || !commander.tc_kimlik || !commander.phone || !commander.username || !commander.password) {
error = 'Mal sorumlusunun tüm bilgileri zorunludur.';
return;
}
if (!/^[0-9]{11}$/.test(commander.tc_kimlik)) {
error = 'TC Kimlik numarası 11 haneli olmalıdır.';
return;
}
try {
const response = await fetch('/api/units', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
id: selectedUnit.id,
...formData
}),
});
const data = await response.json();
if (response.ok) {
await loadUnits();
closeModal();
error = '';
} else {
error = data.message || 'Birlik güncellenemedi.';
}
} catch (err) {
error = 'Bağlantı hatası.';
console.error('Update unit error:', err);
}
}
async function handleDeleteUnit(unit) {
if (!confirm(`${unit.name} birliğini silmek istediğinizden emin misiniz?`)) {
return;
}
try {
const response = await fetch('/api/units', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ id: unit.id }),
});
const data = await response.json();
if (response.ok) {
await loadUnits();
error = '';
} else {
error = data.message || 'Birlik silinemedi.';
}
} catch (err) {
error = 'Bağlantı hatası.';
console.error('Delete unit error:', err);
}
}
function goBack() {
goto('/dashboard');
}
</script>
<div class="units-page">
<div class="page-header">
<div class="header-left">
<button class="btn btn-secondary" on:click={goBack}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M19 12H5"/>
<path d="M12 19l-7-7 7-7"/>
</svg>
Geri
</button>
<h1 class="page-title">Birlik Yönetimi</h1>
</div>
<button class="btn btn-primary" on:click={openAddModal}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"/>
<line x1="5" y1="12" x2="19" y2="12"/>
</svg>
Yeni Birlik Ekle
</button>
</div>
{#if error}
<div class="error-message">
{error}
</div>
{/if}
{#if loading}
<div class="loading-container">
<div class="spinner"></div>
<p>Yükleniyor...</p>
</div>
{:else if units.length === 0}
<div class="empty-state">
<div class="empty-icon">
<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M3 21h18"/>
<path d="M5 21V7l8-4v18"/>
<path d="M19 21V11l-6-4"/>
</svg>
</div>
<h3>Henüz Birlik Yok</h3>
<p>Sisteme birlik eklemek için "Yeni Birlik Ekle" butonuna tıklayın.</p>
<button class="btn btn-primary" on:click={openAddModal}>
İlk Birliği Ekle
</button>
</div>
{:else}
<div class="units-grid">
{#each units as unit (unit.id)}
<div class="unit-card card">
<div class="unit-header">
<div class="unit-info">
<h3 class="unit-name">{unit.name}</h3>
<p class="unit-address">{unit.address}</p>
</div>
</div>
<div class="unit-details">
<div class="detail-item">
<span class="detail-label">STK:</span>
<span class="detail-value">{unit.stk}</span>
</div>
<div class="detail-item">
<span class="detail-label">BTK:</span>
<span class="detail-value">{unit.btk}</span>
</div>
</div>
<div class="commander-section">
<h4 class="commander-title">Mal Sorumlusu</h4>
<div class="commander-info">
<div class="commander-details">
<p class="commander-name">{unit.commander.rank} {unit.commander.full_name}</p>
<p class="commander-detail">Sicil: {unit.commander.registration_number}</p>
<p class="commander-detail">TC: {unit.commander.tc_kimlik}</p>
<p class="commander-detail">İrtibat: {unit.commander.phone}</p>
</div>
</div>
</div>
<div class="unit-actions">
<button class="btn btn-sm btn-secondary" on:click={() => openEditModal(unit)}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
</svg>
Düzenle
</button>
<button class="btn btn-sm btn-danger" on:click={() => handleDeleteUnit(unit)}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3 6 5 6 21 6"/>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
</svg>
Sil
</button>
</div>
</div>
{/each}
</div>
{/if}
</div>
<!-- Birlik Ekle Modal -->
{#if showAddModal}
<div class="modal-overlay" on:click={closeModal}>
<div class="modal modal-large" on:click|stopPropagation>
<div class="modal-header">
<h2>Yeni Birlik Ekle</h2>
<button class="modal-close" on:click={closeModal}>×</button>
</div>
<form on:submit|preventDefault={handleAddUnit} class="modal-form">
<div class="form-section">
<h3>Birlik Bilgileri</h3>
<div class="form-group">
<label for="name">Birlik Adı</label>
<input
id="name"
type="text"
class="form-input"
bind:value={formData.name}
placeholder="1. Motorlu Piyade Tugayı"
required
/>
</div>
<div class="form-group">
<label for="address">Adres</label>
<input
id="address"
type="text"
class="form-input"
bind:value={formData.address}
placeholder="Mecidiyeköy, Şişli/İstanbul"
required
/>
</div>
<div class="form-row">
<div class="form-group">
<label for="stk">STK</label>
<input
id="stk"
type="text"
class="form-input"
bind:value={formData.stk}
placeholder="STK-12345"
required
/>
</div>
<div class="form-group">
<label for="btk">BTK</label>
<input
id="btk"
type="text"
class="form-input"
bind:value={formData.btk}
placeholder="BTK-67890"
required
/>
</div>
</div>
</div>
<div class="form-section">
<h3>Mal Sorumlusu</h3>
<div class="form-row">
<div class="form-group">
<label for="commander-name">Adı Soyadı</label>
<input
id="commander-name"
type="text"
class="form-input"
bind:value={formData.commander.full_name}
placeholder="Mehmet Yılmaz"
required
/>
</div>
<div class="form-group">
<label for="commander-rank">Rütbesi</label>
<input
id="commander-rank"
type="text"
class="form-input"
bind:value={formData.commander.rank}
placeholder="Yüzbaşı"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="commander-registration">Sicil No</label>
<input
id="commander-registration"
type="text"
class="form-input"
bind:value={formData.commander.registration_number}
placeholder="123456"
required
/>
</div>
<div class="form-group">
<label for="commander-phone">İrtibat No</label>
<input
id="commander-phone"
type="tel"
class="form-input"
bind:value={formData.commander.phone}
placeholder="05321234567"
required
/>
</div>
</div>
<div class="form-group">
<label for="commander-tc">TC Kimlik Numarası</label>
<input
id="commander-tc"
type="text"
class="form-input"
bind:value={formData.commander.tc_kimlik}
placeholder="12345678901"
maxlength="11"
required
/>
</div>
<div class="form-row">
<div class="form-group">
<label for="commander-username">Kullanıcı Adı</label>
<input
id="commander-username"
type="text"
class="form-input"
bind:value={formData.commander.username}
placeholder="kullanici_adi"
required
/>
</div>
<div class="form-group">
<label for="commander-password">Şifre</label>
<input
id="commander-password"
type="password"
class="form-input"
bind:value={formData.commander.password}
placeholder="şifre123"
required
/>
</div>
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-secondary" on:click={closeModal}>İptal</button>
<button type="submit" class="btn btn-primary">Kaydet</button>
</div>
</form>
</div>
</div>
{/if}
<!-- Birlik Düzenle Modal -->
{#if showEditModal}
<div class="modal-overlay" on:click={closeModal}>
<div class="modal modal-large" on:click|stopPropagation>
<div class="modal-header">
<h2>Birlik Düzenle</h2>
<button class="modal-close" on:click={closeModal}>×</button>
</div>
<form on:submit|preventDefault={handleUpdateUnit} class="modal-form">
<div class="form-section">
<h3>Birlik Bilgileri</h3>
<div class="form-group">
<label for="edit-name">Birlik Adı</label>
<input
id="edit-name"
type="text"
class="form-input"
bind:value={formData.name}
placeholder="1. Motorlu Piyade Tugayı"
required
/>
</div>
<div class="form-group">
<label for="edit-address">Adres</label>
<input
id="edit-address"
type="text"
class="form-input"
bind:value={formData.address}
placeholder="Mecidiyeköy, Şişli/İstanbul"
required
/>
</div>
<div class="form-row">
<div class="form-group">
<label for="edit-stk">STK</label>
<input
id="edit-stk"
type="text"
class="form-input"
bind:value={formData.stk}
placeholder="STK-12345"
required
/>
</div>
<div class="form-group">
<label for="edit-btk">BTK</label>
<input
id="edit-btk"
type="text"
class="form-input"
bind:value={formData.btk}
placeholder="BTK-67890"
required
/>
</div>
</div>
</div>
<div class="form-section">
<h3>Mal Sorumlusu</h3>
<div class="form-row">
<div class="form-group">
<label for="edit-commander-name">Adı Soyadı</label>
<input
id="edit-commander-name"
type="text"
class="form-input"
bind:value={formData.commander.full_name}
placeholder="Mehmet Yılmaz"
required
/>
</div>
<div class="form-group">
<label for="edit-commander-rank">Rütbesi</label>
<input
id="edit-commander-rank"
type="text"
class="form-input"
bind:value={formData.commander.rank}
placeholder="Yüzbaşı"
required
/>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="edit-commander-registration">Sicil No</label>
<input
id="edit-commander-registration"
type="text"
class="form-input"
bind:value={formData.commander.registration_number}
placeholder="123456"
required
/>
</div>
<div class="form-group">
<label for="edit-commander-phone">İrtibat No</label>
<input
id="edit-commander-phone"
type="tel"
class="form-input"
bind:value={formData.commander.phone}
placeholder="05321234567"
required
/>
</div>
</div>
<div class="form-group">
<label for="edit-commander-tc">TC Kimlik Numarası</label>
<input
id="edit-commander-tc"
type="text"
class="form-input"
bind:value={formData.commander.tc_kimlik}
placeholder="12345678901"
maxlength="11"
required
/>
</div>
<div class="form-row">
<div class="form-group">
<label for="edit-commander-username">Kullanıcı Adı</label>
<input
id="edit-commander-username"
type="text"
class="form-input"
bind:value={formData.commander.username}
placeholder="kullanici_adi"
required
/>
</div>
<div class="form-group">
<label for="edit-commander-password">Şifre</label>
<input
id="edit-commander-password"
type="password"
class="form-input"
bind:value={formData.commander.password}
placeholder="şifre123"
required
/>
</div>
</div>
</div>
<div class="modal-actions">
<button type="button" class="btn btn-secondary" on:click={closeModal}>İptal</button>
<button type="submit" class="btn btn-primary">Güncelle</button>
</div>
</form>
</div>
</div>
{/if}
<style>
.units-page {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
gap: 1rem;
}
.header-left {
display: flex;
align-items: center;
gap: 1rem;
}
.page-title {
font-size: 2rem;
font-weight: 700;
color: var(--text-color);
margin: 0;
}
.error-message {
background: #FEE2E2;
color: #DC2626;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1.5rem;
border: 1px solid #FECACA;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #E5E7EB;
border-top: 4px solid var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.empty-state {
text-align: center;
padding: 3rem;
background: white;
border-radius: 12px;
border: 1px solid var(--card-border-color);
}
.empty-icon {
color: var(--text-secondary);
margin-bottom: 1rem;
}
.empty-state h3 {
font-size: 1.5rem;
font-weight: 600;
color: var(--text-color);
margin-bottom: 0.5rem;
}
.empty-state p {
color: var(--text-secondary);
margin-bottom: 2rem;
}
.units-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 1.5rem;
}
.unit-card {
background: white;
border: 1px solid var(--card-border-color);
border-radius: 12px;
padding: 1.5rem;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.unit-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.unit-header {
margin-bottom: 1rem;
padding-bottom: 1rem;
border-bottom: 1px solid var(--card-border-color);
}
.unit-name {
font-size: 1.2rem;
font-weight: 600;
color: var(--text-color);
margin: 0 0 0.5rem 0;
}
.unit-address {
color: var(--text-secondary);
margin: 0;
font-size: 0.9rem;
}
.unit-details {
margin-bottom: 1rem;
}
.detail-item {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.detail-label {
font-weight: 500;
color: var(--text-secondary);
}
.detail-value {
font-weight: 500;
color: var(--text-color);
}
.commander-section {
background: #F9FAFB;
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.commander-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-color);
margin: 0 0 0.75rem 0;
}
.commander-name {
font-weight: 600;
color: var(--text-color);
margin: 0 0 0.5rem 0;
}
.commander-detail {
font-size: 0.85rem;
color: var(--text-secondary);
margin: 0.25rem 0;
}
.unit-actions {
display: flex;
gap: 0.5rem;
}
.btn-sm {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.btn-danger {
background: #DC2626;
color: white;
border: 1px solid #B91C1C;
}
.btn-danger:hover {
background: #B91C1C;
}
/* Modal Stilleri */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.modal {
background: white;
border-radius: 12px;
max-width: 500px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
}
.modal-large {
max-width: 700px;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid var(--card-border-color);
}
.modal-header h2 {
margin: 0;
font-size: 1.3rem;
font-weight: 600;
color: var(--text-color);
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
color: var(--text-secondary);
cursor: pointer;
padding: 0.25rem;
}
.modal-form {
padding: 1.5rem;
}
.form-section {
margin-bottom: 2rem;
}
.form-section h3 {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-color);
margin: 0 0 1rem 0;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--card-border-color);
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--text-color);
}
.form-row {
display: flex;
gap: 1rem;
}
.form-row .form-group {
flex: 1;
}
.modal-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 2rem;
padding-top: 1.5rem;
border-top: 1px solid var(--card-border-color);
}
/* Responsive Tasarım */
@media (max-width: 768px) {
.units-page {
padding: 1rem;
}
.page-header {
flex-direction: column;
align-items: stretch;
gap: 1rem;
}
.header-left {
flex-direction: column;
gap: 0.5rem;
}
.page-title {
font-size: 1.5rem;
}
.units-grid {
grid-template-columns: 1fr;
}
.unit-actions {
justify-content: stretch;
}
.modal {
margin: 0;
max-height: 100vh;
}
.modal-large {
max-width: 100%;
}
.form-row {
flex-direction: column;
gap: 0;
}
.modal-actions {
flex-direction: column;
}
.detail-item {
flex-direction: column;
gap: 0.25rem;
}
}
</style>