refactor: implement strict admin authentication for management actions and add PIN verification flow for client access

This commit is contained in:
Ümit Tunç
2026-07-30 18:10:06 +03:00
parent dcda744b4a
commit 6dd03cc9ca
2 changed files with 939 additions and 846 deletions
+46 -18
View File
@@ -25,10 +25,13 @@ class ProjectController extends Controller
// Recalculate progress dynamically
$project->recalculateProgress();
// Check if admin mode is active (logged in user OR session toggle)
$isAdminMode = Auth::check() || session()->get('admin_mode_' . $project->id, true);
// Strict Admin Check: ONLY logged-in admin users can edit/manage
$isAdminMode = Auth::check();
return view('projects.show', compact('project', 'isAdminMode'));
// Client PIN Verification Check: Admins are auto-verified, clients need PIN verification in session
$isVerified = $isAdminMode || session()->get('project_access_' . $project->id, false);
return view('projects.show', compact('project', 'isAdminMode', 'isVerified'));
}
/**
@@ -55,6 +58,13 @@ class ProjectController extends Controller
*/
public function updateModuleStatus(Request $request, string $slug)
{
if (!Auth::check()) {
if ($request->wantsJson() || $request->ajax()) {
return response()->json(['success' => false, 'message' => 'Bu işlem için yönetici girişi yapmanız gerekmektedir.'], 403);
}
return back()->withErrors(['error' => 'Yetkisiz erişim.']);
}
$project = Project::where('slug', $slug)->firstOrFail();
$request->validate([
@@ -67,8 +77,6 @@ class ProjectController extends Controller
->firstOrFail();
$module->update(['status' => $request->input('status')]);
// Recalculate overall progress %
$newProgress = $project->recalculateProgress();
if ($request->wantsJson() || $request->ajax()) {
@@ -87,6 +95,13 @@ class ProjectController extends Controller
*/
public function updateTaskStatus(Request $request, string $slug)
{
if (!Auth::check()) {
if ($request->wantsJson() || $request->ajax()) {
return response()->json(['success' => false, 'message' => 'Bu işlem için yönetici girişi yapmanız gerekmektedir.'], 403);
}
return back()->withErrors(['error' => 'Yetkisiz erişim.']);
}
$project = Project::where('slug', $slug)->firstOrFail();
$request->validate([
@@ -123,6 +138,10 @@ class ProjectController extends Controller
*/
public function addTask(Request $request, string $slug)
{
if (!Auth::check()) {
return back()->withErrors(['error' => 'Yetkisiz erişim.']);
}
$project = Project::where('slug', $slug)->firstOrFail();
$request->validate([
@@ -154,6 +173,13 @@ class ProjectController extends Controller
*/
public function deleteTask(Request $request, string $slug)
{
if (!Auth::check()) {
if ($request->wantsJson() || $request->ajax()) {
return response()->json(['success' => false, 'message' => 'Yetkisiz erişim.'], 403);
}
return back()->withErrors(['error' => 'Yetkisiz erişim.']);
}
$project = Project::where('slug', $slug)->firstOrFail();
$request->validate([
@@ -176,6 +202,10 @@ class ProjectController extends Controller
*/
public function addUpdate(Request $request, string $slug)
{
if (!Auth::check()) {
return back()->withErrors(['error' => 'Yetkisiz erişim.']);
}
$project = Project::where('slug', $slug)->firstOrFail();
$request->validate([
@@ -200,6 +230,13 @@ class ProjectController extends Controller
*/
public function deleteUpdate(Request $request, string $slug)
{
if (!Auth::check()) {
if ($request->wantsJson() || $request->ajax()) {
return response()->json(['success' => false, 'message' => 'Yetkisiz erişim.'], 403);
}
return back()->withErrors(['error' => 'Yetkisiz erişim.']);
}
$project = Project::where('slug', $slug)->firstOrFail();
$request->validate([
@@ -227,22 +264,13 @@ class ProjectController extends Controller
*/
public function recalculate(Request $request, string $slug)
{
if (!Auth::check()) {
return back()->withErrors(['error' => 'Yetkisiz erişim.']);
}
$project = Project::where('slug', $slug)->firstOrFail();
$pct = $project->recalculateProgress();
return back()->with('success', "Proje ilerleme yüzdesi yeniden hesaplandı: %{$pct}");
}
/**
* Toggle Admin / Client View Mode in session
*/
public function toggleAdminMode(Request $request, string $slug)
{
$project = Project::where('slug', $slug)->firstOrFail();
$key = 'admin_mode_' . $project->id;
$current = session()->get($key, true);
session()->put($key, !$current);
return back();
}
}
+96 -31
View File
@@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="robots" content="noindex, nofollow">
<title>{{ $project->title }} - Canlı Proje Takip & Yönetim Portalı | Trunçgil Teknoloji</title>
<title>{{ $project->title }} - Canlı Proje Takip Portalı | Trunçgil Teknoloji</title>
<!-- Google Fonts: Inter & Outfit -->
<link rel="preconnect" href="https://fonts.googleapis.com">
@@ -16,7 +16,9 @@
<script src="https://cdn.tailwindcss.com"></script>
<!-- Sortable JS for Drag and Drop -->
@if($isAdminMode)
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
@endif
<script>
tailwind.config = {
@@ -140,6 +142,43 @@
</head>
<body class="bg-slate-50 dark:bg-slate-900 text-slate-800 dark:text-slate-200 min-h-screen font-sans transition-colors duration-300">
@if(!$isVerified && !$isAdminMode)
<!-- MÜŞTERİ PIN GİRİŞ EKRANI (CLIENT PIN LOGIN PORTAL) -->
<div class="min-h-screen flex items-center justify-center p-4 bg-gradient-to-br from-slate-900 via-slate-800 to-orange-950">
<div class="max-w-md w-full bg-slate-900/90 backdrop-blur-xl rounded-3xl p-8 shadow-2xl border border-slate-700/80 text-white space-y-6">
<!-- Branding -->
<div class="text-center space-y-3">
<img src="{{ asset('logos/truncgil-yatay-dark.svg') }}" alt="Trunçgil Teknoloji" class="h-10 w-auto mx-auto mb-2">
<span class="inline-block px-3 py-1 rounded-full bg-orange-500/20 text-orange-400 text-xs font-bold uppercase tracking-wider border border-orange-500/30">MÜŞTERİ CANLI TAKİP PORTALI</span>
<h2 class="text-xl font-extrabold font-display text-white">{{ $project->client_name }}</h2>
<p class="text-xs text-slate-300">{{ $project->title }}</p>
</div>
@if($errors->has('access_code'))
<div class="p-3 bg-red-500/20 border border-red-500/40 text-red-200 rounded-2xl text-xs font-bold text-center">
{{ $errors->first('access_code') }}
</div>
@endif
<form action="{{ route('projects.verify', $project->slug) }}" method="POST" class="space-y-4">
@csrf
<div>
<label class="block text-xs font-bold text-slate-300 uppercase tracking-wider mb-2 text-center">Müşteri Giriş Şifresi / PIN</label>
<input type="text" name="access_code" required placeholder="Örn: {{ $project->client_access_code }}" class="w-full px-4 py-3 rounded-2xl bg-slate-800 border border-slate-700 text-center font-mono font-bold text-lg text-orange-400 focus:outline-none focus:border-orange-500 uppercase tracking-widest">
</div>
<button type="submit" class="w-full py-3.5 rounded-2xl bg-gradient-to-r from-orange-500 to-rose-600 hover:from-orange-600 hover:to-rose-700 text-white font-extrabold text-sm shadow-xl transition-all flex items-center justify-center gap-2">
<i data-lucide="lock" class="w-4 h-4"></i>
<span>Projeyi Canlı İzle →</span>
</button>
</form>
<p class="text-[11px] text-center text-slate-500">
Projenizin şifresi sözleşme belgelerinizde yer almaktadır. Destek için Trunçgil Teknoloji ile iletişime geçebilirsiniz.
</p>
</div>
</div>
@else
<!-- Floating Toast Notification -->
<div id="toast-notification" class="fixed bottom-6 right-6 z-50 transform translate-y-20 opacity-0 transition-all duration-300 pointer-events-none">
<div class="bg-slate-900 text-white px-5 py-3 rounded-2xl shadow-2xl border border-slate-700 flex items-center gap-3">
@@ -148,13 +187,14 @@
</div>
</div>
<!-- Admin / Manager Floating Top Control Bar -->
<!-- Top Header Bar (Admin vs Client Mode) -->
@if($isAdminMode)
<div class="bg-gradient-to-r from-amber-600 via-orange-600 to-red-600 text-white px-4 py-2.5 shadow-lg border-b border-orange-500/40 sticky top-0 z-50">
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-3 text-xs">
<div class="flex items-center gap-2 font-bold uppercase tracking-wider">
<span class="w-2.5 h-2.5 rounded-full bg-white animate-ping"></span>
<i data-lucide="shield-check" class="w-4 h-4"></i>
<span>WEB YÖNETİCİ DÜZENLEME MODU (INTERACTIVE AJAX & DRAG-AND-DROP)</span>
<span>YÖNETİCİ DÜZENLEME MODU (ADMIN GİRİŞİ YAPILDI)</span>
</div>
<div class="flex flex-wrap items-center gap-2">
@@ -183,9 +223,20 @@
</div>
</div>
</div>
@else
<div class="bg-gradient-to-r from-blue-900 via-indigo-900 to-slate-900 text-white px-4 py-2 shadow-md border-b border-indigo-500/30 sticky top-0 z-50">
<div class="max-w-7xl mx-auto flex items-center justify-between text-xs">
<div class="flex items-center gap-2 font-bold uppercase tracking-wider text-blue-300">
<i data-lucide="eye" class="w-4 h-4 text-cyan-400"></i>
<span>🔒 MÜŞTERİ CANLI TAKİP PORTALI (SADECE OKUMA MODU)</span>
</div>
<span class="hidden sm:inline text-[11px] text-slate-300">Bu sayfa projenizi canlı izleyebilmeniz için tasarlanmıştır. Tüm değişiklikler Trunçgil Teknoloji ekibi tarafından yönetilmektedir.</span>
</div>
</div>
@endif
<!-- Header Navigation -->
<header class="w-full glass-header sticky top-[41px] z-40">
<header class="w-full glass-header sticky top-[37px] z-40">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-18 flex items-center justify-between py-4">
<!-- Logos -->
@@ -196,7 +247,7 @@
</a>
<div class="h-6 w-px bg-slate-300 dark:bg-slate-700"></div>
<div class="flex flex-col">
<span class="text-xs font-bold uppercase tracking-wider text-orange-600 dark:text-orange-400">Canlı Proje Takip & Yönetim Portalı</span>
<span class="text-xs font-bold uppercase tracking-wider text-orange-600 dark:text-orange-400">Canlı Proje Takip Portalı</span>
<span class="text-sm font-semibold text-slate-700 dark:text-slate-200 line-clamp-1">{{ $project->client_name }}</span>
</div>
</div>
@@ -252,7 +303,7 @@
</h1>
<p class="text-sm sm:text-base text-slate-300 font-medium">
Bu ekran, <strong>{{ $project->client_name }}</strong> projesine ait iş takvimini, modül durumlarını ve sürükle-bırak destekli canlı Kanban panosunu yönetebileceğiniz portal ekranıdır.
Bu ekran, <strong>{{ $project->client_name }}</strong> projesine ait iş takvimini, modül durumlarını ve canlı Kanban panosunu izleyebileceğiniz portal ekranıdır.
</p>
<!-- Meta Tags -->
@@ -270,11 +321,6 @@
<span>Hedef Bitiş: {{ $project->target_date->format('d.m.Y') }}</span>
</div>
@endif
<div class="flex items-center gap-1.5 bg-orange-500/20 px-3 py-1.5 rounded-xl border border-orange-500/40 text-orange-300">
<i data-lucide="key" class="w-3.5 h-3.5"></i>
<span>Müşteri PIN: <strong>{{ $project->client_access_code }}</strong></span>
</div>
</div>
</div>
@@ -331,7 +377,7 @@
</div>
<div>
<h4 class="text-sm font-bold text-slate-900 dark:text-white">Kanban Panosu</h4>
<p class="text-xs text-slate-500"><span id="total-tasks-count">{{ $project->tasks->count() }}</span> Adet Sürükle-Bırak Kartı</p>
<p class="text-xs text-slate-500"><span id="total-tasks-count">{{ $project->tasks->count() }}</span> Adet Görev Kartı</p>
</div>
</a>
@@ -389,7 +435,7 @@ gantt
</div>
</section>
<!-- Section 2: Modules Completion Matrix (INTERACTIVE AJAX STATUS TOGGLES) -->
<!-- Section 2: Modules Completion Matrix -->
<section id="modules-section" class="glass-card rounded-3xl p-6 sm:p-8 space-y-6">
<div class="flex flex-col sm:flex-row sm:items-center justify-between border-b border-slate-200 dark:border-slate-800 pb-4 gap-4">
<div class="flex items-center gap-3">
@@ -398,12 +444,9 @@ gantt
</div>
<div>
<h2 class="text-lg font-bold text-slate-900 dark:text-white font-display">2. Hizmet Modülleri İlerleme Matrisi</h2>
<p class="text-xs text-slate-500">Sayfa yenilemeden tek tıkla modül durumlarını canlı değiştirin</p>
<p class="text-xs text-slate-500">Sözleşmedeki hizmet kalemlerinin tamamlama durumları</p>
</div>
</div>
<span class="text-xs font-bold px-3 py-1.5 rounded-full bg-orange-500/10 text-orange-600 dark:text-orange-400 border border-orange-500/20 self-start sm:self-auto">
⚡ Canlı AJAX Düzenleme
</span>
</div>
<!-- Modules Grid -->
@@ -443,10 +486,10 @@ gantt
</p>
@endif
<!-- Admin Web Status Toggle Buttons (AJAX enabled) -->
<div class="pt-3 border-t border-slate-200/60 dark:border-slate-800/60 flex flex-wrap items-center justify-between gap-2">
<span class="text-xs font-semibold text-slate-500">Ağırlık: %{{ $module->weight_percent }}</span>
@if($isAdminMode)
<div class="flex items-center gap-1.5">
<button onclick="setModuleStatusAjax({{ $module->id }}, 'pending')" class="px-2.5 py-1 rounded-lg text-xs font-bold transition-all hover:scale-105 active:scale-95 {{ $module->status === 'pending' ? 'bg-slate-700 text-white shadow-sm' : 'bg-slate-200 dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-300' }}">
Bekliyor
@@ -460,6 +503,7 @@ gantt
✓ Tamamlandı
</button>
</div>
@endif
</div>
</div>
@empty
@@ -470,7 +514,7 @@ gantt
</div>
</section>
<!-- Section 3: Live Drag-and-Drop Kanban Board (Sortable.js + AJAX) -->
<!-- Section 3: Live Kanban Board -->
<section id="kanban-section" class="glass-card rounded-3xl p-6 sm:p-8 space-y-6">
<div class="flex flex-col sm:flex-row sm:items-center justify-between border-b border-slate-200 dark:border-slate-800 pb-4 gap-4">
<div class="flex items-center gap-3">
@@ -478,15 +522,17 @@ gantt
<i data-lucide="kanban-square" class="w-4 h-4"></i>
</div>
<div>
<h2 class="text-lg font-bold text-slate-900 dark:text-white font-display">3. Canlı Sürükle-Bırak Kanban Görev Panosu</h2>
<p class="text-xs text-slate-500">Kartları kolonlar arasında sürükleyip bırakarak sayfa yenilemeden canlı güncelleyin</p>
<h2 class="text-lg font-bold text-slate-900 dark:text-white font-display">3. Canlı Kanban Görev Panosu</h2>
<p class="text-xs text-slate-500">Mühendislik ekibinin anlık görev statüleri</p>
</div>
</div>
@if($isAdminMode)
<button onclick="openAddTaskModal()" class="px-4 py-2 rounded-xl bg-orange-600 hover:bg-orange-700 text-white font-bold text-xs shadow-md transition-all flex items-center gap-1.5 self-start sm:self-auto">
<i data-lucide="plus" class="w-4 h-4"></i>
<span>Yeni Görev Ekle</span>
</button>
@endif
</div>
<!-- 4 Column Drag and Drop Board -->
@@ -505,12 +551,14 @@ gantt
<div class="kanban-drop-zone min-h-[220px] flex-1 space-y-3 pt-1" data-status="todo">
@foreach($project->tasks->where('status', 'todo') as $task)
<div class="kanban-card cursor-grab active:cursor-grabbing p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm space-y-2.5 transition-all hover:shadow-md" data-task-id="{{ $task->id }}">
<div class="kanban-card {{ $isAdminMode ? 'cursor-grab active:cursor-grabbing hover:shadow-md' : 'cursor-default' }} p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm space-y-2.5 transition-all" data-task-id="{{ $task->id }}">
<div class="flex items-start justify-between gap-2">
<h4 class="text-xs font-bold text-slate-900 dark:text-white leading-snug">{{ $task->title }}</h4>
@if($isAdminMode)
<button onclick="deleteTaskAjax({{ $task->id }}, this)" class="text-slate-400 hover:text-red-500 p-0.5 flex-shrink-0">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>
@endif
</div>
@if($task->description)
@@ -539,12 +587,14 @@ gantt
<div class="kanban-drop-zone min-h-[220px] flex-1 space-y-3 pt-1" data-status="in_progress">
@foreach($project->tasks->where('status', 'in_progress') as $task)
<div class="kanban-card cursor-grab active:cursor-grabbing p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-orange-200 dark:border-orange-900/40 shadow-sm space-y-2.5 transition-all hover:shadow-md" data-task-id="{{ $task->id }}">
<div class="kanban-card {{ $isAdminMode ? 'cursor-grab active:cursor-grabbing hover:shadow-md' : 'cursor-default' }} p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-orange-200 dark:border-orange-900/40 shadow-sm space-y-2.5 transition-all" data-task-id="{{ $task->id }}">
<div class="flex items-start justify-between gap-2">
<h4 class="text-xs font-bold text-slate-900 dark:text-white leading-snug">{{ $task->title }}</h4>
@if($isAdminMode)
<button onclick="deleteTaskAjax({{ $task->id }}, this)" class="text-slate-400 hover:text-red-500 p-0.5 flex-shrink-0">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>
@endif
</div>
@if($task->description)
@@ -573,12 +623,14 @@ gantt
<div class="kanban-drop-zone min-h-[220px] flex-1 space-y-3 pt-1" data-status="review">
@foreach($project->tasks->where('status', 'review') as $task)
<div class="kanban-card cursor-grab active:cursor-grabbing p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-amber-200 dark:border-amber-900/40 shadow-sm space-y-2.5 transition-all hover:shadow-md" data-task-id="{{ $task->id }}">
<div class="kanban-card {{ $isAdminMode ? 'cursor-grab active:cursor-grabbing hover:shadow-md' : 'cursor-default' }} p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-amber-200 dark:border-amber-900/40 shadow-sm space-y-2.5 transition-all" data-task-id="{{ $task->id }}">
<div class="flex items-start justify-between gap-2">
<h4 class="text-xs font-bold text-slate-900 dark:text-white leading-snug">{{ $task->title }}</h4>
@if($isAdminMode)
<button onclick="deleteTaskAjax({{ $task->id }}, this)" class="text-slate-400 hover:text-red-500 p-0.5 flex-shrink-0">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>
@endif
</div>
@if($task->description)
@@ -607,12 +659,14 @@ gantt
<div class="kanban-drop-zone min-h-[220px] flex-1 space-y-3 pt-1" data-status="done">
@foreach($project->tasks->where('status', 'done') as $task)
<div class="kanban-card cursor-grab active:cursor-grabbing p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-emerald-200 dark:border-emerald-900/40 shadow-sm space-y-2.5 transition-all hover:shadow-md opacity-95" data-task-id="{{ $task->id }}">
<div class="kanban-card {{ $isAdminMode ? 'cursor-grab active:cursor-grabbing hover:shadow-md' : 'cursor-default' }} p-3.5 bg-white dark:bg-slate-900 rounded-xl border border-emerald-200 dark:border-emerald-900/40 shadow-sm space-y-2.5 transition-all opacity-95" data-task-id="{{ $task->id }}">
<div class="flex items-start justify-between gap-2">
<h4 class="text-xs font-bold text-slate-900 dark:text-white leading-snug line-through decoration-emerald-500">{{ $task->title }}</h4>
@if($isAdminMode)
<button onclick="deleteTaskAjax({{ $task->id }}, this)" class="text-slate-400 hover:text-red-500 p-0.5 flex-shrink-0">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
</button>
@endif
</div>
@if($task->description)
@@ -644,13 +698,16 @@ gantt
</div>
</div>
@if($isAdminMode)
<button onclick="openAddUpdateModal()" class="px-4 py-2 rounded-xl bg-emerald-600 hover:bg-emerald-700 text-white font-bold text-xs shadow-md transition-all flex items-center gap-1.5 self-start sm:self-auto">
<i data-lucide="plus" class="w-4 h-4"></i>
<span>Yeni Duyuru Yayınla</span>
</button>
@endif
</div>
<!-- Fast Add Update Box -->
@if($isAdminMode)
<!-- Fast Add Update Box (Admin Only) -->
<div class="bg-slate-100/70 dark:bg-slate-800/60 p-4 sm:p-5 rounded-2xl border border-slate-200 dark:border-slate-700">
<h3 class="text-xs font-bold uppercase tracking-wider text-slate-600 dark:text-slate-300 mb-3 flex items-center gap-1.5">
<i data-lucide="send" class="w-3.5 h-3.5 text-emerald-500"></i>
@@ -667,6 +724,7 @@ gantt
</div>
</form>
</div>
@endif
<!-- Timeline Stream -->
<div class="relative pl-6 border-l-2 border-orange-500/30 space-y-8 my-4">
@@ -683,10 +741,12 @@ gantt
<span class="text-[11px] font-semibold text-slate-400">
{{ $update->created_at->format('d.m.Y H:i') }}
</span>
@if($isAdminMode)
<button onclick="deleteUpdateAjax({{ $update->id }}, this)" title="Duyuruyu Sil" class="px-2 py-1 rounded-lg bg-red-50 hover:bg-red-100 text-red-600 dark:bg-red-950/40 dark:hover:bg-red-900/60 dark:text-red-400 font-bold text-xs transition-all flex items-center gap-1 border border-red-200 dark:border-red-800/40">
<i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
<span>Sil</span>
</button>
@endif
</div>
</div>
@@ -712,7 +772,8 @@ gantt
</main>
<!-- Add Task Modal -->
@if($isAdminMode)
<!-- Add Task Modal (Admin Only) -->
<div id="add-task-modal" class="fixed inset-0 z-50 bg-slate-900/80 backdrop-blur-sm hidden flex items-center justify-center p-4">
<div class="bg-white dark:bg-slate-900 rounded-3xl max-w-lg w-full p-6 space-y-4 shadow-2xl border border-slate-200 dark:border-slate-800">
<div class="flex items-center justify-between border-b border-slate-200 dark:border-slate-800 pb-3">
@@ -771,6 +832,7 @@ gantt
</form>
</div>
</div>
@endif
<!-- Corporate Footer -->
<footer class="mt-16 border-t border-slate-200 dark:border-slate-800 py-8 text-center text-xs text-slate-500">
@@ -784,8 +846,6 @@ gantt
<script src="https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/svg-pan-zoom@3.6.1/dist/svg-pan-zoom.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lucide@0.321.0/dist/umd/lucide.min.js"></script>
<!-- Sortable JS for Drag and Drop -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.0/Sortable.min.js"></script>
<script>
lucide.createIcons();
@@ -868,7 +928,8 @@ gantt
else ganttPanZoom.zoomOut();
}
// Sortable.js Drag and Drop Setup for 4 Kanban Columns
@if($isAdminMode)
// Sortable.js Drag and Drop Setup (Admin Only)
function initKanbanSortable() {
if (typeof Sortable === 'undefined') {
setTimeout(initKanbanSortable, 100);
@@ -911,7 +972,7 @@ gantt
}
showToast(data.message, 'success');
} else {
showToast('Görev taşınamadı.', 'error');
showToast(data.message || 'Görev taşınamadı.', 'error');
}
})
.catch(err => {
@@ -1014,6 +1075,7 @@ gantt
showToast('Bağlantı hatası.', 'error');
});
}
@endif
// Dynamic UI Updates
function updateProgressUI(pct) {
@@ -1054,6 +1116,7 @@ gantt
}
}
@if($isAdminMode)
// Modal Handlers
function openAddTaskModal() {
document.getElementById('add-task-modal').classList.remove('hidden');
@@ -1064,6 +1127,8 @@ gantt
function openAddUpdateModal() {
window.location.hash = 'updates-section';
}
@endif
</script>
@endif
</body>
</html>