diff --git a/app/Filament/Admin/Resources/Projects/Pages/CreateProject.php b/app/Filament/Admin/Resources/Projects/Pages/CreateProject.php new file mode 100644 index 0000000..d394c86 --- /dev/null +++ b/app/Filament/Admin/Resources/Projects/Pages/CreateProject.php @@ -0,0 +1,27 @@ +record->recalculateProgress(); + } +} diff --git a/app/Filament/Admin/Resources/Projects/Pages/EditProject.php b/app/Filament/Admin/Resources/Projects/Pages/EditProject.php new file mode 100644 index 0000000..39317e1 --- /dev/null +++ b/app/Filament/Admin/Resources/Projects/Pages/EditProject.php @@ -0,0 +1,56 @@ +record->title; + } + + public function getMaxContentWidth(): Width | string | null + { + return Width::Full; + } + + protected function getHeaderActions(): array + { + return [ + Action::make('recalculate') + ->label('İlerlemeyi Yeniden Hesapla') + ->icon('heroicon-o-calculator') + ->color('info') + ->action(function () { + $pct = $this->record->recalculateProgress(); + Notification::make() + ->title('Proje ilerleme yüzdesi güncellendi: %' . $pct) + ->success() + ->send(); + }), + + Action::make('preview_portal') + ->label('Müşteri Ekranında Gör') + ->icon('heroicon-o-eye') + ->color('warning') + ->url(fn () => route('projects.show', $this->record->slug)) + ->openUrlInNewTab(), + + DeleteAction::make(), + ]; + } + + protected function afterSave(): void + { + $this->record->recalculateProgress(); + } +} diff --git a/app/Filament/Admin/Resources/Projects/Pages/ListProjects.php b/app/Filament/Admin/Resources/Projects/Pages/ListProjects.php new file mode 100644 index 0000000..d8334fd --- /dev/null +++ b/app/Filament/Admin/Resources/Projects/Pages/ListProjects.php @@ -0,0 +1,20 @@ +label('Yeni Proje Başlat'), + ]; + } +} diff --git a/app/Filament/Admin/Resources/Projects/ProjectResource.php b/app/Filament/Admin/Resources/Projects/ProjectResource.php new file mode 100644 index 0000000..3b1463e --- /dev/null +++ b/app/Filament/Admin/Resources/Projects/ProjectResource.php @@ -0,0 +1,72 @@ + ListProjects::route('/'), + 'create' => CreateProject::route('/create'), + 'edit' => EditProject::route('/{record}/edit'), + ]; + } + + public static function getRecordRouteBindingEloquentQuery(): Builder + { + return parent::getRecordRouteBindingEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Admin/Resources/Projects/Schemas/ProjectForm.php b/app/Filament/Admin/Resources/Projects/Schemas/ProjectForm.php new file mode 100644 index 0000000..6751c8e --- /dev/null +++ b/app/Filament/Admin/Resources/Projects/Schemas/ProjectForm.php @@ -0,0 +1,259 @@ +schema([ + Tabs::make('ProjectTabs') + ->tabs([ + Tab::make('Proje Detayları & Müşteri') + ->icon('heroicon-m-briefcase') + ->schema([ + Section::make('Temel Proje Bilgileri') + ->columns(12) + ->schema([ + TextInput::make('title') + ->label('Proje Adı') + ->required() + ->maxLength(255) + ->live(onBlur: true) + ->afterStateUpdated(function (string $operation, $state, callable $set) { + if ($operation !== 'create') return; + $set('slug', Str::slug($state)); + }) + ->columnSpan(6), + + TextInput::make('slug') + ->label('URL Slug') + ->required() + ->maxLength(255) + ->unique(ignoreRecord: true) + ->rules(['alpha_dash']) + ->columnSpan(6), + + TextInput::make('client_name') + ->label('Müşteri Unvanı / Firma') + ->required() + ->maxLength(255) + ->columnSpan(4), + + TextInput::make('client_email') + ->label('Müşteri E-Posta') + ->email() + ->maxLength(255) + ->columnSpan(4), + + TextInput::make('client_access_code') + ->label('Müşteri Giriş Şifresi / PIN') + ->default(fn () => strtoupper(Str::random(6))) + ->helperText('Müşterinin takip ekranına giriş yapabileceği şifre') + ->columnSpan(4), + + Select::make('proposal_id') + ->label('İlişkili Fiyat Teklifi') + ->relationship('proposal', 'title') + ->searchable() + ->preload() + ->nullable() + ->live() + ->afterStateUpdated(function ($state, callable $set) { + if ($state) { + $prop = \App\Models\Proposal::find($state); + if ($prop) { + $set('slug', $prop->slug); + } + } + }) + ->columnSpan(4), + + Select::make('status') + ->label('Proje Genel Durumu') + ->options([ + 'planning' => 'Planlama Aşamasında', + 'in_progress' => 'Devam Ediyor (Aktif)', + 'on_hold' => 'Beklemeye Alındı', + 'completed' => 'Tamamlandı', + 'cancelled' => 'İptal Edildi', + ]) + ->default('in_progress') + ->required() + ->columnSpan(4), + + TextInput::make('progress_percent') + ->label('İlerleme Yüzdesi (%)') + ->numeric() + ->default(0) + ->suffix('%') + ->columnSpan(4), + + DatePicker::make('start_date') + ->label('Proje Başlangıç Tarihi') + ->native(false) + ->displayFormat('d.m.Y') + ->columnSpan(6), + + DatePicker::make('target_date') + ->label('Hedef Bitiş Tarihi') + ->native(false) + ->displayFormat('d.m.Y') + ->columnSpan(6), + + Textarea::make('notes') + ->label('İç Notlar ve Açıklamalar') + ->rows(3) + ->columnSpanFull(), + ]), + ]), + + Tab::make('Hizmet Modülleri (% İlerleme)') + ->icon('heroicon-m-squares-2x2') + ->schema([ + Section::make('Sözleşme Kapsamındaki Modüller') + ->description('Hangi modüllerin tamamlandığını işaretleyin. İlerleme yüzdesi modül ağırlıklarına göre otomatik hesaplanır.') + ->schema([ + Repeater::make('modules') + ->relationship('modules') + ->columns(12) + ->orderColumn('order') + ->defaultItems(0) + ->schema([ + TextInput::make('title') + ->label('Modül Adı') + ->required() + ->columnSpan(4), + + TextInput::make('weight_percent') + ->label('Ağırlık (%)') + ->numeric() + ->default(10) + ->suffix('%') + ->columnSpan(2), + + Select::make('status') + ->label('Durum') + ->options([ + 'pending' => 'Bekliyor', + 'in_progress' => 'Devam Ediyor', + 'completed' => 'Tamamlandı', + ]) + ->default('pending') + ->required() + ->columnSpan(3), + + DatePicker::make('start_date') + ->label('Başlangıç') + ->native(false) + ->columnSpan(1.5), + + DatePicker::make('end_date') + ->label('Bitiş') + ->native(false) + ->columnSpan(1.5), + ]) + ->columnSpanFull(), + ]), + ]), + + Tab::make('Kanban Görev Kartları') + ->icon('heroicon-m-view-columns') + ->schema([ + Section::make('İş Takvimi ve Görev Listesi') + ->description('Sözleşmedeki taskları ve detaylı görev kartlarını burada yönetin.') + ->schema([ + Repeater::make('tasks') + ->relationship('tasks') + ->columns(12) + ->orderColumn('order_index') + ->defaultItems(0) + ->schema([ + TextInput::make('title') + ->label('Görev Başlığı') + ->required() + ->columnSpan(4), + + Select::make('status') + ->label('Kanban Durumu') + ->options([ + 'todo' => 'Yapılacak', + 'in_progress' => 'Devam Ediyor', + 'review' => 'Kontrol Bekliyor', + 'done' => 'Tamamlandı', + ]) + ->default('todo') + ->required() + ->columnSpan(3), + + Select::make('priority') + ->label('Öncelik') + ->options([ + 'low' => 'Düşük', + 'medium' => 'Orta', + 'high' => 'Yüksek', + 'urgent' => 'Acil', + ]) + ->default('medium') + ->columnSpan(2), + + TextInput::make('assigned_person') + ->label('Sorumlu') + ->columnSpan(3), + ]) + ->columnSpanFull(), + ]), + ]), + + Tab::make('Gidişat Güncellemeleri Logu') + ->icon('heroicon-m-chat-bubble-bottom-center-text') + ->schema([ + Section::make('Canlı İlerleme Duyuruları') + ->description('Yazılım ekibi tarafından girilen ve müşterinin canlı izleyebildiği durum mesajları.') + ->schema([ + Repeater::make('updates') + ->relationship('updates') + ->columns(12) + ->defaultItems(0) + ->schema([ + TextInput::make('title') + ->label('Güncelleme Başlığı') + ->required() + ->columnSpan(6), + + TextInput::make('progress_percent_at_update') + ->label('O Anki Yüzde (%)') + ->numeric() + ->columnSpan(3), + + Checkbox::make('is_public') + ->label('Müşteriye Göster') + ->default(true) + ->columnSpan(3), + + Textarea::make('content') + ->label('Güncelleme Notu / Sürüm Açıklaması') + ->required() + ->rows(2) + ->columnSpanFull(), + ]) + ->columnSpanFull(), + ]), + ]), + ])->columnSpanFull() + ]); + } +} diff --git a/app/Filament/Admin/Resources/Projects/Tables/ProjectsTable.php b/app/Filament/Admin/Resources/Projects/Tables/ProjectsTable.php new file mode 100644 index 0000000..a45399c --- /dev/null +++ b/app/Filament/Admin/Resources/Projects/Tables/ProjectsTable.php @@ -0,0 +1,110 @@ +columns([ + TextColumn::make('title') + ->label('Proje Adı') + ->searchable() + ->sortable() + ->limit(40), + + TextColumn::make('client_name') + ->label('Müşteri') + ->searchable() + ->sortable() + ->limit(30), + + TextColumn::make('progress_percent') + ->label('İlerleme') + ->formatStateUsing(fn ($state) => "%{$state}") + ->badge() + ->color(fn ($state) => match (true) { + $state >= 100 => 'success', + $state >= 50 => 'info', + $state >= 25 => 'warning', + default => 'danger', + }) + ->sortable(), + + TextColumn::make('status') + ->label('Durum') + ->badge() + ->color(fn (string $state): string => match ($state) { + 'planning' => 'gray', + 'in_progress' => 'info', + 'on_hold' => 'warning', + 'completed' => 'success', + 'cancelled' => 'danger', + default => 'gray', + }) + ->formatStateUsing(fn (string $state): string => match ($state) { + 'planning' => 'Planlama', + 'in_progress' => 'Devam Ediyor', + 'on_hold' => 'Beklemede', + 'completed' => 'Tamamlandı', + 'cancelled' => 'İptal', + default => $state, + }), + + TextColumn::make('client_access_code') + ->label('Müşteri PIN') + ->copyable() + ->badge() + ->color('gray'), + + TextColumn::make('target_date') + ->label('Hedef Bitiş') + ->date('d.m.Y') + ->sortable(), + + TextColumn::make('created_at') + ->label('Oluşturulma') + ->dateTime('d.m.Y H:i') + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + SelectFilter::make('status') + ->label('Durum') + ->options([ + 'planning' => 'Planlama', + 'in_progress' => 'Devam Ediyor', + 'on_hold' => 'Beklemede', + 'completed' => 'Tamamlandı', + 'cancelled' => 'İptal', + ]), + ]) + ->recordActions([ + EditAction::make() + ->label('Düzenle'), + ]) + ->actions([ + Action::make('view_client_portal') + ->label('Müşteri Ekranı') + ->icon('heroicon-o-arrow-top-right-on-square') + ->url(fn ($record) => route('projects.show', $record->slug)) + ->openUrlInNewTab(), + ]) + ->bulkActions([ + BulkActionGroup::make([ + DeleteBulkAction::make() + ->label('Sil'), + ]), + ]) + ->defaultSort('created_at', 'desc'); + } +} diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php new file mode 100644 index 0000000..629ce74 --- /dev/null +++ b/app/Http/Controllers/ProjectController.php @@ -0,0 +1,49 @@ +with(['modules', 'tasks', 'updates' => function($q) { + $q->where('is_public', true)->latest(); + }, 'proposal']) + ->firstOrFail(); + + // Recalculate progress dynamically + $project->recalculateProgress(); + + // Check if access code protection is active and verified in session + $sessionKey = 'project_access_' . $project->id; + $isVerified = session()->get($sessionKey, true); // Verified by default for link convenience + + return view('projects.show', compact('project', 'isVerified')); + } + + /** + * Verify client access code (PIN). + */ + public function verify(Request $request, string $slug) + { + $project = Project::where('slug', $slug)->firstOrFail(); + + $request->validate([ + 'access_code' => 'required|string', + ]); + + if (strtoupper(trim($request->input('access_code'))) === strtoupper($project->client_access_code)) { + session()->put('project_access_' . $project->id, true); + return back()->with('success', 'Erişim doğrulandı.'); + } + + return back()->withErrors(['access_code' => 'Geçersiz müşteri takip şifresi.']); + } +} diff --git a/app/Models/Project.php b/app/Models/Project.php new file mode 100644 index 0000000..5e0ad09 --- /dev/null +++ b/app/Models/Project.php @@ -0,0 +1,92 @@ + 'date', + 'target_date' => 'date', + 'completed_at' => 'datetime', + 'progress_percent' => 'integer', + ]; + + protected static function boot() + { + parent::boot(); + + static::creating(function ($project) { + if (empty($project->slug)) { + $project->slug = Str::slug($project->title) . '-' . Str::random(5); + } + if (empty($project->client_access_code)) { + $project->client_access_code = strtoupper(Str::random(6)); + } + }); + } + + public function proposal() + { + return $this->belongsTo(Proposal::class); + } + + public function modules() + { + return $this->hasMany(ProjectModule::class)->orderBy('order', 'asc'); + } + + public function tasks() + { + return $this->hasMany(ProjectTask::class)->orderBy('order_index', 'asc'); + } + + public function updates() + { + return $this->hasMany(ProjectUpdate::class)->latest(); + } + + /** + * Recalculate progress percentage based on completed modules weight + */ + public function recalculateProgress() + { + $totalWeight = $this->modules()->sum('weight_percent'); + if ($totalWeight > 0) { + $completedWeight = $this->modules()->where('status', 'completed')->sum('weight_percent'); + $progress = (int) round(($completedWeight / $totalWeight) * 100); + } else { + $totalTasks = $this->tasks()->count(); + if ($totalTasks > 0) { + $completedTasks = $this->tasks()->where('status', 'done')->count(); + $progress = (int) round(($completedTasks / $totalTasks) * 100); + } else { + $progress = $this->progress_percent; + } + } + + $this->update(['progress_percent' => min(100, max(0, $progress))]); + return $this->progress_percent; + } +} diff --git a/app/Models/ProjectModule.php b/app/Models/ProjectModule.php new file mode 100644 index 0000000..0ff6e0d --- /dev/null +++ b/app/Models/ProjectModule.php @@ -0,0 +1,39 @@ + 'date', + 'end_date' => 'date', + 'weight_percent' => 'integer', + 'order' => 'integer', + ]; + + public function project() + { + return $this->belongsTo(Project::class); + } + + public function tasks() + { + return $this->hasMany(ProjectTask::class, 'project_module_id'); + } +} diff --git a/app/Models/ProjectTask.php b/app/Models/ProjectTask.php new file mode 100644 index 0000000..92dbd4a --- /dev/null +++ b/app/Models/ProjectTask.php @@ -0,0 +1,38 @@ + 'date', + 'order_index' => 'integer', + ]; + + public function project() + { + return $this->belongsTo(Project::class); + } + + public function module() + { + return $this->belongsTo(ProjectModule::class, 'project_module_id'); + } +} diff --git a/app/Models/ProjectUpdate.php b/app/Models/ProjectUpdate.php new file mode 100644 index 0000000..f79b336 --- /dev/null +++ b/app/Models/ProjectUpdate.php @@ -0,0 +1,35 @@ + 'integer', + 'is_public' => 'boolean', + ]; + + public function project() + { + return $this->belongsTo(Project::class); + } + + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/database/migrations/2026_07_30_172000_create_project_management_tables.php b/database/migrations/2026_07_30_172000_create_project_management_tables.php new file mode 100644 index 0000000..112e2d9 --- /dev/null +++ b/database/migrations/2026_07_30_172000_create_project_management_tables.php @@ -0,0 +1,81 @@ +id(); + $table->foreignId('proposal_id')->nullable()->constrained('proposals')->nullOnDelete(); + $table->string('title'); + $table->string('slug')->unique(); + $table->string('client_name'); + $table->string('client_email')->nullable(); + $table->string('client_access_code')->nullable(); + $table->enum('status', ['planning', 'in_progress', 'on_hold', 'completed', 'cancelled'])->default('in_progress'); + $table->unsignedInteger('progress_percent')->default(0); + $table->date('start_date')->nullable(); + $table->date('target_date')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->text('notes')->nullable(); + $table->timestamps(); + $table->softDeletes(); + }); + + Schema::create('project_modules', function (Blueprint $table) { + $table->id(); + $table->foreignId('project_id')->constrained('projects')->cascadeOnDelete(); + $table->string('title'); + $table->text('description')->nullable(); + $table->unsignedInteger('weight_percent')->default(10); + $table->enum('status', ['pending', 'in_progress', 'completed'])->default('pending'); + $table->date('start_date')->nullable(); + $table->date('end_date')->nullable(); + $table->integer('order')->default(0); + $table->timestamps(); + }); + + Schema::create('project_tasks', function (Blueprint $table) { + $table->id(); + $table->foreignId('project_id')->constrained('projects')->cascadeOnDelete(); + $table->foreignId('project_module_id')->nullable()->constrained('project_modules')->nullOnDelete(); + $table->string('title'); + $table->text('description')->nullable(); + $table->enum('status', ['todo', 'in_progress', 'review', 'done'])->default('todo'); + $table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium'); + $table->date('due_date')->nullable(); + $table->string('assigned_person')->nullable(); + $table->integer('order_index')->default(0); + $table->timestamps(); + }); + + Schema::create('project_updates', function (Blueprint $table) { + $table->id(); + $table->foreignId('project_id')->constrained('projects')->cascadeOnDelete(); + $table->foreignId('user_id')->nullable()->constrained('users')->nullOnDelete(); + $table->string('title'); + $table->text('content'); + $table->unsignedInteger('progress_percent_at_update')->nullable(); + $table->boolean('is_public')->default(true); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('project_updates'); + Schema::dropIfExists('project_tasks'); + Schema::dropIfExists('project_modules'); + Schema::dropIfExists('projects'); + } +}; diff --git a/public/robots.txt b/public/robots.txt index 3479ec3..711ad2e 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -2,5 +2,7 @@ User-agent: * Allow: / Disallow: /admin/ Disallow: /stajyer/admin/ +Disallow: /teklif/ +Disallow: /proje-takip/ Sitemap: https://truncgil.com/sitemap.xml diff --git a/resources/views/projects/show.blade.php b/resources/views/projects/show.blade.php new file mode 100644 index 0000000..7d42e23 --- /dev/null +++ b/resources/views/projects/show.blade.php @@ -0,0 +1,666 @@ + + + + + + + + {{ $project->title }} - Canlı Proje Takip Portalı | Trunçgil Teknoloji + + + + + + + + + + + + + + + + +
+
+ + +
+ + Trunçgil Teknoloji + + +
+
+ Canlı Proje Takip Portalı + {{ $project->client_name }} +
+
+ + +
+ @if($project->proposal) + + + Sözleşme / Teklif + + @endif + + +
+ +
+
+ + +
+ + +
+
+ +
+ +
+
+ + MÜŞTERİ CANLI TAKİP PANELİ +
+ +

+ {{ $project->title }} +

+ +

+ Bu ekran, {{ $project->client_name }} projesine ait iş takvimini, tamamlanan hizmet modüllerini ve canlı Kanban görev akışını şeffaf biçimde sunmaktadır. +

+ + +
+ @if($project->start_date) +
+ + Başlangıç: {{ $project->start_date->format('d.m.Y') }} +
+ @endif + + @if($project->target_date) +
+ + Hedef Bitiş: {{ $project->target_date->format('d.m.Y') }} +
+ @endif + +
+ + Müşteri PIN: {{ $project->client_access_code }} +
+
+
+ + +
+ GENEL PROJE İLERLEMESİ + + +
+ %{{ $project->progress_percent }} +
+ +
+
+
+ + + @if($project->progress_percent >= 100) + 🎉 Proje %100 Başarıyla Tamamlandı! + @elseif($project->progress_percent >= 50) + ⚡ Proje Geliştirmeleri Hızla Devam Ediyor + @else + 🚀 Faz 1 Çalışmaları Başlatıldı + @endif + +
+
+
+ + +
+ +
+ +
+
+

İş Takvimi (Gantt)

+

12 Haftalık Zaman Çizelgesi

+
+
+ + +
+ +
+
+

Hizmet Modülleri

+

{{ $project->modules->where('status', 'completed')->count() }} / {{ $project->modules->count() }} Modül Bitti

+
+
+ + +
+ +
+
+

Kanban Panosu

+

{{ $project->tasks->count() }} Adet Görev Kartı

+
+
+ + +
+ +
+
+

Canlı Log Akışı

+

{{ $project->updates->count() }} İlerleme Duyurusu

+
+
+
+ + +
+
+
+
+ +
+
+

1. İş Takvimi ve Gantt Çizelgesi

+

Proje aşamalarının ve geliştirme süreçlerinin zamansal planı

+
+
+
+ + +
+
+ GANTT ZAMAN ÇİZELGESİ +
+ + 100% + +
+
+ +
+
+gantt + title {{ $project->title }} (İş Takvimi) + dateFormat YYYY-MM-DD + section Faz 1: İş Takip & WhatsApp Otomasyonu + Backend REST API & Veritabanı :a1, 2026-08-01, 21d + Flutter Responsive Kanban Board :a2, after a1, 21d + WhatsApp API & AI NLP Parser :a3, after a2, 14d + AI Görsel Analiz & PDF Raporlama :a4, after a3, 14d + section Faz 2: CMS Migrasyonu & Web Sync + Citrus CMS Migrasyonu & Formlar :b1, 2026-10-10, 7d + Portfolyo Web Sync & Nihai Testler :b2, after b1, 7d +
+
+
+
+ + +
+
+
+
+ +
+
+

2. Hizmet Modülleri İlerleme Matrisi

+

Sözleşmedeki hizmet kalemlerinin tamamlanma ve gidişat durumları

+
+
+ + Toplam {{ $project->modules->count() }} Modül + +
+ + +
+ @forelse($project->modules as $module) +
+
+
+ @if($module->status === 'completed') +
+ +
+ @elseif($module->status === 'in_progress') +
+ +
+ @else +
+ +
+ @endif +

+ {{ $module->title }} +

+
+ + + @if($module->status === 'completed') TAMAMLANDI @elseif($module->status === 'in_progress') DEVAM EDİYOR @else BEKLİYOR @endif + +
+ + @if($module->description) +

+ {{ $module->description }} +

+ @endif + +
+ Ağırlık: %{{ $module->weight_percent }} + @if($module->start_date && $module->end_date) + {{ $module->start_date->format('d.m') }} - {{ $module->end_date->format('d.m.Y') }} + @endif +
+
+ @empty +
+ Henüz tanımlanmış modül bulunmamaktadır. +
+ @endforelse +
+
+ + +
+
+
+
+ +
+
+

3. Canlı Kanban Görev Panosu

+

Mühendislik ekibinin anlık görev statüleri ve çalışma kartları

+
+
+
+ + +
+ + +
+
+ + Yapılacaklar + + + {{ $project->tasks->where('status', 'todo')->count() }} + +
+ + @forelse($project->tasks->where('status', 'todo') as $task) +
+

{{ $task->title }}

+ @if($task->description) +

{{ $task->description }}

+ @endif +
+ {{ $task->assigned_person ?? 'Atanmadı' }} + {{ $task->priority }} +
+
+ @empty +

Görev yok

+ @endforelse +
+ + +
+
+ + Devam Edenler + + + {{ $project->tasks->where('status', 'in_progress')->count() }} + +
+ + @forelse($project->tasks->where('status', 'in_progress') as $task) +
+

{{ $task->title }}

+ @if($task->description) +

{{ $task->description }}

+ @endif +
+ {{ $task->assigned_person ?? 'Yazılım Ekibi' }} + {{ $task->priority }} +
+
+ @empty +

Görev yok

+ @endforelse +
+ + +
+
+ + Kontrol / Test + + + {{ $project->tasks->where('status', 'review')->count() }} + +
+ + @forelse($project->tasks->where('status', 'review') as $task) +
+

{{ $task->title }}

+ @if($task->description) +

{{ $task->description }}

+ @endif +
+ {{ $task->assigned_person ?? 'Test Ekibi' }} + {{ $task->priority }} +
+
+ @empty +

Görev yok

+ @endforelse +
+ + +
+
+ + Tamamlananlar + + + {{ $project->tasks->where('status', 'done')->count() }} + +
+ + @forelse($project->tasks->where('status', 'done') as $task) +
+

{{ $task->title }}

+ @if($task->description) +

{{ $task->description }}

+ @endif +
+ Tamamlandı + +
+
+ @empty +

Görev yok

+ @endforelse +
+ +
+
+ + +
+
+
+
+ +
+
+

4. Canlı İlerleme Duyuruları & Log Akışı

+

Yazılım yöneticisinden gelen canlı proje güncellemeleri

+
+
+
+ + +
+ @forelse($project->updates as $update) +
+ +
+ +
+
+

+ {{ $update->title }} +

+ + {{ $update->created_at->format('d.m.Y H:i') }} + +
+ +

+ {{ $update->content }} +

+ + @if($update->progress_percent_at_update !== null) +
+ + Güncelleme Anındaki İlerleme: %{{ $update->progress_percent_at_update }} +
+ @endif +
+
+ @empty +
+ Henüz yayınlanmış bir duyuru bulunmamaktadır. +
+ @endforelse +
+
+ +
+ + + + + + + + + + + + diff --git a/resources/views/proposals/show.blade.php b/resources/views/proposals/show.blade.php index f5e1189..ea710d6 100644 --- a/resources/views/proposals/show.blade.php +++ b/resources/views/proposals/show.blade.php @@ -4,6 +4,7 @@ + {{ $proposal->title }} - Trunçgil Teknoloji diff --git a/routes/web.php b/routes/web.php index 9fc5353..406cef8 100644 --- a/routes/web.php +++ b/routes/web.php @@ -174,6 +174,17 @@ Route::get('/sitemap.xml', [\App\Http\Controllers\SitemapController::class, 'ind Route::get('/teklif/{slug}', [\App\Http\Controllers\ProposalController::class, 'show'])->name('proposals.show'); Route::post('/teklif/{slug}/action', [\App\Http\Controllers\ProposalController::class, 'action'])->name('proposals.action'); +// Project Tracking Client Portal & Web Admin Management +Route::get('/proje-takip/{slug}', [\App\Http\Controllers\ProjectController::class, 'show'])->name('projects.show'); +Route::post('/proje-takip/{slug}/verify', [\App\Http\Controllers\ProjectController::class, 'verify'])->name('projects.verify'); +Route::post('/proje-takip/{slug}/admin/module-status', [\App\Http\Controllers\ProjectController::class, 'updateModuleStatus'])->name('projects.admin.module-status'); +Route::post('/proje-takip/{slug}/admin/task-status', [\App\Http\Controllers\ProjectController::class, 'updateTaskStatus'])->name('projects.admin.task-status'); +Route::post('/proje-takip/{slug}/admin/add-task', [\App\Http\Controllers\ProjectController::class, 'addTask'])->name('projects.admin.add-task'); +Route::post('/proje-takip/{slug}/admin/delete-task', [\App\Http\Controllers\ProjectController::class, 'deleteTask'])->name('projects.admin.delete-task'); +Route::post('/proje-takip/{slug}/admin/add-update', [\App\Http\Controllers\ProjectController::class, 'addUpdate'])->name('projects.admin.add-update'); +Route::post('/proje-takip/{slug}/admin/recalculate', [\App\Http\Controllers\ProjectController::class, 'recalculate'])->name('projects.admin.recalculate'); +Route::post('/proje-takip/{slug}/admin/toggle-mode', [\App\Http\Controllers\ProjectController::class, 'toggleAdminMode'])->name('projects.admin.toggle-mode'); + // Privacy Policy Alternatives Route::get('/privacy-policy', function () { return app(PageController::class)->show('privacy');