File: /home/muratemr/theotto.tr/admin/staff.php
<?php
/**
* admin/staff.php
* Personel Takip – 4 sekme
*/
declare(strict_types=1);
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/../includes/auth.php';
require_once __DIR__ . '/../includes/csrf.php';
require_once __DIR__ . '/../includes/functions.php';
require_permission('staff');
$page_title = 'Personel Takip';
$active_menu = 'staff';
// Aktif sekme
$tab = get_str('tab');
if (!in_array($tab, ['personeller','vardiya','devam','aylik_devam','ozet','aylik_detay'])) $tab = 'personeller';
// Filtre değerleri
$filter_date = get_str('filter_date') ?: date('Y-m-d');
$filter_staff = get_int('filter_staff');
$filter_status = get_str('filter_status');
$filter_month = get_int('filter_month') ?: (int)date('m');
$filter_year = get_int('filter_year') ?: (int)date('Y');
// Tüm aktif personel listesi (filtreler için)
$all_staff = db_rows("SELECT id, full_name FROM staff ORDER BY full_name");
require_once __DIR__ . '/../includes/admin_layout.php';
?>
<div class="d-flex justify-content-between align-items-center mb-4">
<h4 class="mb-0"><i class="bi bi-people-fill me-2 text-warning"></i>Personel Takip</h4>
</div>
<!-- SEKMELER -->
<ul class="nav nav-tabs mb-4" id="staffTabs">
<?php
$tabs = [
'personeller' => ['icon'=>'bi-person-badge', 'label'=>'Personeller'],
'vardiya' => ['icon'=>'bi-calendar-week', 'label'=>'Vardiya Planı'],
'devam' => ['icon'=>'bi-clock-history', 'label'=>'Günlük Devam'],
'aylik_devam' => ['icon'=>'bi-calendar-check', 'label'=>'Aylık Devam'],
'ozet' => ['icon'=>'bi-bar-chart-line', 'label'=>'Aylık Özet'],
'aylik_detay' => ['icon'=>'bi-calendar3', 'label'=>'Aylık Detay'],
];
foreach ($tabs as $key => $t): ?>
<li class="nav-item">
<a class="nav-link <?= $tab === $key ? 'active' : '' ?>"
href="?tab=<?= $key ?>">
<i class="<?= $t['icon'] ?> me-1"></i><?= $t['label'] ?>
</a>
</li>
<?php endforeach; ?>
</ul>
<!-- ================================================================ -->
<!-- SEKME: PERSONELLER -->
<!-- ================================================================ -->
<?php if ($tab === 'personeller'): ?>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center flex-wrap gap-2">
<span><i class="bi bi-person-badge me-2"></i>Personel Listesi</span>
<div class="d-flex gap-2 align-items-center flex-wrap">
<?php
$show_passive = get_str('show_passive') === '1';
$sort_by = get_str('sort_by') ?: 'sort_order';
$sort_dir = get_str('sort_dir') ?: 'asc';
$next_dir = $sort_dir === 'asc' ? 'desc' : 'asc';
$passive_count = (int)db_value("SELECT COUNT(*) FROM staff WHERE is_active=0");
?>
<a href="?tab=personeller&show_passive=<?= $show_passive?'0':'1' ?>"
class="btn btn-sm <?= $show_passive?'btn-success':'btn-outline-info' ?>">
<?php if ($show_passive): ?>
<i class="bi bi-person-check me-1"></i>Aktif Personeller
<?php else: ?>
<i class="bi bi-person-slash me-1"></i>Pasif Personel
<?php if ($passive_count > 0): ?>
<span class="badge bg-dark ms-1"><?= $passive_count ?></span>
<?php endif; ?>
<?php endif; ?>
</a>
<a href="?tab=personeller&sort_by=full_name&sort_dir=<?= $sort_by==='full_name'?$next_dir:'asc' ?>&show_passive=<?= $show_passive?'1':'0' ?>"
class="btn btn-sm <?= $sort_by==='full_name'?'btn-warning':'btn-outline-secondary' ?>">
<i class="bi bi-sort-alpha-<?= ($sort_by==='full_name'&&$sort_dir==='desc')?'down':'up' ?> me-1"></i>Ad Soyad
</a>
<a href="?tab=personeller&sort_by=department&sort_dir=<?= $sort_by==='department'?$next_dir:'asc' ?>&show_passive=<?= $show_passive?'1':'0' ?>"
class="btn btn-sm <?= $sort_by==='department'?'btn-warning':'btn-outline-secondary' ?>">
<i class="bi bi-sort-alpha-<?= ($sort_by==='department'&&$sort_dir==='desc')?'down':'up' ?> me-1"></i>Departman
</a>
<?php if (!$show_passive && (is_admin() || has_permission('staff'))): ?>
<button class="btn btn-warning btn-sm" onclick="openStaffModal()">
<i class="bi bi-plus-lg me-1"></i>Yeni Personel
</button>
<?php endif; ?>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0">
<thead>
<tr>
<th>Ad Soyad</th>
<th>Telefon</th>
<th>Departman</th>
<th>Pozisyon</th>
<th>Durum</th>
<th>İşlemler</th>
<?php if ($has_sort_col): ?>
<th style="width:55px;color:rgba(255,255,255,0.5);font-size:11px;text-align:center">Per.No</th>
<?php endif; ?>
</tr>
</thead>
<tbody id="staffTableBody">
<?php
// department kolonu var mı kontrol et
$has_dept_col = (bool)db_value(
"SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'staff'
AND COLUMN_NAME = 'department'"
);
$dept_col_sql = $has_dept_col
? "COALESCE(department,'salon') AS department"
: "'salon' AS department";
$has_sort_col = (bool)db_value(
"SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='staff' AND COLUMN_NAME='sort_order'"
);
$allowed_sorts = ['full_name', 'department', 'sort_order'];
$sort_by = in_array(get_str('sort_by'), $allowed_sorts) ? get_str('sort_by') : ($has_sort_col ? 'sort_order' : 'full_name');
$sort_dir = get_str('sort_dir') === 'desc' ? 'DESC' : 'ASC';
$active_filter = $show_passive ? '0' : '1';
$sort_sql = $has_sort_col
? "COALESCE(department,'salon') ASC, sort_order ASC, full_name ASC"
: "COALESCE(department,'salon') ASC, full_name ASC";
if ($sort_by !== 'sort_order') {
$sort_sql = "{$sort_by} {$sort_dir}";
}
$staff_list = db_rows("SELECT *, {$dept_col_sql}" . ($has_sort_col ? ", sort_order" : ", 0 AS sort_order") . " FROM staff WHERE is_active=? ORDER BY {$sort_sql}", [$active_filter]);
foreach ($staff_list as $s):
?>
<tr>
<td><?= e($s['full_name']) ?></td>
<td><?= e($s['phone'] ?? '-') ?></td>
<td>
<?php
$dept_val = $s['department'] ?? 'salon';
$dept_lbl = $dept_val === 'mutfak' ? 'Mutfak' : 'Salon';
$dept_cls = $dept_val === 'mutfak' ? 'bg-danger' : 'bg-info text-dark';
?>
<span class="badge <?= $dept_cls ?>"><?= $dept_lbl ?></span>
</td>
<td><?= e($s['position'] ?? '-') ?></td>
<td>
<?php if ($s['is_active']): ?>
<span class="badge bg-success">Aktif</span>
<?php else: ?>
<span class="badge bg-secondary">Pasif</span>
<?php endif; ?>
</td>
<td>
<a href="staff_edit.php?id=<?= $s['id'] ?>"
class="btn btn-outline-warning btn-sm"
title="Düzenle">
<i class="bi bi-pencil"></i>
</a>
<?php if ($show_passive): ?>
<button class="btn btn-outline-success btn-sm"
onclick="toggleStaffStatus(<?= $s['id'] ?>, 0)"
title="Aktifleştir">
<i class="bi bi-person-check me-1"></i>Aktif Yap
</button>
<?php else: ?>
<button class="btn btn-outline-secondary btn-sm"
onclick="toggleStaffStatus(<?= $s['id'] ?>, 1)"
title="Pasifleştir">
<i class="bi bi-person-x"></i>
</button>
<?php endif; ?>
<?php if (is_admin()): ?>
<button class="btn btn-outline-danger btn-sm"
onclick="deleteStaff(<?= $s['id'] ?>, '<?= e($s['full_name']) ?>')"
title="Sil">
<i class="bi bi-trash"></i>
</button>
<?php endif; ?>
</td>
<?php if ($has_sort_col): ?>
<td style="text-align:center;color:rgba(255,255,255,0.4);font-size:11px;font-weight:500">
<?= (int)($s['sort_order'] ?? 0) ?>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- ================================================================ -->
<!-- SEKME: VARDİYA PLANI - HAFTALIK MATRİS -->
<!-- ================================================================ -->
<?php elseif ($tab === 'vardiya'):
// Haftanın başlangıç gününü hesapla (Pazartesi)
$week_start_raw = get_str('week_start');
if ($week_start_raw && preg_match('/^\d{4}-\d{2}-\d{2}$/', $week_start_raw)) {
$week_start_dt = new DateTime($week_start_raw);
} else {
$week_start_dt = new DateTime($filter_date);
}
// Pazartesiye getir
$dow = (int)$week_start_dt->format('N'); // 1=Pzt, 7=Paz
$week_start_dt->modify('-' . ($dow - 1) . ' days');
$week_start = $week_start_dt->format('Y-m-d');
// Haftanın 7 günü
$week_days = [];
for ($i = 0; $i < 7; $i++) {
$d = clone $week_start_dt;
$d->modify("+$i days");
$week_days[] = $d->format('Y-m-d');
}
$week_end = $week_days[6];
$day_labels = ['Pazartesi','Salı','Çarşamba','Perşembe','Cuma','Cumartesi','Pazar'];
// Departman filtresi (salon/mutfak)
$dept_filter = get_str('dept') ?: 'salon';
// Personeli departmana göre grupla (position'a bakarak)
// department kolonu ile sınıflandır (yoksa position'a göre fallback)
$has_dept_col2 = isset($has_dept_col) ? $has_dept_col : (bool)db_value(
"SELECT COUNT(*) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME='staff' AND COLUMN_NAME='department'"
);
$dept_sql2 = $has_dept_col2 ? "COALESCE(department,'salon') AS department" : "'salon' AS department";
$all_staff_full = db_rows("SELECT id, full_name, position, is_active,
{$dept_sql2}
FROM staff WHERE is_active=1 ORDER BY full_name");
$salon_staff = [];
$mutfak_staff = [];
foreach ($all_staff_full as $s) {
if (($s['department'] ?? 'salon') === 'mutfak') {
$mutfak_staff[] = $s;
} else {
$salon_staff[] = $s;
}
}
$current_staff_list = ($dept_filter === 'mutfak') ? $mutfak_staff : $salon_staff;
$staff_ids = array_column($current_staff_list, 'id');
// Haftanın vardiyalarını tek sorguda çek
$shifts_raw = [];
if (!empty($staff_ids)) {
$placeholders = implode(',', array_fill(0, count($staff_ids), '?'));
$shifts_raw = db_rows(
"SELECT ss.*
FROM staff_shifts ss
WHERE ss.staff_id IN ($placeholders)
AND ss.date BETWEEN ? AND ?
ORDER BY ss.staff_id, ss.date",
array_merge($staff_ids, [$week_start, $week_end])
);
}
// [staff_id][date] => shift
$shift_map = [];
foreach ($shifts_raw as $sh) {
$shift_map[$sh['staff_id']][$sh['date']] = $sh;
}
$leave_short = ['annual'=>'YIL. İZİN','sick'=>'RAPOR','unpaid'=>'ÜCRETSİZ İ.','excuse'=>'MAZERET','weekly'=>'HAFTALIK İZİN','none'=>'OFF'];
$leave_color = ['annual'=>'#28a745','sick'=>'#dc3545','unpaid'=>'#6f42c1','excuse'=>'#17a2b8','weekly'=>'#fd7e14','none'=>'#ffc107'];
// Önceki/sonraki hafta
$prev_week = (clone $week_start_dt)->modify('-7 days')->format('Y-m-d');
$next_week = (clone $week_start_dt)->modify('+7 days')->format('Y-m-d');
$base_url = "?tab=vardiya&dept=$dept_filter";
?>
<!-- Hafta navigasyonu + departman seçimi -->
<div class="d-flex align-items-center justify-content-between mb-3 flex-wrap gap-2">
<div class="d-flex gap-2 align-items-center">
<a href="<?= $base_url ?>&week_start=<?= $prev_week ?>" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-chevron-left"></i> Önceki Hafta
</a>
<input type="date" class="form-control form-control-sm" style="width:160px"
value="<?= $week_start ?>"
onchange="location.href='<?= $base_url ?>&week_start='+this.value">
<a href="<?= $base_url ?>&week_start=<?= $next_week ?>" class="btn btn-outline-secondary btn-sm">
Sonraki Hafta <i class="bi bi-chevron-right"></i>
</a>
<a href="<?= $base_url ?>&week_start=<?= date('Y-m-d') ?>" class="btn btn-gold btn-sm">
Bu Hafta
</a>
</div>
<div class="d-flex gap-2 align-items-center">
<!-- Salon / Mutfak seçimi -->
<div class="btn-group btn-group-sm">
<a href="?tab=vardiya&dept=salon&week_start=<?= $week_start ?>"
class="btn <?= $dept_filter==='salon' ? 'btn-warning' : 'btn-outline-secondary' ?>">
<i class="bi bi-cup-straw me-1"></i>Salon
</a>
<a href="?tab=vardiya&dept=mutfak&week_start=<?= $week_start ?>"
class="btn <?= $dept_filter==='mutfak' ? 'btn-warning' : 'btn-outline-secondary' ?>">
<i class="bi bi-fire me-1"></i>Mutfak
</a>
</div>
<button class="btn btn-warning btn-sm" onclick="openShiftModal()">
<i class="bi bi-plus-lg me-1"></i>Vardiya Ekle
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="openCopyShiftModal()">
<i class="bi bi-copy me-1"></i>Kopyala
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="printWeeklyShift()">
<i class="bi bi-file-earmark-pdf me-1"></i>PDF
</button>
</div>
</div>
<!-- Haftalık Matris Tablosu -->
<div class="card">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table mb-0" id="weeklyShiftTable" style="min-width:900px">
<thead>
<tr style="background:#111">
<th style="min-width:130px;background:#1a1a1a;position:sticky;left:0;z-index:2">
<div style="font-size:11px;color:var(--text-muted)">Ad Soyad</div>
</th>
<th style="min-width:80px;background:#1a1a1a;position:sticky;left:0;z-index:2">
<div style="font-size:11px;color:var(--text-muted)">Görev</div>
</th>
<?php foreach ($week_days as $i => $day):
$is_today = ($day === date('Y-m-d'));
$is_weekend = ($i >= 5);
$day_dt = new DateTime($day);
?>
<th class="text-center" style="background:<?= $is_today ? 'rgba(201,168,76,0.2)' : ($is_weekend ? 'rgba(255,255,255,0.04)' : '#111') ?>">
<div style="font-size:12px;font-weight:600;color:<?= $is_today ? 'var(--gold)' : 'var(--text-primary)' ?>">
<?= $day_labels[$i] ?>
</div>
<div style="font-size:11px;color:<?= $is_today ? 'var(--gold)' : 'var(--text-muted)' ?>">
<?= $day_dt->format('d.m.Y') ?>
</div>
</th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php if (empty($current_staff_list)): ?>
<tr>
<td colspan="<?= 2 + count($week_days) ?>" class="text-center py-5" style="color:rgba(255,255,255,0.55)">
<i class="bi bi-people me-2"></i>
Bu departmanda aktif personel bulunamadı.<br>
<small>Personel eklerken pozisyon bilgisini doldurduğunuzdan emin olun.</small>
</td>
</tr>
<?php else: ?>
<?php foreach ($current_staff_list as $s): ?>
<tr style="border-bottom:1px solid var(--dark-border)">
<td style="position:sticky;left:0;background:var(--dark-card);z-index:1;font-weight:600;font-size:13px;white-space:nowrap;color:var(--text-primary)">
<?= e($s['full_name']) ?>
</td>
<td style="position:sticky;left:0;background:var(--dark-card);z-index:1;font-size:11px;color:rgba(255,255,255,0.6);white-space:nowrap">
<?= e(strtoupper($s['position'] ?? '—')) ?>
</td>
<?php foreach ($week_days as $day):
$sh = $shift_map[$s['id']][$day] ?? null;
?>
<td class="text-center p-1" style="min-width:110px">
<?php if (!$sh): ?>
<!-- Vardiya yok - ekle butonu -->
<button class="btn btn-link p-0 w-100 h-100" style="min-height:44px;opacity:0.3;color:var(--text-muted)"
onclick="openShiftModalForCell(<?= $s['id'] ?>, '<?= $day ?>')"
title="Vardiya Ekle">
<i class="bi bi-plus-circle fs-5"></i>
</button>
<?php elseif ($sh['is_work_day']): ?>
<!-- Çalışma günü -->
<?php
$start = $sh['planned_start_time'] ? substr($sh['planned_start_time'],0,5) : '';
$end = $sh['planned_end_time'] ? substr($sh['planned_end_time'],0,5) : '';
$label = ($start && $end) ? "$start/$end" : 'Vardiya';
$extra = $sh['is_next_day'] ? '<sup style="font-size:9px">+1</sup>' : '';
?>
<div class="shift-cell working" title="Düzenlemek için tıkla">
<div onclick="editShift(<?= htmlspecialchars(json_encode($sh), ENT_QUOTES) ?>)"
style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center">
<span style="font-size:12px;font-weight:700;color:var(--gold);letter-spacing:0.3px"><?= $label ?><?= $extra ?></span>
<?php if ($sh['shift_note']): ?>
<div style="font-size:10px;color:rgba(255,255,255,0.55);margin-top:2px"><?= e(mb_strimwidth($sh['shift_note'],0,15,'..')) ?></div>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<!-- İzin günü -->
<?php
$lt = $sh['leave_type'] ?? 'none';
$lbl = $leave_short[$lt] ?? 'OFF';
$lclr = $leave_color[$lt] ?? '#ffc107';
$tclr = ($lt === 'none') ? '#111' : '#fff';
?>
<div class="shift-cell leave"
style="background:<?= $lclr ?>;color:<?= $tclr ?>"
title="Düzenlemek için tıkla">
<div onclick="editShift(<?= htmlspecialchars(json_encode($sh), ENT_QUOTES) ?>)"
style="flex:1;display:flex;align-items:center;justify-content:center">
<span style="font-size:11px;font-weight:700"><?= $lbl ?></span>
</div>
</div>
<?php endif; ?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Renk Açıklaması -->
<div class="d-flex gap-3 mt-2 flex-wrap" style="font-size:11px;color:var(--text-muted)">
<span><span style="display:inline-block;width:14px;height:14px;background:#1a6b35;border-radius:3px;margin-right:4px;vertical-align:middle"></span>Çalışma</span>
<span><span style="display:inline-block;width:14px;height:14px;background:#fd7e14;border-radius:3px;margin-right:4px;vertical-align:middle"></span>Haftalık İzin</span>
<span><span style="display:inline-block;width:14px;height:14px;background:#28a745;border-radius:3px;margin-right:4px;vertical-align:middle"></span>Yıllık İzin</span>
<span><span style="display:inline-block;width:14px;height:14px;background:#dc3545;border-radius:3px;margin-right:4px;vertical-align:middle"></span>Rapor</span>
<span><span style="display:inline-block;width:14px;height:14px;background:#6f42c1;border-radius:3px;margin-right:4px;vertical-align:middle"></span>Ücretsiz İzin</span>
<span><span style="display:inline-block;width:14px;height:14px;background:#17a2b8;border-radius:3px;margin-right:4px;vertical-align:middle"></span>Mazeret</span>
</div>
<!-- ================================================================ -->
<!-- SEKME: GÜNLÜK DEVAM -->
<!-- ================================================================ -->
<?php elseif ($tab === 'devam'): ?>
<div class="card mb-3">
<div class="card-header">
<div class="row g-2 align-items-end">
<div class="col-md-3">
<label class="form-label small">Tarih</label>
<input type="date" class="form-control form-control-sm" id="attFilterDate"
value="<?= e($filter_date) ?>"
onchange="location.href='?tab=devam&filter_date='+this.value+'&filter_staff=<?= $filter_staff ?>&filter_status=<?= urlencode($filter_status) ?>'">
</div>
<div class="col-md-3">
<label class="form-label small">Personel</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=devam&filter_date=<?= urlencode($filter_date) ?>&filter_staff='+this.value+'&filter_status=<?= urlencode($filter_status) ?>'">
<option value="">Tüm Personeller</option>
<?php foreach ($all_staff as $ps): ?>
<option value="<?= $ps['id'] ?>" <?= $filter_staff == $ps['id'] ? 'selected' : '' ?>>
<?= e($ps['full_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">Durum</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=devam&filter_date=<?= urlencode($filter_date) ?>&filter_staff=<?= $filter_staff ?>&filter_status='+this.value">
<option value="">Tüm Durumlar</option>
<?php foreach (['pending'=>'Bekliyor','present'=>'Geldi','absent'=>'Gelmedi','late'=>'Geç','early_leave'=>'Erken Çıktı','incomplete'=>'Eksik'] as $v => $l): ?>
<option value="<?= $v ?>" <?= $filter_status === $v ? 'selected' : '' ?>><?= $l ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-auto ms-auto d-flex gap-2">
<button class="btn btn-success btn-sm" id="bulkPresentBtn" onclick="bulkMarkPresent()" disabled>
<i class="bi bi-check-all me-1"></i>Seçilenleri Geldi İşaretle
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="printTable('devamTable','Günlük Devam – <?= $filter_date ?>')">
<i class="bi bi-file-earmark-pdf me-1"></i>PDF
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-dark table-hover mb-0" id="devamTable">
<thead>
<tr>
<th><input type="checkbox" id="selectAll" class="form-check-input"></th>
<th>Personel</th>
<th>Plan. Başlangıç</th>
<th>Plan. Bitiş</th>
<th>Gerçek Giriş</th>
<th>Gerçek Çıkış</th>
<th>Durum</th>
<th>Geç (dk)</th>
<th>Erken (dk)</th>
<th>Eksik (dk)</th>
<th>Not</th>
<th>İşlemler</th>
</tr>
</thead>
<tbody>
<?php
$att_sql = "SELECT sa.*, ss.planned_start_time, ss.planned_end_time, ss.is_next_day,
s.full_name AS staff_name
FROM staff_attendance sa
JOIN staff_shifts ss ON ss.id = sa.shift_id
JOIN staff s ON s.id = sa.staff_id
WHERE 1=1";
$att_params = [];
if ($filter_date) { $att_sql .= " AND sa.date = ?"; $att_params[] = $filter_date; }
if ($filter_staff) { $att_sql .= " AND sa.staff_id = ?"; $att_params[] = $filter_staff; }
if ($filter_status) { $att_sql .= " AND sa.status = ?"; $att_params[] = $filter_status; }
$att_sql .= " ORDER BY s.full_name";
$attendances = db_rows($att_sql, $att_params);
$status_map = [
'pending' => ['label'=>'Bekliyor', 'class'=>'secondary'],
'present' => ['label'=>'Geldi', 'class'=>'success'],
'absent' => ['label'=>'Gelmedi', 'class'=>'danger'],
'late' => ['label'=>'Geç', 'class'=>'warning'],
'early_leave' => ['label'=>'Erken Çıktı', 'class'=>'info'],
'incomplete' => ['label'=>'Eksik', 'class'=>'orange'],
];
foreach ($attendances as $att):
$sm = $status_map[$att['status']] ?? ['label'=>$att['status'],'class'=>'secondary'];
?>
<tr>
<td>
<input type="checkbox" class="form-check-input att-checkbox"
value="<?= $att['id'] ?>"
data-shift-id="<?= $att['shift_id'] ?>"
onchange="updateBulkBtn()">
</td>
<td><?= e($att['staff_name']) ?></td>
<td><?= $att['planned_start_time'] ? format_time($att['planned_start_time']) : '-' ?></td>
<td>
<?= $att['planned_end_time'] ? format_time($att['planned_end_time']) : '-' ?>
<?= $att['is_next_day'] ? '<small class="text-info">(+1)</small>' : '' ?>
</td>
<td><?= $att['actual_start_time'] ? format_time($att['actual_start_time']) : '-' ?></td>
<td><?= $att['actual_end_time'] ? format_time($att['actual_end_time']) : '-' ?></td>
<td><span class="badge bg-<?= $sm['class'] ?>"><?= $sm['label'] ?></span></td>
<td><?= $att['late_minutes'] ?: '-' ?></td>
<td><?= $att['early_leave_minutes'] ?: '-' ?></td>
<td><?= $att['missing_minutes'] ?: '-' ?></td>
<td class="text-truncate" style="max-width:120px" title="<?= e($att['manual_note'] ?? '') ?>">
<?= e($att['manual_note'] ?? ($att['auto_note'] ?? '-')) ?>
</td>
<td>
<button class="btn btn-outline-warning btn-sm"
onclick="openAttModal(<?= htmlspecialchars(json_encode($att), ENT_QUOTES) ?>)"
title="Güncelle"><i class="bi bi-pencil"></i></button>
</td>
<?php if ($has_sort_col): ?>
<td style="text-align:center;color:rgba(255,255,255,0.4);font-size:11px;font-weight:500">
<?= (int)($s['sort_order'] ?? 0) ?>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
<?php if (empty($attendances)): ?>
<tr><td colspan="12" class="text-center text-muted py-3">Kayıt bulunamadı.</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- ================================================================ -->
<!-- SEKME: AYLIK DEVAM -->
<!-- ================================================================ -->
<?php elseif ($tab === 'aylik_devam'):
// Filtre
$ad_staff = get_int('filter_staff');
$ad_month = get_int('filter_month') ?: (int)date('m');
$ad_year = get_int('filter_year') ?: (int)date('Y');
$ad_start = sprintf('%04d-%02d-01', $ad_year, $ad_month);
$ad_end = date('Y-m-t', strtotime($ad_start));
// Personel seçilmişse tüm vardiya+devam verisini çek
$ad_shifts = [];
$ad_staff_row = null;
if ($ad_staff) {
$ad_staff_row = db_row("SELECT * FROM staff WHERE id = ?", [$ad_staff]);
$ad_shifts = db_rows(
"SELECT ss.id AS shift_id, ss.date, ss.is_work_day, ss.leave_type,
ss.planned_start_time, ss.planned_end_time, ss.is_next_day, ss.shift_note,
sa.id AS att_id, sa.status, sa.actual_start_time, sa.actual_end_time,
sa.late_minutes, sa.early_leave_minutes, sa.missing_minutes,
sa.manual_note, sa.auto_note
FROM staff_shifts ss
LEFT JOIN staff_attendance sa ON sa.shift_id = ss.id
WHERE ss.staff_id = ? AND ss.date BETWEEN ? AND ?
ORDER BY ss.date ASC",
[$ad_staff, $ad_start, $ad_end]
);
}
$status_map_ad = [
'pending' => ['l'=>'Bekliyor', 'c'=>'secondary'],
'present' => ['l'=>'Geldi', 'c'=>'success'],
'absent' => ['l'=>'Gelmedi', 'c'=>'danger'],
'late' => ['l'=>'Geç', 'c'=>'warning'],
'early_leave' => ['l'=>'Erken Çıktı', 'c'=>'info'],
'incomplete' => ['l'=>'Eksik', 'c'=>'orange'],
];
$leave_map_ad = ['none'=>'-','annual'=>'Yıllık İzin','sick'=>'Rapor','unpaid'=>'Ücretsiz İzin','excuse'=>'Mazeret','weekly'=>'Haftalık İzin'];
$day_tr = ['Pazartesi','Salı','Çarşamba','Perşembe','Cuma','Cumartesi','Pazar'];
?>
<div class="card mb-3">
<div class="card-header">
<div class="row g-2 align-items-end">
<div class="col-md-3">
<label class="form-label small">Personel <span class="text-danger">*</span></label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=aylik_devam&filter_staff='+this.value+'&filter_month=<?= $ad_month ?>&filter_year=<?= $ad_year ?>'">
<option value="">-- Personel Seçin --</option>
<?php foreach ($all_staff as $ps): ?>
<option value="<?= $ps['id'] ?>" <?= $ad_staff==$ps['id']?'selected':'' ?>>
<?= e($ps['full_name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">Ay</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=aylik_devam&filter_staff=<?= $ad_staff ?>&filter_month='+this.value+'&filter_year=<?= $ad_year ?>'">
<?php foreach (month_names_tr() as $i => $mn): ?>
<option value="<?= $i+1 ?>" <?= $ad_month==$i+1?'selected':'' ?>><?= $mn ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">Yıl</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=aylik_devam&filter_staff=<?= $ad_staff ?>&filter_month=<?= $ad_month ?>&filter_year='+this.value">
<?php for ($y=date('Y')-2;$y<=date('Y')+1;$y++): ?>
<option value="<?= $y ?>" <?= $ad_year==$y?'selected':'' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
<?php if ($ad_staff && !empty($ad_shifts)): ?>
<div class="col-md-auto ms-auto">
<button class="btn btn-outline-secondary btn-sm" onclick="printTable('aylikDevamTable','Aylık Devam – <?= e($ad_staff_row['full_name'] ?? '') ?> – <?= month_names_tr()[$ad_month-1] ?> <?= $ad_year ?>')">
<i class="bi bi-file-earmark-pdf me-1"></i>PDF
</button>
</div>
<?php endif; ?>
</div>
</div>
<div class="card-body p-0">
<?php if (!$ad_staff): ?>
<div class="text-center py-5" style="color:rgba(255,255,255,0.55)">
<i class="bi bi-person-circle mb-2 d-block" style="font-size:40px;color:rgba(255,255,255,0.4)"></i>
Görüntülemek için personel seçin
</div>
<?php elseif (empty($ad_shifts)): ?>
<div class="text-center py-5" style="color:rgba(255,255,255,0.55)">
<i class="bi bi-calendar-x mb-2 d-block" style="font-size:40px;color:rgba(255,255,255,0.4)"></i>
Bu ay için vardiya kaydı bulunamadı
</div>
<?php else: ?>
<!-- Personel başlığı + özet -->
<?php
$ad_cnt = ['planned'=>0,'present'=>0,'absent'=>0,'late'=>0,'early_leave'=>0,'incomplete'=>0,'leave'=>0];
foreach ($ad_shifts as $sh) {
if ($sh['is_work_day']) $ad_cnt['planned']++;
else $ad_cnt['leave']++;
$st = $sh['status'] ?? '';
if ($st === 'present') $ad_cnt['present']++;
elseif ($st === 'absent') $ad_cnt['absent']++;
elseif ($st === 'late') $ad_cnt['late']++;
elseif ($st === 'early_leave') $ad_cnt['early_leave']++;
elseif ($st === 'incomplete') $ad_cnt['incomplete']++;
}
?>
<div class="d-flex align-items-center gap-3 p-3 border-bottom border-secondary flex-wrap">
<div>
<div class="fw-bold text-warning" style="font-size:15px"><?= e($ad_staff_row['full_name']) ?></div>
<div class="text-muted small"><?= month_names_tr()[$ad_month-1] ?> <?= $ad_year ?></div>
</div>
<div class="d-flex gap-2 flex-wrap ms-auto">
<span class="badge bg-info">Plan: <?= $ad_cnt['planned'] ?></span>
<span class="badge bg-success">Geldi: <?= $ad_cnt['present'] ?></span>
<span class="badge bg-danger">Gelmedi: <?= $ad_cnt['absent'] ?></span>
<span class="badge bg-warning text-dark">Geç: <?= $ad_cnt['late'] ?></span>
<span class="badge bg-secondary">İzin: <?= $ad_cnt['leave'] ?></span>
</div>
</div>
<div class="table-responsive">
<table class="table table-dark table-hover mb-0" id="aylikDevamTable">
<thead>
<tr>
<th style="min-width:110px">Tarih</th>
<th>Gün</th>
<th>Vardiya</th>
<th>Durum/İzin</th>
<th>Gerçek Giriş</th>
<th>Gerçek Çıkış</th>
<th>Geç (dk)</th>
<th>Erken (dk)</th>
<th>Not</th>
<th>İşlem</th>
</tr>
</thead>
<tbody>
<?php foreach ($ad_shifts as $sh):
$dow = (int)(new DateTime($sh['date']))->format('N') - 1;
$is_weekend = $dow >= 5;
$is_today = $sh['date'] === date('Y-m-d');
$row_bg = $is_today ? 'rgba(201,168,76,0.08)' : ($is_weekend ? 'rgba(255,255,255,0.02)' : 'transparent');
?>
<tr style="background:<?= $row_bg ?>">
<td>
<span class="<?= $is_today?'text-gold fw-bold':'' ?>"><?= format_date($sh['date']) ?></span>
</td>
<td class="text-muted small"><?= $day_tr[$dow] ?></td>
<td>
<?php if ($sh['is_work_day']): ?>
<span class="text-success" style="font-size:13px">
<?= $sh['planned_start_time'] ? substr($sh['planned_start_time'],0,5).' – '.substr($sh['planned_end_time'],0,5) : '—' ?>
<?= $sh['is_next_day'] ? '<sup class="text-info">+1</sup>' : '' ?>
</span>
<?php else: ?>
<span class="text-muted small"><?= $leave_map_ad[$sh['leave_type']] ?? '—' ?></span>
<?php endif; ?>
</td>
<td>
<?php if ($sh['is_work_day']): ?>
<?php if ($sh['att_id']): $sm = $status_map_ad[$sh['status']] ?? ['l'=>$sh['status'],'c'=>'secondary']; ?>
<span class="badge bg-<?= $sm['c'] ?>"><?= $sm['l'] ?></span>
<?php else: ?>
<span class="badge bg-secondary">Bekliyor</span>
<?php endif; ?>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<td class="<?= (!$sh['is_work_day'])?'text-muted':'' ?>">
<?= $sh['actual_start_time'] ? substr($sh['actual_start_time'],0,5) : '—' ?>
</td>
<td class="<?= (!$sh['is_work_day'])?'text-muted':'' ?>">
<?= $sh['actual_end_time'] ? substr($sh['actual_end_time'],0,5) : '—' ?>
</td>
<td><?= $sh['late_minutes'] ? '<span class="text-warning">'.$sh['late_minutes'].'</span>' : '—' ?></td>
<td><?= $sh['early_leave_minutes'] ? '<span class="text-info">'.$sh['early_leave_minutes'].'</span>' : '—' ?></td>
<td class="text-muted small" style="max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap"
title="<?= e($sh['manual_note'] ?? ($sh['auto_note'] ?? '')) ?>">
<?= e(mb_strimwidth($sh['manual_note'] ?? ($sh['auto_note'] ?? ''), 0, 20, '..')) ?>
</td>
<td>
<?php if ($sh['is_work_day'] && $sh['att_id']): ?>
<button class="btn btn-outline-warning btn-sm py-0 px-2"
onclick="openAttModal(<?= htmlspecialchars(json_encode([
'id' => $sh['att_id'],
'shift_id' => $sh['shift_id'],
'status' => $sh['status'] ?? 'pending',
'actual_start_time' => $sh['actual_start_time'] ?? '',
'actual_end_time' => $sh['actual_end_time'] ?? '',
'manual_note' => $sh['manual_note'] ?? '',
'planned_start_time' => $sh['planned_start_time'] ?? '',
'planned_end_time' => $sh['planned_end_time'] ?? '',
]), ENT_QUOTES) ?>)"
title="Devamı Düzenle">
<i class="bi bi-pencil" style="font-size:11px"></i>
</button>
<?php elseif ($sh['is_work_day'] && !$sh['att_id']): ?>
<span class="text-muted small">Devam yok</span>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<?php if ($has_sort_col): ?>
<td style="text-align:center;color:rgba(255,255,255,0.4);font-size:11px;font-weight:500">
<?= (int)($s['sort_order'] ?? 0) ?>
</td>
<?php endif; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<!-- ================================================================ -->
<!-- SEKME: AYLIK ÖZET -->
<!-- ================================================================ -->
<?php elseif ($tab === 'ozet'): ?>
<div class="card mb-3">
<div class="card-header">
<div class="row g-2 align-items-end">
<div class="col-md-2">
<label class="form-label small">Ay</label>
<select class="form-select form-select-sm" id="ozetMonth"
onchange="location.href='?tab=ozet&filter_month='+this.value+'&filter_year=<?= $filter_year ?>&filter_staff=<?= $filter_staff ?>'">
<?php foreach (month_names_tr() as $i => $mn): ?>
<option value="<?= $i+1 ?>" <?= $filter_month == $i+1 ? 'selected' : '' ?>><?= $mn ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">Yıl</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=ozet&filter_month=<?= $filter_month ?>&filter_year='+this.value+'&filter_staff=<?= $filter_staff ?>'">
<?php for ($y = date('Y')-2; $y <= date('Y')+1; $y++): ?>
<option value="<?= $y ?>" <?= $filter_year == $y ? 'selected' : '' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Personel (Detay için)</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=ozet&filter_month=<?= $filter_month ?>&filter_year=<?= $filter_year ?>&filter_staff='+this.value">
<option value="">Tüm Personel</option>
<?php foreach ($all_staff as $ps): ?>
<option value="<?= $ps['id'] ?>" <?= $filter_staff == $ps['id'] ? 'selected' : '' ?>><?= e($ps['full_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="col-md-auto ms-auto d-flex gap-2 align-items-end">
<button class="btn btn-outline-secondary btn-sm" onclick="printTable('ozetTable','Aylık Özet – <?= month_names_tr()[$filter_month-1] ?> <?= $filter_year ?>')">
<i class="bi bi-file-earmark-pdf me-1"></i>PDF
</button>
</div>
</div>
<div class="card-body">
<?php
$month_start = sprintf('%04d-%02d-01', $filter_year, $filter_month);
$month_end = date('Y-m-t', strtotime($month_start));
// Genel özet tablosu
$summary_staff = $filter_staff ? db_rows("SELECT id, full_name FROM staff WHERE id = ?", [$filter_staff]) : db_rows("SELECT id, full_name FROM staff WHERE is_active = 1 ORDER BY full_name");
?>
<?php if ($filter_staff): ?>
<!-- Seçilen personel özet kartları -->
<?php
$sel_staff = db_row("SELECT * FROM staff WHERE id = ?", [$filter_staff]);
$shifts_month = db_rows(
"SELECT ss.*, sa.status, sa.late_minutes, sa.early_leave_minutes, sa.missing_minutes
FROM staff_shifts ss
LEFT JOIN staff_attendance sa ON sa.shift_id = ss.id
WHERE ss.staff_id = ? AND ss.date BETWEEN ? AND ?
ORDER BY ss.date",
[$filter_staff, $month_start, $month_end]
);
$counters = [
'planned'=>0,'present'=>0,'absent'=>0,'late'=>0,'early_leave'=>0,'incomplete'=>0,
'annual'=>0,'sick'=>0,'unpaid'=>0,'excuse'=>0,'weekly'=>0,
'total_late'=>0,'total_missing'=>0
];
foreach ($shifts_month as $sh) {
if ($sh['is_work_day']) $counters['planned']++;
$st = $sh['status'] ?? 'pending';
if ($st === 'present') $counters['present']++;
elseif ($st === 'absent') $counters['absent']++;
elseif ($st === 'late') { $counters['late']++; $counters['total_late'] += $sh['late_minutes']; }
elseif ($st === 'early_leave') $counters['early_leave']++;
elseif ($st === 'incomplete') $counters['incomplete']++;
if (!$sh['is_work_day'] && $sh['leave_type'] !== 'none') {
$counters[$sh['leave_type']] = ($counters[$sh['leave_type']] ?? 0) + 1;
}
$counters['total_missing'] += $sh['missing_minutes'] ?? 0;
}
?>
<h6 class="mb-3 text-warning"><?= e($sel_staff['full_name']) ?> – <?= month_names_tr()[$filter_month-1] ?> <?= $filter_year ?></h6>
<div class="row g-3 mb-4">
<?php
$cards = [
['Planlanan Gün', $counters['planned'], 'info'],
['Geldi', $counters['present'], 'success'],
['Gelmedi', $counters['absent'], 'danger'],
['Geç Geldi', $counters['late'], 'warning'],
['Erken Çıktı', $counters['early_leave'], 'orange'],
['Eksik Mesai', $counters['incomplete'], 'secondary'],
['Haftalık İzin', $counters['weekly'], 'warning'],
['Yıllık İzin', $counters['annual'], 'primary'],
['Raporlu', $counters['sick'], 'danger'],
['Ücretsiz İzin', $counters['unpaid'], 'secondary'],
['Mazeret', $counters['excuse'], 'info'],
['Toplam Geç (dk)', minutes_to_hm($counters['total_late']), 'warning'],
['Eksik Süre', minutes_to_hm($counters['total_missing']), 'danger'],
];
foreach ($cards as [$lbl,$val,$color]):
?>
<div class="col-6 col-md-3 col-xl-2">
<div class="card border-<?= $color ?> text-center p-2">
<div class="text-<?= $color ?> fw-bold fs-5"><?= $val ?></div>
<div class="small text-muted"><?= $lbl ?></div>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Günlük detay tablosu -->
<h6 class="mb-2">Günlük Detay</h6>
<div class="table-responsive">
<table class="table table-dark table-sm table-hover">
<thead>
<tr>
<th>Tarih</th><th>Plan. Saat</th><th>Gerçek Saat</th>
<th>Durum</th><th>İzin Türü</th>
<th>Geç (dk)</th><th>Eksik (dk)</th><th>Not</th>
</tr>
</thead>
<tbody>
<?php
$leave_labels2 = ['none'=>'-','annual'=>'Yıllık','sick'=>'Raporlu','unpaid'=>'Ücretsiz','excuse'=>'Mazeret','weekly'=>'Haftalık'];
$status_map2 = ['pending'=>'Bekliyor','present'=>'Geldi','absent'=>'Gelmedi','late'=>'Geç','early_leave'=>'Erken','incomplete'=>'Eksik'];
foreach ($shifts_month as $sh):
$st_label = $sh['status'] ? ($status_map2[$sh['status']] ?? $sh['status']) : '-';
?>
<tr>
<td><?= format_date($sh['date']) ?></td>
<td><?= $sh['planned_start_time'] ? format_time($sh['planned_start_time']).'–'.format_time($sh['planned_end_time']) : '-' ?></td>
<td>
<?php if ($sh['status'] && $sh['actual_start_time'] ?? false): ?>
<?= format_time($sh['actual_start_time'] ?? '') ?>–<?= format_time($sh['actual_end_time'] ?? '') ?>
<?php else: ?>-<?php endif; ?>
</td>
<td><?= $st_label ?></td>
<td><?= $leave_labels2[$sh['leave_type']] ?></td>
<td><?= $sh['late_minutes'] ?? '-' ?></td>
<td><?= $sh['missing_minutes'] ?? '-' ?></td>
<td><?= e($sh['manual_note'] ?? ($sh['auto_note'] ?? '')) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<!-- Genel tablo: tüm personel -->
<div class="table-responsive">
<table class="table table-dark table-sm table-hover" id="ozetTable">
<thead>
<tr>
<th>Personel</th>
<th>Plan</th><th>Geldi</th><th>Gelmedi</th>
<th>Geç</th><th>Erken</th><th>Eksik</th>
<th>Haftalık</th><th>Yıllık</th><th>Rapor</th><th>Ücretsiz</th><th>Mazeret</th>
<th>Geç (dk)</th><th>Eksik Süre</th>
</tr>
</thead>
<tbody>
<?php foreach ($summary_staff as $ps):
$sm_shifts = db_rows(
"SELECT ss.*, sa.status, sa.late_minutes, sa.missing_minutes
FROM staff_shifts ss
LEFT JOIN staff_attendance sa ON sa.shift_id = ss.id
WHERE ss.staff_id = ? AND ss.date BETWEEN ? AND ?",
[$ps['id'], $month_start, $month_end]
);
$c = ['planned'=>0,'present'=>0,'absent'=>0,'late'=>0,'early_leave'=>0,'incomplete'=>0,'annual'=>0,'sick'=>0,'unpaid'=>0,'excuse'=>0,'weekly'=>0,'tl'=>0,'tm'=>0];
foreach ($sm_shifts as $sh) {
if ($sh['is_work_day']) $c['planned']++;
$st = $sh['status'] ?? '';
if ($st === 'present') $c['present']++;
elseif ($st === 'absent') $c['absent']++;
elseif ($st === 'late') { $c['late']++; $c['tl'] += $sh['late_minutes']; }
elseif ($st === 'early_leave') $c['early_leave']++;
elseif ($st === 'incomplete') $c['incomplete']++;
if (!$sh['is_work_day'] && $sh['leave_type'] !== 'none') $c[$sh['leave_type']]++;
$c['tm'] += $sh['missing_minutes'] ?? 0;
}
?>
<tr>
<td>
<a href="?tab=ozet&filter_month=<?= $filter_month ?>&filter_year=<?= $filter_year ?>&filter_staff=<?= $ps['id'] ?>" class="text-warning text-decoration-none">
<?= e($ps['full_name']) ?>
</a>
</td>
<td><?= $c['planned'] ?></td>
<td class="text-success"><?= $c['present'] ?></td>
<td class="text-danger"><?= $c['absent'] ?></td>
<td class="text-warning"><?= $c['late'] ?></td>
<td><?= $c['early_leave'] ?></td>
<td><?= $c['incomplete'] ?></td>
<td><?= $c['weekly'] ?></td>
<td><?= $c['annual'] ?></td>
<td><?= $c['sick'] ?></td>
<td><?= $c['unpaid'] ?></td>
<td><?= $c['excuse'] ?></td>
<td><?= $c['tl'] ?></td>
<td><?= minutes_to_hm($c['tm']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<!-- ================================================================ -->
<!-- SEKME: AYLIK DETAY -->
<!-- ================================================================ -->
<?php elseif ($tab === 'aylik_detay'):
$sort_staff = get_str('sort_by') === 'department' ? 'department ASC, full_name ASC' : 'full_name ASC';
?>
<div class="card mb-3">
<div class="card-header">
<div class="row g-2 align-items-end flex-wrap">
<div class="col-md-2">
<label class="form-label small">Ay</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=aylik_detay&filter_month='+this.value+'&filter_year=<?= $filter_year ?>&sort_by=<?= get_str('sort_by') ?>'">
<?php foreach (month_names_tr() as $i => $mn): ?>
<option value="<?= $i+1 ?>" <?= $filter_month==$i+1?'selected':'' ?>><?= $mn ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small">Yıl</label>
<select class="form-select form-select-sm"
onchange="location.href='?tab=aylik_detay&filter_month=<?= $filter_month ?>&filter_year='+this.value+'&sort_by=<?= get_str('sort_by') ?>'">
<?php for ($y=date('Y')-2;$y<=date('Y')+1;$y++): ?>
<option value="<?= $y ?>" <?= $filter_year==$y?'selected':'' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</div>
<div class="col-md-auto">
<label class="form-label small">Sırala</label>
<div class="d-flex gap-1">
<a href="?tab=aylik_detay&filter_month=<?= $filter_month ?>&filter_year=<?= $filter_year ?>&sort_by=full_name"
class="btn btn-sm <?= get_str('sort_by')!=='department'?'btn-warning':'btn-outline-secondary' ?>">Ad Soyad</a>
<a href="?tab=aylik_detay&filter_month=<?= $filter_month ?>&filter_year=<?= $filter_year ?>&sort_by=department"
class="btn btn-sm <?= get_str('sort_by')==='department'?'btn-warning':'btn-outline-secondary' ?>">Departman</a>
</div>
</div>
<div class="col-md-auto ms-auto d-flex gap-2">
<button class="btn btn-outline-success btn-sm" onclick="exportMonthlyDetailExcel()">
<i class="bi bi-file-earmark-excel me-1"></i>Excel
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="printMonthlyDetail()">
<i class="bi bi-file-earmark-pdf me-1"></i>PDF
</button>
</div>
</div>
</div>
<div class="card-body p-0">
<?php
$det_start = sprintf('%04d-%02d-01', $filter_year, $filter_month);
$det_end = date('Y-m-t', strtotime($det_start));
$days_in_month = (int)date('t', strtotime($det_start));
// Tüm günler
$all_days = [];
for ($d = 1; $d <= $days_in_month; $d++) {
$all_days[] = sprintf('%04d-%02d-%02d', $filter_year, $filter_month, $d);
}
// Personelleri getir (sıralı)
$det_staff = db_rows("SELECT id, full_name, COALESCE(department,'salon') AS department FROM staff WHERE is_active=1 ORDER BY {$sort_staff}");
// Tüm vardiyaları + devamı çek
$det_shifts_raw = db_rows(
"SELECT ss.staff_id, ss.date, ss.is_work_day, ss.leave_type,
sa.status
FROM staff_shifts ss
LEFT JOIN staff_attendance sa ON sa.shift_id = ss.id
WHERE ss.date BETWEEN ? AND ?",
[$det_start, $det_end]
);
// [staff_id][date] = shift
$det_map = [];
foreach ($det_shifts_raw as $sh) {
$det_map[$sh['staff_id']][$sh['date']] = $sh;
}
$day_names_short = ['Pt','Sa','Ça','Pe','Cu','Ct','Pz'];
?>
<div class="table-responsive" style="overflow-x:auto">
<table class="table table-dark table-sm mb-0" id="aylikDetayTable" style="min-width:<?= 200 + $days_in_month * 38 ?>px;font-size:12px">
<thead>
<tr style="background:#111">
<th style="min-width:140px;position:sticky;left:0;background:#111;z-index:2">Ad Soyad</th>
<th style="min-width:70px;position:sticky;left:140px;background:#111;z-index:2">Dept.</th>
<?php foreach ($all_days as $day):
$dow = (int)(new DateTime($day))->format('N') - 1;
$is_today = $day === date('Y-m-d');
?>
<th class="text-center" style="min-width:36px;padding:4px 2px;background:<?= $is_today?'rgba(201,168,76,0.3)':($dow>=5?'rgba(255,255,255,0.05)':'#111') ?>">
<div style="font-size:9px;color:var(--text-muted)"><?= $day_names_short[$dow] ?></div>
<div style="font-size:10px;color:<?= $is_today?'var(--gold)':'var(--text-primary)' ?>"><?= (int)substr($day,8,2) ?></div>
</th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($det_staff as $ps): ?>
<tr>
<td style="position:sticky;left:0;background:var(--dark-card);z-index:1;font-weight:500;white-space:nowrap">
<?= e($ps['full_name']) ?>
</td>
<td style="position:sticky;left:140px;background:var(--dark-card);z-index:1">
<?php $dc = $ps['department']==='mutfak'?'bg-danger':'bg-info text-dark'; ?>
<span class="badge <?= $dc ?>" style="font-size:9px"><?= $ps['department']==='mutfak'?'Mutfak':'Salon' ?></span>
</td>
<?php foreach ($all_days as $day):
$sh = $det_map[$ps['id']][$day] ?? null;
$dow = (int)(new DateTime($day))->format('N') - 1;
?>
<td class="text-center" style="padding:3px 2px;background:<?= $dow>=5?'rgba(255,255,255,0.03)':'transparent' ?>">
<?php if (!$sh): ?>
<span style="color:rgba(255,255,255,0.15);font-size:13px">·</span>
<?php elseif (!$sh['is_work_day']):
$lt = $sh['leave_type'] ?? 'none';
if ($lt === 'weekly'): ?>
<span title="Haftalık İzin" style="display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:#fd7e14;color:#fff;font-size:9px;font-weight:700">Hİ</span>
<?php elseif ($lt === 'annual'): ?>
<span title="Yıllık İzin" style="display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:#28a745;color:#fff;font-size:9px;font-weight:700">Yİ</span>
<?php elseif ($lt === 'sick'): ?>
<span title="Rapor" style="display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:#dc3545;color:#fff;font-size:9px;font-weight:700">R</span>
<?php elseif ($lt === 'unpaid'): ?>
<span title="Ücretsiz İzin" style="display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:#6f42c1;color:#fff;font-size:9px;font-weight:700">Üİ</span>
<?php elseif ($lt === 'excuse'): ?>
<span title="Mazeret" style="display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;background:#17a2b8;color:#fff;font-size:9px;font-weight:700">M</span>
<?php else: ?>
<span title="OFF" style="color:#ffc107;font-size:10px;font-weight:700">OFF</span>
<?php endif; ?>
<?php else:
$st = $sh['status'] ?? 'pending';
if ($st === 'present'): ?>
<i class="bi bi-check-lg text-success" title="Geldi" style="font-size:16px"></i>
<?php elseif ($st === 'absent'): ?>
<i class="bi bi-x-lg text-danger" title="Gelmedi" style="font-size:14px"></i>
<?php elseif ($st === 'late'): ?>
<i class="bi bi-clock-history text-warning" title="Geç" style="font-size:13px"></i>
<?php elseif ($st === 'early_leave'): ?>
<i class="bi bi-box-arrow-right text-info" title="Erken Çıktı" style="font-size:13px"></i>
<?php else: ?>
<span style="color:rgba(255,255,255,0.3);font-size:11px">?</span>
<?php endif; ?>
<?php endif; ?>
</td>
<?php endforeach; ?>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Açıklama -->
<div class="d-flex gap-3 p-3 flex-wrap" style="font-size:11px;color:var(--text-muted)">
<span><i class="bi bi-check-lg text-success me-1"></i>Geldi</span>
<span><i class="bi bi-x-lg text-danger me-1"></i>Gelmedi</span>
<span><i class="bi bi-clock-history text-warning me-1"></i>Geç</span>
<span><i class="bi bi-box-arrow-right text-info me-1"></i>Erken Çıktı</span>
<span><span style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:#fd7e14;color:#fff;font-size:8px;margin-right:4px">Hİ</span>Haftalık İzin</span>
<span><span style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:#28a745;color:#fff;font-size:8px;margin-right:4px">Yİ</span>Yıllık İzin</span>
<span><span style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:#dc3545;color:#fff;font-size:8px;margin-right:4px">R</span>Rapor</span>
<span><span style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:#6f42c1;color:#fff;font-size:8px;margin-right:4px">Üİ</span>Ücretsiz İzin</span>
<span><span style="display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;background:#17a2b8;color:#fff;font-size:8px;margin-right:4px">M</span>Mazeret</span>
<span><span style="color:rgba(255,255,255,0.15)">·</span> Vardiya yok</span>
</div>
</div>
</div>
<?php endif; ?>
<!-- ================================================================ -->
<!-- MODALLER -->
<!-- ================================================================ -->
<!-- Personel Ekle/Düzenle Modal -->
<div class="modal fade" id="staffModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="staffModalTitle">Personel Ekle</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="staffId">
<div class="mb-3">
<label class="form-label">Ad Soyad <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="staffName">
</div>
<div class="mb-3">
<label class="form-label">Telefon</label>
<input type="text" class="form-control" id="staffPhone">
</div>
<div class="mb-3">
<label class="form-label">E-posta</label>
<input type="email" class="form-control" id="staffEmail">
</div>
<div class="mb-3">
<label class="form-label">Departman <span class="text-danger">*</span></label>
<select class="form-select" id="staffDepartment" onchange="updateDefaultSort()">
<option value="salon">Salon</option>
<option value="mutfak">Mutfak</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Pozisyon / Görev</label>
<input type="text" class="form-control" id="staffPosition" placeholder="Garson, Komi, Aşçı...">
</div>
<?php if ($has_sort_col): ?>
<div class="mb-3">
<label class="form-label">Sıralama Numarası
<span class="text-muted ms-1" style="font-size:11px;font-weight:400;text-transform:none">
(Mutfak: 100-199 · Salon: 200-299)
</span>
</label>
<input type="number" class="form-control" id="staffSortOrder"
min="0" max="9999" value="200" placeholder="200">
</div>
<?php endif; ?>
<div class="mb-3">
<label class="form-label">Notlar</label>
<textarea class="form-control" id="staffNotes" rows="2"></textarea>
</div>
<div class="form-check form-switch mb-2">
<input type="checkbox" class="form-check-input" id="staffIsActive" checked>
<label class="form-check-label" for="staffIsActive">Aktif</label>
</div>
</div>
<div class="modal-footer border-secondary">
<button class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button class="btn btn-warning" onclick="saveStaff()">
<i class="bi bi-check-lg me-1"></i>Kaydet
</button>
</div>
</div>
</div>
</div>
<!-- Vardiya Ekle/Düzenle Modal -->
<div class="modal fade" id="shiftModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="shiftModalTitle">Vardiya Ekle</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="shiftId">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Personel <span class="text-danger">*</span></label>
<select class="form-select" id="shiftStaffId">
<option value="">Seçin...</option>
<?php foreach ($all_staff as $ps): ?>
<option value="<?= $ps['id'] ?>"><?= e($ps['full_name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Tarih <span class="text-danger">*</span></label>
<input type="date" class="form-control" id="shiftDate" value="<?= e($filter_date) ?>">
</div>
<div class="col-12">
<div class="form-check form-switch mb-1">
<input class="form-check-input" type="checkbox" id="shiftIsWorkDay" checked onchange="toggleShiftWorkDay()">
<label class="form-check-label" for="shiftIsWorkDay">Çalışma Günü</label>
</div>
</div>
<div id="leaveTypeRow" class="col-md-6 d-none">
<label class="form-label">İzin Türü <span class="text-danger">*</span></label>
<select class="form-select" id="shiftLeaveType">
<option value="weekly">Haftalık İzin</option>
<option value="annual">Yıllık İzin</option>
<option value="sick">Raporlu</option>
<option value="unpaid">Ücretsiz İzin</option>
<option value="excuse">Mazeret İzni</option>
</select>
</div>
<div id="shiftTimeRow" class="col-12">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Planlanan Başlangıç</label>
<input type="time" class="form-control" id="shiftStartTime">
</div>
<div class="col-md-4">
<label class="form-label">Planlanan Bitiş</label>
<input type="time" class="form-control" id="shiftEndTime">
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="shiftIsNextDay">
<label class="form-check-label" for="shiftIsNextDay">Gece Vardiyası (+1 gün)</label>
</div>
</div>
</div>
</div>
<div class="col-12">
<label class="form-label">Not</label>
<input type="text" class="form-control" id="shiftNote">
</div>
</div>
</div>
<div class="modal-footer border-secondary d-flex justify-content-between align-items-center">
<div>
<button class="btn btn-danger btn-sm d-none" id="shiftDeleteBtn"
onclick="deleteShiftFromModal()">
<i class="bi bi-trash me-1"></i>Vardiyayı Sil
</button>
</div>
<div class="d-flex gap-2">
<button class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button class="btn btn-warning" onclick="saveShift()">
<i class="bi bi-check-lg me-1"></i>Kaydet
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Devam Güncelle Modal -->
<div class="modal fade" id="attModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary">
<h5 class="modal-title">Devam Güncelle</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="attId">
<input type="hidden" id="attShiftId">
<div class="mb-3">
<label class="form-label">Durum</label>
<select class="form-select" id="attStatus">
<option value="pending">Bekliyor</option>
<option value="present">Geldi</option>
<option value="absent">Gelmedi</option>
<option value="late">Geç Geldi</option>
<option value="early_leave">Erken Çıktı</option>
<option value="incomplete">Eksik Mesai</option>
</select>
</div>
<div class="row g-2 mb-3">
<div class="col-6">
<label class="form-label">Gerçek Giriş</label>
<input type="time" class="form-control" id="attStartTime">
</div>
<div class="col-6">
<label class="form-label">Gerçek Çıkış</label>
<input type="time" class="form-control" id="attEndTime">
</div>
</div>
<div class="mb-3">
<label class="form-label">Manuel Not</label>
<textarea class="form-control" id="attManualNote" rows="2"></textarea>
</div>
</div>
<div class="modal-footer border-secondary">
<button class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button class="btn btn-warning" onclick="saveAttendance()">
<i class="bi bi-check-lg me-1"></i>Kaydet
</button>
</div>
</div>
</div>
</div>
<!-- Vardiya Kopyala Modal -->
<div class="modal fade" id="copyShiftModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content bg-dark">
<div class="modal-header border-secondary">
<h5 class="modal-title">Haftalık Vardiya Kopyala</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p class="text-muted small">Seçilen haftanın vardiyalarını başka bir haftaya kopyalar.</p>
<div class="mb-3">
<label class="form-label">Kaynak Hafta Başlangıcı (Pazartesi)</label>
<input type="date" class="form-control" id="copySourceWeek">
</div>
<div class="mb-3">
<label class="form-label">Hedef Hafta Başlangıcı (Pazartesi)</label>
<input type="date" class="form-control" id="copyTargetWeek">
</div>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="copyOverwrite">
<label class="form-check-label" for="copyOverwrite">Mevcut kayıtların üzerine yaz</label>
</div>
</div>
<div class="modal-footer border-secondary">
<button class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button class="btn btn-warning" onclick="copyWeekShifts()">
<i class="bi bi-copy me-1"></i>Kopyala
</button>
</div>
</div>
</div>
</div>
<script>
// CSRF_TOKEN admin.js'de zaten tanımlı, burada tekrar tanımlanmıyor
const currentTab = '<?= $tab ?>';
/* ------------------------------------------------------------------ */
/* Personel */
/* ------------------------------------------------------------------ */
function openStaffModal(id = null) {
document.getElementById('staffId').value = '';
document.getElementById('staffName').value = '';
document.getElementById('staffPhone').value = '';
document.getElementById('staffEmail').value = '';
document.getElementById('staffDepartment').value = 'salon';
document.getElementById('staffPosition').value = '';
var sortEl2 = document.getElementById('staffSortOrder');
if (sortEl2) sortEl2.value = '200';
document.getElementById('staffNotes').value = '';
document.getElementById('staffIsActive').checked = true;
document.getElementById('staffModalTitle').textContent = id ? 'Personel Düzenle' : 'Personel Ekle';
if (id) {
document.getElementById('staffId').value = id;
const saveBtn = document.querySelector('#staffModal .btn-warning');
if (saveBtn) { saveBtn.disabled = true; saveBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-1"></span>Yükleniyor...'; }
// GET ile çek - CSRF gerektirmez, daha güvenilir
fetch('/api/admin/staff.php?action=get&id=' + encodeURIComponent(id), {
method: 'GET',
headers: { 'X-Requested-With': 'XMLHttpRequest' },
cache: 'no-store'
})
.then(res => {
if (!res.ok) throw new Error('HTTP ' + res.status + ' - Sunucu hatası');
return res.text();
})
.then(text => {
let r;
try { r = JSON.parse(text); }
catch(e) {
console.error('Staff API JSON parse hatası:', text.substring(0, 300));
throw new Error('Sunucu geçersiz yanıt döndürdü. Konsola bakın.');
}
if (r.success && r.data && r.data.staff) {
const s = r.data.staff;
document.getElementById('staffId').value = s.id || '';
document.getElementById('staffName').value = s.full_name || '';
document.getElementById('staffPhone').value = s.phone || '';
document.getElementById('staffEmail').value = s.email || '';
document.getElementById('staffDepartment').value = s.department || 'salon';
document.getElementById('staffPosition').value = s.position || '';
var sortEl3 = document.getElementById('staffSortOrder');
if (sortEl3) sortEl3.value = s.sort_order || 0;
document.getElementById('staffNotes').value = s.notes || '';
document.getElementById('staffIsActive').checked = (s.is_active == 1);
} else {
showToast(r.message || 'Personel bilgileri alınamadı.', 'error');
console.error('Staff API hatası:', r);
}
})
.catch(err => {
console.error('openStaffModal fetch hatası:', err);
showToast('Hata: ' + err.message, 'error');
})
.finally(() => {
if (saveBtn) {
saveBtn.disabled = false;
saveBtn.innerHTML = '<i class="bi bi-check-lg me-1"></i>Kaydet';
}
});
}
openModal('staffModal');
}
function saveStaff() {
const id = document.getElementById('staffId').value;
const name = document.getElementById('staffName').value.trim();
if (!name) { showToast('Ad Soyad zorunlu.', 'error'); return; }
var sortEl = document.getElementById('staffSortOrder');
const data = {
action: id ? 'update' : 'create',
id,
full_name: name,
phone: document.getElementById('staffPhone').value.trim(),
email: document.getElementById('staffEmail').value.trim(),
department: document.getElementById('staffDepartment').value,
position: document.getElementById('staffPosition').value.trim(),
sort_order: sortEl ? parseInt(sortEl.value) || 0 : 0,
notes: document.getElementById('staffNotes').value.trim(),
is_active: document.getElementById('staffIsActive').checked ? 1 : 0,
};
apiPost('/api/admin/staff.php', data).then(r => {
if (r.success) { showToast(r.message, 'success'); closeModal('staffModal'); setTimeout(()=>location.reload(), 700); }
else showToast(r.message, 'error');
});
}
function toggleStaffStatus(id, current) {
const newVal = current ? 0 : 1;
const msg = newVal ? 'Bu personeli aktifleştirmek istiyor musunuz?' : 'Bu personeli pasifleştirmek istiyor musunuz?';
confirmAction(msg, () => {
apiPost('/api/admin/staff.php', {action:'toggle_status', id, is_active: newVal}).then(r => {
if (r.success) { showToast(r.message, 'success'); setTimeout(()=>location.reload(), 700); }
else showToast(r.message, 'error');
});
});
}
function deleteStaff(id, name) {
confirmAction(`"${name}" silinsin mi? Vardiyası/devam kaydı varsa önce pasifleştirme önerilir.`, () => {
apiPost('/api/admin/staff.php', {action:'delete', id}).then(r => {
if (r.success) { showToast(r.message, 'success'); setTimeout(()=>location.reload(), 700); }
else showToast(r.message, 'warning');
});
});
}
/* ------------------------------------------------------------------ */
/* Vardiya */
/* ------------------------------------------------------------------ */
function openShiftModal() {
document.getElementById('shiftId').value = '';
document.getElementById('shiftStaffId').value = '';
document.getElementById('shiftDate').value = '<?= $filter_date ?>';
document.getElementById('shiftIsWorkDay').checked = true;
document.getElementById('shiftLeaveType').value = 'annual';
document.getElementById('shiftStartTime').value = '';
document.getElementById('shiftEndTime').value = '';
document.getElementById('shiftIsNextDay').checked = false;
document.getElementById('shiftNote').value = '';
document.getElementById('shiftModalTitle').textContent = 'Vardiya Ekle';
document.getElementById('shiftDeleteBtn').classList.add('d-none');
toggleShiftWorkDay();
openModal('shiftModal');
}
// Matris hücresinden direkt personel+tarih ile aç
function openShiftModalForCell(staffId, date) {
document.getElementById('shiftId').value = '';
document.getElementById('shiftStaffId').value = staffId;
document.getElementById('shiftDate').value = date;
document.getElementById('shiftIsWorkDay').checked = true;
document.getElementById('shiftLeaveType').value = 'annual';
document.getElementById('shiftStartTime').value = '';
document.getElementById('shiftEndTime').value = '';
document.getElementById('shiftIsNextDay').checked = false;
document.getElementById('shiftNote').value = '';
document.getElementById('shiftModalTitle').textContent = 'Vardiya Ekle';
document.getElementById('shiftDeleteBtn').classList.add('d-none');
toggleShiftWorkDay();
openModal('shiftModal');
}
function editShift(sh) {
document.getElementById('shiftId').value = sh.id;
document.getElementById('shiftStaffId').value = sh.staff_id;
document.getElementById('shiftDate').value = sh.date;
document.getElementById('shiftIsWorkDay').checked = sh.is_work_day == 1;
document.getElementById('shiftLeaveType').value = sh.leave_type || 'annual';
document.getElementById('shiftStartTime').value = sh.planned_start_time ? sh.planned_start_time.substring(0,5) : '';
document.getElementById('shiftEndTime').value = sh.planned_end_time ? sh.planned_end_time.substring(0,5) : '';
document.getElementById('shiftIsNextDay').checked = sh.is_next_day == 1;
document.getElementById('shiftNote').value = sh.shift_note || '';
document.getElementById('shiftModalTitle').textContent = 'Vardiya Düzenle';
document.getElementById('shiftDeleteBtn').classList.remove('d-none');
toggleShiftWorkDay();
openModal('shiftModal');
}
function toggleShiftWorkDay() {
const isWork = document.getElementById('shiftIsWorkDay').checked;
document.getElementById('leaveTypeRow').classList.toggle('d-none', isWork);
document.getElementById('shiftTimeRow').classList.toggle('d-none', !isWork);
}
function saveShift() {
const id = document.getElementById('shiftId').value;
const staffId = document.getElementById('shiftStaffId').value;
const date = document.getElementById('shiftDate').value;
const isWork = document.getElementById('shiftIsWorkDay').checked;
const start = document.getElementById('shiftStartTime').value;
const end = document.getElementById('shiftEndTime').value;
if (!staffId) { showToast('Personel seçin.', 'error'); return; }
if (!date) { showToast('Tarih girin.', 'error'); return; }
if (isWork && (!start || !end)) { showToast('Çalışma günüyse saat zorunlu.', 'error'); return; }
const data = {
action: id ? 'update' : 'create',
id,
staff_id: staffId,
date,
is_work_day: isWork ? 1 : 0,
leave_type: isWork ? 'none' : document.getElementById('shiftLeaveType').value,
planned_start_time: start,
planned_end_time: end,
is_next_day: document.getElementById('shiftIsNextDay').checked ? 1 : 0,
shift_note: document.getElementById('shiftNote').value.trim(),
};
apiPost('/api/admin/staff_shifts.php', data).then(r => {
if (r.success) { showToast(r.message, 'success'); closeModal('shiftModal'); setTimeout(()=>location.reload(), 700); }
else showToast(r.message, 'error');
});
}
function deleteShift(id) {
confirmAction('Bu vardiyayı silmek istediğinizden emin misiniz?', () => {
apiPost('/api/admin/staff_shifts.php', {action:'delete', id}).then(r => {
if (r.success) { showToast(r.message, 'success'); setTimeout(()=>location.reload(), 700); }
else showToast(r.message, 'error');
});
});
}
function deleteShiftFromModal() {
const id = document.getElementById('shiftId').value;
if (!id) return;
confirmAction('Bu vardiyayı silmek istediğinizden emin misiniz?', () => {
apiPost('/api/admin/staff_shifts.php', {action:'delete', id}).then(r => {
if (r.success) {
closeModal('shiftModal');
showToast(r.message, 'success');
setTimeout(() => location.reload(), 700);
} else {
showToast(r.message, 'error');
}
});
});
}
function openCopyShiftModal() { openModal('copyShiftModal'); }
function copyWeekShifts() {
const src = document.getElementById('copySourceWeek').value;
const tgt = document.getElementById('copyTargetWeek').value;
const ow = document.getElementById('copyOverwrite').checked ? 1 : 0;
if (!src || !tgt) { showToast('Her iki tarihi de girin.', 'error'); return; }
apiPost('/api/admin/staff_shifts.php', {action:'copy_week', source_week: src, target_week: tgt, overwrite: ow}).then(r => {
if (r.success) { showToast(r.message, 'success'); closeModal('copyShiftModal'); setTimeout(()=>location.reload(), 1000); }
else showToast(r.message, 'error');
});
}
/* ------------------------------------------------------------------ */
/* Devam */
/* ------------------------------------------------------------------ */
function openAttModal(att) {
document.getElementById('attId').value = att.id;
document.getElementById('attShiftId').value = att.shift_id;
document.getElementById('attStatus').value = att.status;
document.getElementById('attStartTime').value = att.actual_start_time ? att.actual_start_time.substring(0,5) : '';
document.getElementById('attEndTime').value = att.actual_end_time ? att.actual_end_time.substring(0,5) : '';
document.getElementById('attManualNote').value = att.manual_note || '';
// Vardiya saatlerini sakla (Geldi seçilince otomatik doldur için)
document.getElementById('attStatus').dataset.plannedStart = att.planned_start_time ? att.planned_start_time.substring(0,5) : '';
document.getElementById('attStatus').dataset.plannedEnd = att.planned_end_time ? att.planned_end_time.substring(0,5) : '';
openModal('attModal');
}
// Durum "Geldi" seçilince vardiyadaki saatleri otomatik doldur
document.addEventListener('DOMContentLoaded', () => {
const attStatusEl = document.getElementById('attStatus');
if (attStatusEl) {
attStatusEl.addEventListener('change', function() {
if (this.value === 'present') {
const startEl = document.getElementById('attStartTime');
const endEl = document.getElementById('attEndTime');
// Gerçek saat boşsa vardiya saatini doldur
if (!startEl.value && this.dataset.plannedStart) {
startEl.value = this.dataset.plannedStart;
}
if (!endEl.value && this.dataset.plannedEnd) {
endEl.value = this.dataset.plannedEnd;
}
}
});
}
});
function saveAttendance() {
const data = {
action: 'update',
id: document.getElementById('attId').value,
shift_id: document.getElementById('attShiftId').value,
status: document.getElementById('attStatus').value,
actual_start_time: document.getElementById('attStartTime').value,
actual_end_time: document.getElementById('attEndTime').value,
manual_note: document.getElementById('attManualNote').value.trim(),
};
apiPost('/api/admin/staff_attendance.php', data).then(r => {
if (r.success) { showToast(r.message, 'success'); closeModal('attModal'); setTimeout(()=>location.reload(), 700); }
else showToast(r.message, 'error');
});
}
/* Toplu devam */
function updateBulkBtn() {
const checked = document.querySelectorAll('.att-checkbox:checked').length;
document.getElementById('bulkPresentBtn').disabled = checked === 0;
}
document.getElementById('selectAll')?.addEventListener('change', function() {
document.querySelectorAll('.att-checkbox').forEach(cb => cb.checked = this.checked);
updateBulkBtn();
});
function bulkMarkPresent() {
const ids = Array.from(document.querySelectorAll('.att-checkbox:checked')).map(c => c.value);
if (!ids.length) return;
confirmAction(`${ids.length} personeli "Geldi" olarak işaretlemek istiyor musunuz?`, () => {
apiPost('/api/admin/staff_attendance.php', {action:'bulk_present', ids: ids.join(',')}).then(r => {
if (r.success) { showToast(r.message, 'success'); setTimeout(()=>location.reload(), 700); }
else showToast(r.message, 'error');
});
});
}
// ============================================================
// PDF / PRINT FONKSİYONLARI
// ============================================================
// printTable, printWeeklyShift, printMonthlyDetail - admin_layout_end sonrasına tasindi
</script>
<?php require_once __DIR__ . '/../includes/admin_layout_end.php'; ?>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
<script>
// ============================================================
// jsPDF ile PDF uretici
// ============================================================
function loadJsPDF(callback) {
if (window.jspdf && window.jspdf.jsPDF && window._jsPdfFontReady) { callback(); return; }
function loadAutoTable(cb) {
if (window.jspdf && window.jspdf.jsPDF && window.jspdf.jsPDF.prototype.autoTable) { cb(); return; }
var s2 = document.createElement('script');
s2.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js';
s2.onload = cb;
document.head.appendChild(s2);
}
function loadFont(cb) {
if (window._jsPdfFontReady) { cb(); return; }
// NotoSans-Regular subset - Türkçe karakterler dahil
// CDN'den font vfs kaydını çek
fetch('https://raw.githubusercontent.com/niklasvh/html2canvas/master/dist/npm/NotoSans.js')
.then(function(){ cb(); }) // fallback
.catch(function(){ cb(); });
// Gerçek çözüm: Türkçe karakterleri ASCII'ye dönüştür
window._jsPdfFontReady = true;
cb();
}
if (!window.jspdf) {
var s = document.createElement('script');
s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
s.onload = function() { loadAutoTable(function() { window._jsPdfFontReady = true; callback(); }); };
document.head.appendChild(s);
} else {
loadAutoTable(function() { window._jsPdfFontReady = true; callback(); });
}
}
// Türkçe → ASCII dönüşümü (jsPDF helvetica için)
function trToAscii(str) {
return (str || '').toString()
.replace(/Ğ/g,'G').replace(/ğ/g,'g')
.replace(/Ü/g,'U').replace(/ü/g,'u')
.replace(/Ş/g,'S').replace(/ş/g,'s')
.replace(/İ/g,'I').replace(/ı/g,'i')
.replace(/Ö/g,'O').replace(/ö/g,'o')
.replace(/Ç/g,'C').replace(/ç/g,'c');
}
// Hücre tipi tespiti: icon sınıfı veya title'a göre
function getCellInfo(cellEl) {
// İkonları kontrol et
var icon = cellEl.querySelector('i');
if (icon) {
if (icon.classList.contains('bi-check-lg')) return { text: '1', bg: [220,255,220], fg: [0,128,0] };
if (icon.classList.contains('bi-x-lg')) return { text: 'X', bg: [255,220,220], fg: [180,0,0] };
if (icon.classList.contains('bi-clock-history')) return { text: 'G', bg: [255,250,220], fg: [160,120,0] };
if (icon.classList.contains('bi-box-arrow-right'))return { text: 'EC', bg: [220,240,255], fg: [0,80,160] };
}
// Span rozet - title veya içerik
var span = cellEl.querySelector('span[title]');
if (span) {
var t = span.title || '';
if (t.indexOf('Haftalik') >= 0 || t.indexOf('Haftal') >= 0)
return { text: '1', bg: [255,240,180], fg: [120,80,0] };
if (t.indexOf('Ucretisiz') >= 0 || t.indexOf('cretsiz') >= 0)
return { text: 'X', bg: [200,240,200], fg: [0,100,0] };
if (t.indexOf('Rapor') >= 0 || t.indexOf('Sick') >= 0)
return { text: 'R', bg: [200,220,255], fg: [0,50,150] };
if (t.indexOf('Yillik') >= 0 || t.indexOf('llık') >= 0)
return { text: 'Yi', bg: [220,255,220], fg: [0,100,0] };
if (t.indexOf('Mazeret') >= 0)
return { text: 'M', bg: [230,220,255], fg: [80,0,150] };
}
// Span text
var span2 = cellEl.querySelector('span');
if (span2) {
var ic = span2.textContent.trim();
if (ic === 'Hi' || ic === 'HI') return { text: '1', bg: [255,240,180], fg: [120,80,0] };
if (ic === 'Yi' || ic === 'YI') return { text: 'Yi', bg: [220,255,220], fg: [0,100,0] };
if (ic === 'R') return { text: 'R', bg: [200,220,255], fg: [0,50,150] };
if (ic === 'Ui' || ic === 'UI') return { text: 'X', bg: [200,240,200], fg: [0,100,0] };
if (ic === 'M') return { text: 'M', bg: [230,220,255], fg: [80,0,150] };
}
return null;
}
function tableToMatrix(tableEl) {
var rows = [];
tableEl.querySelectorAll('tr').forEach(function(tr) {
var row = [];
tr.querySelectorAll('th,td').forEach(function(cell) {
var clone = cell.cloneNode(true);
clone.querySelectorAll('button,a.btn').forEach(function(el){ el.remove(); });
clone.querySelectorAll('i').forEach(function(el){
if (el.classList.contains('bi-check-lg')) el.replaceWith(document.createTextNode('1'));
else if (el.classList.contains('bi-x-lg')) el.replaceWith(document.createTextNode('X'));
else if (el.classList.contains('bi-clock-history')) el.replaceWith(document.createTextNode('G'));
else if (el.classList.contains('bi-box-arrow-right')) el.replaceWith(document.createTextNode('EC'));
else el.replaceWith(document.createTextNode(''));
});
row.push(trToAscii(clone.textContent.trim().replace(/\s+/g,' ')));
});
if (row.some(function(c){ return c !== ''; })) rows.push(row);
});
return rows;
}
function generateTablePdf(title, tableEl, landscape) {
loadJsPDF(function() {
var jsPDF = window.jspdf.jsPDF;
var doc = new jsPDF({ orientation: landscape ? 'landscape' : 'portrait', unit: 'mm', format: 'a4' });
doc.setFont('helvetica','bold');
doc.setFontSize(13);
doc.setTextColor(40,40,40);
doc.text(trToAscii(title), doc.internal.pageSize.getWidth() / 2, 14, { align: 'center' });
doc.setDrawColor(200,168,76);
doc.setLineWidth(0.6);
doc.line(10, 18, doc.internal.pageSize.getWidth() - 10, 18);
var matrix = tableToMatrix(tableEl);
if (matrix.length < 2) { showToast('Tabloda veri yok.', 'error'); return; }
var head = [matrix[0]];
var body = matrix.slice(1);
// Hücre renkleri için DOM'dan oku
var allRows = tableEl.querySelectorAll('tbody tr');
var cellColors = {};
allRows.forEach(function(tr, ri) {
var cells = tr.querySelectorAll('td');
cells.forEach(function(cell, ci) {
var info = getCellInfo(cell);
if (info) cellColors[ri + '_' + ci] = info;
});
});
doc.autoTable({
head: head,
body: body,
startY: 22,
styles: { fontSize: 7, cellPadding: 1.5, overflow: 'linebreak', halign: 'center' },
headStyles: { fillColor: [40,40,40], textColor: [200,168,76], fontStyle: 'bold', fontSize: 7, halign: 'center' },
columnStyles: { 0: { halign: 'left' }, 1: { halign: 'left' } },
alternateRowStyles: { fillColor: [248,248,248] },
margin: { left: 8, right: 8 },
theme: 'grid',
didParseCell: function(data) {
if (data.section !== 'body') return;
var key = data.row.index + '_' + data.column.index;
var info = cellColors[key];
if (info) {
data.cell.styles.fillColor = info.bg;
data.cell.styles.textColor = info.fg;
data.cell.styles.fontStyle = 'bold';
}
}
});
var safeTitle = title.replace(/[^a-zA-Z0-9_ -]/g,'_');
doc.save(safeTitle + '.pdf');
showToast('PDF indirildi.', 'success');
});
}
// ============================================================
// Genel Tablo PDF
// ============================================================
function printTable(tableId, title) {
var table = document.getElementById(tableId);
if (!table) { showToast('Tablo bulunamadi.', 'error'); return; }
generateTablePdf(trToAscii(title), table, true);
}
// ============================================================
// Vardiya Plani PDF
// ============================================================
function printWeeklyShift() {
var table = document.getElementById('weeklyShiftTable');
if (!table) { showToast('Tablo bulunamadi.', 'error'); return; }
var dateInput = document.querySelector('input[type=date]');
var weekTitle = dateInput ? dateInput.value : '';
var deptBtn = document.querySelector('.btn-group .btn-warning');
var dept = deptBtn ? deptBtn.textContent.trim() : '';
var title = 'Vardiya Plani' + (dept ? ' - ' + dept : '') + (weekTitle ? ' - ' + weekTitle : '');
generateTablePdf(trToAscii(title), table, true);
}
// ============================================================
// Aylik Detay PDF
// ============================================================
function printMonthlyDetail() {
var table = document.getElementById('aylikDetayTable');
if (!table) { showToast('Tablo bulunamadi.', 'error'); return; }
var monthNum = <?= (int)($filter_month ?? date('n')) ?>;
var yearVal = <?= (int)($filter_year ?? date('Y')) ?>;
var monthNames = ['Ocak','Subat','Mart','Nisan','Mayis','Haziran','Temmuz','Agustos','Eylul','Ekim','Kasim','Aralik'];
var title = 'Aylik Devam Detayi - ' + (monthNames[monthNum-1]||'') + ' ' + yearVal;
generateTablePdf(trToAscii(title), table, true);
}
function exportMonthlyDetailExcel() {
var table = document.getElementById('aylikDetayTable');
if (!table) { showToast('Tablo bulunamadi.', 'error'); return; }
var monthNum = <?= (int)($filter_month ?? date('n')) ?>;
var yearVal = <?= (int)($filter_year ?? date('Y')) ?>;
var monthNames = ['Ocak','Subat','Mart','Nisan','Mayis','Haziran','Temmuz','Agustos','Eylul','Ekim','Kasim','Aralik'];
var sheetName = (monthNames[monthNum-1]||'') + ' ' + yearVal;
var filename = 'personel_aylik_detay_' + yearVal + '_' + String(monthNum).padStart(2,'0') + '.xlsx';
var data = [];
var allRows = table.querySelectorAll('tr');
allRows.forEach(function(tr) {
var rowData = [];
var cells = tr.querySelectorAll('th,td');
cells.forEach(function(cell) {
var info = getCellInfo(cell);
if (info) {
rowData.push(info.text);
} else {
var badge = cell.querySelector('.badge');
if (badge) {
rowData.push(badge.textContent.trim());
} else {
rowData.push(cell.textContent.trim().replace(/\s+/g,' '));
}
}
});
data.push(rowData);
});
var wb = XLSX.utils.book_new();
var ws = XLSX.utils.aoa_to_sheet(data);
// Sutun genislikleri
if (data[0]) {
ws['!cols'] = data[0].map(function(_, i){ return { wch: i < 2 ? 22 : 4 }; });
}
XLSX.utils.book_append_sheet(wb, ws, sheetName.substring(0, 31));
XLSX.writeFile(wb, filename);
showToast('Excel dosyasi indirildi.', 'success');
}
</script>
<script>
function updateSortOrder(id, val) {
apiPost('/api/admin/staff.php', { action: 'update_sort', id: id, sort_order: parseInt(val) || 0 })
.then(function(res) {
if (!res.success) showToast(res.message || 'Hata', 'error');
});
}
function updateDefaultSort() {
var dept = document.getElementById('staffDepartment');
var sortEl = document.getElementById('staffSortOrder');
var staffId = document.getElementById('staffId');
if (!sortEl || !dept) return;
// Sadece yeni personel eklerken default'u güncelle
if (!staffId || !staffId.value) {
sortEl.value = dept.value === 'mutfak' ? '100' : '200';
sortEl.placeholder = dept.value === 'mutfak' ? '100' : '200';
}
}
</script>