Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 42fcc5759f | |||
| 161d1c86bc | |||
| 4af8900a78 | |||
| abcf105d07 | |||
| d0292af20c | |||
| 26670fcca5 | |||
| f557d4b91c | |||
| 09e92506fd | |||
| 129dae4570 | |||
| f0a9180a54 |
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ResetUserPassword extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'user:reset-password {email? : Kullanıcının e-posta adresi} {password? : Yeni şifre (Belirtilmezse etkileşimli olarak istenir veya otomatik oluşturulur)}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Belirtilen kullanıcının şifresini günceller';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle(): int
|
||||||
|
{
|
||||||
|
// E-posta adresini al veya sor
|
||||||
|
$email = $this->argument('email') ?: $this->ask('Kullanıcının e-posta adresini giriniz:');
|
||||||
|
|
||||||
|
if (empty($email)) {
|
||||||
|
$this->error('E-posta adresi boş olamaz!');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Email formatını kontrol et
|
||||||
|
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$this->error('Geçersiz e-posta formatı!');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kullanıcıyı bul
|
||||||
|
$user = User::where('email', $email)->first();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
$this->error("E-posta adresi '{$email}' ile kayıtlı kullanıcı bulunamadı!");
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Şifreyi al veya sor ya da rastgele oluştur
|
||||||
|
$password = $this->argument('password');
|
||||||
|
|
||||||
|
if ($password === null) {
|
||||||
|
if ($this->confirm('Yeni şifreyi otomatik rastgele mi üretelim (6 haneli rakamsal)?', true)) {
|
||||||
|
$password = (string) random_int(100000, 999999);
|
||||||
|
} else {
|
||||||
|
$password = $this->secret('Yeni şifreyi giriniz:');
|
||||||
|
if (empty($password)) {
|
||||||
|
$this->error('Şifre boş olamaz!');
|
||||||
|
return self::FAILURE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Şifreyi güncelle ve kaydet
|
||||||
|
$user->password = Hash::make($password);
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$this->info("✓ Kullanıcı '{$user->name}' ({$user->email}) şifresi başarıyla güncellendi!");
|
||||||
|
$this->info("Yeni Şifre: {$password}");
|
||||||
|
|
||||||
|
return self::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -510,10 +510,4 @@ class InternApplicationResource extends Resource
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getWidgets(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
Widgets\InternGanttChartWidget::class,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,4 @@ use Filament\Resources\Pages\ListRecords;
|
|||||||
class ListInternApplications extends ListRecords
|
class ListInternApplications extends ListRecords
|
||||||
{
|
{
|
||||||
protected static string $resource = InternApplicationResource::class;
|
protected static string $resource = InternApplicationResource::class;
|
||||||
|
|
||||||
protected function getHeaderWidgets(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
\App\Filament\Admin\Resources\InternApplications\Widgets\InternGanttChartWidget::class,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-174
@@ -1,175 +1,2 @@
|
|||||||
<?php
|
<?php
|
||||||
|
// Bu bileşen devre dışı bırakılmıştır.
|
||||||
namespace App\Filament\Admin\Resources\InternApplications\Widgets;
|
|
||||||
|
|
||||||
use App\Models\CareerApplication;
|
|
||||||
use Filament\Widgets\Widget;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
|
|
||||||
class InternGanttChartWidget extends Widget
|
|
||||||
{
|
|
||||||
protected string $view = 'filament.admin.resources.intern-applications.widgets.intern-gantt-chart-widget';
|
|
||||||
|
|
||||||
protected int | string | array $columnSpan = 'full';
|
|
||||||
|
|
||||||
public function getViewData(): array
|
|
||||||
{
|
|
||||||
$interns = CareerApplication::query()
|
|
||||||
->where('type', 'internship')
|
|
||||||
->where('status', 'accepted')
|
|
||||||
->whereNotNull('internship_start_date')
|
|
||||||
->whereNotNull('internship_end_date')
|
|
||||||
->orderBy('internship_start_date', 'asc')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
if ($interns->isEmpty()) {
|
|
||||||
return [
|
|
||||||
'interns' => [],
|
|
||||||
'months' => [],
|
|
||||||
'totalDays' => 0,
|
|
||||||
'minDate' => null,
|
|
||||||
'todayPercent' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$minDate = Carbon::parse($interns->min('internship_start_date'))->startOfDay();
|
|
||||||
$maxDate = Carbon::parse($interns->max('internship_end_date'))->endOfDay();
|
|
||||||
|
|
||||||
// Pad the range slightly for better visual presentation
|
|
||||||
$minDate = $minDate->copy()->subDays(5);
|
|
||||||
$maxDate = $maxDate->copy()->addDays(5);
|
|
||||||
|
|
||||||
$totalDays = $minDate->diffInDays($maxDate);
|
|
||||||
if ($totalDays <= 0) {
|
|
||||||
$totalDays = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate months in range
|
|
||||||
$months = [];
|
|
||||||
$tempDate = $minDate->copy()->startOfMonth();
|
|
||||||
$endLimit = $maxDate->copy()->endOfMonth();
|
|
||||||
|
|
||||||
while ($tempDate->lte($endLimit)) {
|
|
||||||
$monthStart = $tempDate->copy()->startOfMonth();
|
|
||||||
$monthEnd = $tempDate->copy()->endOfMonth();
|
|
||||||
|
|
||||||
$overlapStart = $monthStart->gt($minDate) ? $monthStart : $minDate;
|
|
||||||
$overlapEnd = $monthEnd->lt($maxDate) ? $monthEnd : $maxDate;
|
|
||||||
|
|
||||||
if ($overlapStart->lte($overlapEnd)) {
|
|
||||||
$daysInOverlap = $overlapStart->diffInDays($overlapEnd) + 1;
|
|
||||||
$startOffset = $minDate->diffInDays($overlapStart);
|
|
||||||
$months[] = [
|
|
||||||
'name' => $tempDate->translatedFormat('F Y'),
|
|
||||||
'left' => ($startOffset / $totalDays) * 100,
|
|
||||||
'width' => ($daysInOverlap / $totalDays) * 100,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$tempDate->addMonth();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate today's line position if it falls within the range
|
|
||||||
$today = Carbon::today();
|
|
||||||
$todayPercent = null;
|
|
||||||
if ($today->between($minDate, $maxDate)) {
|
|
||||||
$todayPercent = ($minDate->diffInDays($today) / $totalDays) * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
$formattedInterns = $interns->map(function ($intern) use ($minDate, $totalDays) {
|
|
||||||
$start = Carbon::parse($intern->internship_start_date)->startOfDay();
|
|
||||||
$end = Carbon::parse($intern->internship_end_date)->endOfDay();
|
|
||||||
|
|
||||||
$startOffset = $minDate->diffInDays($start);
|
|
||||||
$duration = $start->diffInDays($end) + 1;
|
|
||||||
|
|
||||||
$left = ($startOffset / $totalDays) * 100;
|
|
||||||
$width = ($duration / $totalDays) * 100;
|
|
||||||
|
|
||||||
// Ensure width is at least a visible sliver
|
|
||||||
if ($width < 1.5) {
|
|
||||||
$width = 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign a stable color palette based on the intern ID
|
|
||||||
$palettes = [
|
|
||||||
[
|
|
||||||
'bg' => 'bg-emerald-500 dark:bg-emerald-600',
|
|
||||||
'gradient' => 'from-emerald-400 to-teal-500 dark:from-emerald-500 dark:to-teal-600',
|
|
||||||
'text' => 'text-emerald-950 dark:text-emerald-50',
|
|
||||||
'border' => 'border-emerald-500/20',
|
|
||||||
'shadow' => 'shadow-emerald-500/20'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'bg' => 'bg-blue-500 dark:bg-blue-600',
|
|
||||||
'gradient' => 'from-blue-400 to-indigo-500 dark:from-blue-500 dark:to-indigo-600',
|
|
||||||
'text' => 'text-blue-950 dark:text-blue-50',
|
|
||||||
'border' => 'border-blue-500/20',
|
|
||||||
'shadow' => 'shadow-blue-500/20'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'bg' => 'bg-rose-500 dark:bg-rose-600',
|
|
||||||
'gradient' => 'from-rose-400 to-pink-500 dark:from-rose-500 dark:to-pink-600',
|
|
||||||
'text' => 'text-rose-950 dark:text-rose-50',
|
|
||||||
'border' => 'border-rose-500/20',
|
|
||||||
'shadow' => 'shadow-rose-500/20'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'bg' => 'bg-amber-500 dark:bg-amber-600',
|
|
||||||
'gradient' => 'from-amber-400 to-orange-500 dark:from-amber-500 dark:to-orange-600',
|
|
||||||
'text' => 'text-amber-950 dark:text-amber-50',
|
|
||||||
'border' => 'border-amber-500/20',
|
|
||||||
'shadow' => 'shadow-amber-500/20'
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'bg' => 'bg-violet-500 dark:bg-violet-600',
|
|
||||||
'gradient' => 'from-violet-400 to-purple-500 dark:from-violet-500 dark:to-purple-600',
|
|
||||||
'text' => 'text-violet-950 dark:text-violet-50',
|
|
||||||
'border' => 'border-violet-500/20',
|
|
||||||
'shadow' => 'shadow-violet-500/20'
|
|
||||||
],
|
|
||||||
];
|
|
||||||
|
|
||||||
$paletteIndex = $intern->id % count($palettes);
|
|
||||||
$color = $palettes[$paletteIndex];
|
|
||||||
|
|
||||||
$startMonth = (int) $start->format('n');
|
|
||||||
$endMonth = (int) $end->format('n');
|
|
||||||
$gridStart = $startMonth + 1;
|
|
||||||
$gridEnd = min(14, $endMonth + 2);
|
|
||||||
|
|
||||||
// Background color map based on user's examples
|
|
||||||
$hexColors = [
|
|
||||||
'#2ecaac', // Emerald Teal
|
|
||||||
'#54c6f9', // Sky Blue
|
|
||||||
'#ff6252', // Red Coral
|
|
||||||
'#f59e0b', // Amber
|
|
||||||
'#8b5cf6', // Violet
|
|
||||||
];
|
|
||||||
$hexColor = $hexColors[$intern->id % count($hexColors)];
|
|
||||||
$isStripes = ($intern->id % 2 === 0);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'id' => $intern->id,
|
|
||||||
'name' => $intern->name,
|
|
||||||
'start_date' => $start->translatedFormat('d M Y'),
|
|
||||||
'end_date' => $end->translatedFormat('d M Y'),
|
|
||||||
'total_days' => $intern->internship_total_days,
|
|
||||||
'left' => $left,
|
|
||||||
'width' => $width,
|
|
||||||
'color' => $color,
|
|
||||||
'grid_start' => $gridStart,
|
|
||||||
'grid_end' => $gridEnd,
|
|
||||||
'hex_color' => $hexColor,
|
|
||||||
'is_stripes' => $isStripes,
|
|
||||||
'raw_start' => $intern->internship_start_date,
|
|
||||||
'raw_end' => $intern->internship_end_date,
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
return [
|
|
||||||
'interns' => $formattedInterns,
|
|
||||||
'months' => $months,
|
|
||||||
'todayPercent' => $todayPercent,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -208,22 +208,35 @@ class CareerController extends Controller
|
|||||||
|
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'github_repo' => 'required|string|url|max:255',
|
'github_repo' => 'required|string|url|max:255',
|
||||||
|
'github_username' => 'nullable|string|max:100',
|
||||||
], [
|
], [
|
||||||
'github_repo.required' => 'Lütfen Github depo URL\'sini girin.',
|
'github_repo.required' => 'Lütfen Github depo URL\'sini girin.',
|
||||||
'github_repo.url' => 'Geçerli bir URL girilmelidir.',
|
'github_repo.url' => 'Geçerli bir URL girilmelidir.',
|
||||||
'github_repo.max' => 'Github depo URL\'si en fazla 255 karakter olabilir.',
|
'github_repo.max' => 'Github depo URL\'si en fazla 255 karakter olabilir.',
|
||||||
|
'github_username.max' => 'Github kullanıcı adı en fazla 100 karakter olabilir.',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||||
|
|
||||||
// Sanitize & format github URL
|
// Sanitize & format github URL
|
||||||
$repoUrl = trim($request->github_repo);
|
$repoUrl = trim($request->github_repo);
|
||||||
|
$username = trim($request->github_username);
|
||||||
|
|
||||||
$intern->update([
|
$intern->update([
|
||||||
'github_repo' => $repoUrl
|
'github_repo' => $repoUrl,
|
||||||
|
'github_username' => $username ?: null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Github deposu başarıyla güncellendi.');
|
if ($request->expectsJson() || $request->ajax()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Github bilgileri başarıyla güncellendi.',
|
||||||
|
'github_repo' => $repoUrl,
|
||||||
|
'github_username' => $username ?: null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Github bilgileri başarıyla güncellendi.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadMarkdown()
|
public function downloadMarkdown()
|
||||||
@@ -393,6 +406,7 @@ class CareerController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$allInterns = CareerApplication::where('type', 'internship')
|
$allInterns = CareerApplication::where('type', 'internship')
|
||||||
|
->with(['journalEntries'])
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
@@ -493,6 +507,8 @@ class CareerController extends Controller
|
|||||||
], [
|
], [
|
||||||
'content' => $request->content,
|
'content' => $request->content,
|
||||||
'is_retroactive' => $isRetroactive,
|
'is_retroactive' => $isRetroactive,
|
||||||
|
'supervisor_approved' => false,
|
||||||
|
'supervisor_name' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
@@ -520,6 +536,15 @@ class CareerController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
||||||
|
|
||||||
|
$dayFilter = $request->query('day');
|
||||||
|
if ($dayFilter) {
|
||||||
|
$days = array_filter($days, function($d) use ($dayFilter) {
|
||||||
|
return $d['day_number'] == $dayFilter;
|
||||||
|
});
|
||||||
|
$days = array_values($days);
|
||||||
|
}
|
||||||
|
|
||||||
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
||||||
|
|
||||||
return view('front.career.print', [
|
return view('front.career.print', [
|
||||||
@@ -606,5 +631,114 @@ class CareerController extends Controller
|
|||||||
'message' => 'Staj sorumlusu onayı güncellendi.'
|
'message' => 'Staj sorumlusu onayı güncellendi.'
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getInternJournalDetails(Request $request)
|
||||||
|
{
|
||||||
|
if (!auth()->check()) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Yetkisiz işlem.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'intern_id' => 'required|integer|exists:career_applications,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$intern = CareerApplication::findOrFail($request->intern_id);
|
||||||
|
|
||||||
|
// Get all days calculated
|
||||||
|
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
||||||
|
|
||||||
|
// Get saved journal entries
|
||||||
|
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
||||||
|
|
||||||
|
// Prepare entries list matched by day number
|
||||||
|
$entries = [];
|
||||||
|
$filledDaysCount = 0;
|
||||||
|
|
||||||
|
foreach ($days as $d) {
|
||||||
|
$dayNum = $d['day_number'];
|
||||||
|
$entry = $savedEntries->get($dayNum);
|
||||||
|
|
||||||
|
$hasSaved = $entry && trim($entry->content) !== '';
|
||||||
|
if ($hasSaved) {
|
||||||
|
$filledDaysCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$entries[] = [
|
||||||
|
'day_number' => $dayNum,
|
||||||
|
'date' => $d['date'],
|
||||||
|
'formatted_date' => $d['formatted_date'],
|
||||||
|
'filled' => $hasSaved,
|
||||||
|
'entry_id' => $entry ? $entry->id : null,
|
||||||
|
'content' => $entry ? $entry->content : null,
|
||||||
|
'is_retroactive' => $entry ? $entry->is_retroactive : false,
|
||||||
|
'supervisor_approved' => $entry ? (bool)$entry->supervisor_approved : false,
|
||||||
|
'supervisor_name' => $entry ? $entry->supervisor_name : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'intern' => [
|
||||||
|
'id' => $intern->id,
|
||||||
|
'name' => $intern->name,
|
||||||
|
'email' => $intern->email,
|
||||||
|
'phone' => $intern->phone,
|
||||||
|
'start_date' => $intern->internship_start_date,
|
||||||
|
'end_date' => $intern->internship_end_date,
|
||||||
|
'total_days' => $intern->internship_total_days,
|
||||||
|
'filled_days' => $filledDaysCount,
|
||||||
|
'notebook_supervisor_signed' => (bool)$intern->notebook_supervisor_signed,
|
||||||
|
'notebook_supervisor_name' => $intern->notebook_supervisor_name,
|
||||||
|
'notebook_unit_signed' => (bool)$intern->notebook_unit_signed,
|
||||||
|
'notebook_unit_name' => $intern->notebook_unit_name,
|
||||||
|
'notebook_approved' => (bool)$intern->notebook_approved,
|
||||||
|
],
|
||||||
|
'entries' => $entries,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toggleNotebookSignature(Request $request)
|
||||||
|
{
|
||||||
|
if (!auth()->check()) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Yetkisiz işlem.'], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'intern_id' => 'required|integer|exists:career_applications,id',
|
||||||
|
'type' => 'required|string|in:supervisor,unit,approved',
|
||||||
|
'signed' => 'required|boolean',
|
||||||
|
'name' => 'nullable|string|max:255',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$intern = CareerApplication::findOrFail($request->intern_id);
|
||||||
|
|
||||||
|
$type = $request->type;
|
||||||
|
$signed = $request->signed;
|
||||||
|
$name = $request->name ?: auth()->user()->name;
|
||||||
|
|
||||||
|
if ($type === 'supervisor') {
|
||||||
|
$intern->notebook_supervisor_signed = $signed;
|
||||||
|
$intern->notebook_supervisor_name = $signed ? $name : null;
|
||||||
|
} elseif ($type === 'unit') {
|
||||||
|
$intern->notebook_unit_signed = $signed;
|
||||||
|
$intern->notebook_unit_name = $signed ? $name : null;
|
||||||
|
} elseif ($type === 'approved') {
|
||||||
|
$intern->notebook_approved = $signed;
|
||||||
|
}
|
||||||
|
|
||||||
|
$intern->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Staj defteri onay durumu güncellendi.',
|
||||||
|
'intern' => [
|
||||||
|
'notebook_supervisor_signed' => (bool)$intern->notebook_supervisor_signed,
|
||||||
|
'notebook_supervisor_name' => $intern->notebook_supervisor_name,
|
||||||
|
'notebook_unit_signed' => (bool)$intern->notebook_unit_signed,
|
||||||
|
'notebook_unit_name' => $intern->notebook_unit_name,
|
||||||
|
'notebook_approved' => (bool)$intern->notebook_approved,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,24 +12,50 @@ class SitemapController extends Controller
|
|||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
$urls = [];
|
$urls = [];
|
||||||
|
$activeLanguages = [];
|
||||||
|
if (class_exists(\App\Models\Language::class)) {
|
||||||
|
$activeLanguages = \App\Models\Language::where('is_active', true)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
$getAlternates = function (string $baseUrl) use ($activeLanguages) {
|
||||||
|
$alternates = [];
|
||||||
|
foreach ($activeLanguages as $lang) {
|
||||||
|
$connector = str_contains($baseUrl, '?') ? '&' : '?';
|
||||||
|
$alternates[] = [
|
||||||
|
'hreflang' => $lang->code,
|
||||||
|
'href' => $baseUrl . $connector . 'locale=' . $lang->code,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$alternates[] = [
|
||||||
|
'hreflang' => 'x-default',
|
||||||
|
'href' => $baseUrl,
|
||||||
|
];
|
||||||
|
return $alternates;
|
||||||
|
};
|
||||||
|
|
||||||
// 1. Static & Main Pages
|
// 1. Static & Main Pages
|
||||||
|
$homeUrl = url('/');
|
||||||
$urls[] = [
|
$urls[] = [
|
||||||
'loc' => url('/'),
|
'loc' => $homeUrl,
|
||||||
|
'alternates' => $getAlternates($homeUrl),
|
||||||
'lastmod' => now()->startOfDay()->toAtomString(),
|
'lastmod' => now()->startOfDay()->toAtomString(),
|
||||||
'changefreq' => 'daily',
|
'changefreq' => 'daily',
|
||||||
'priority' => '1.0',
|
'priority' => '1.0',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$blogIndexUrl = route('blog.index');
|
||||||
$urls[] = [
|
$urls[] = [
|
||||||
'loc' => route('blog.index'),
|
'loc' => $blogIndexUrl,
|
||||||
|
'alternates' => $getAlternates($blogIndexUrl),
|
||||||
'lastmod' => now()->startOfDay()->toAtomString(),
|
'lastmod' => now()->startOfDay()->toAtomString(),
|
||||||
'changefreq' => 'weekly',
|
'changefreq' => 'weekly',
|
||||||
'priority' => '0.8',
|
'priority' => '0.8',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
$careerIndexUrl = route('career.index');
|
||||||
$urls[] = [
|
$urls[] = [
|
||||||
'loc' => route('career.index'),
|
'loc' => $careerIndexUrl,
|
||||||
|
'alternates' => $getAlternates($careerIndexUrl),
|
||||||
'lastmod' => now()->startOfMonth()->toAtomString(),
|
'lastmod' => now()->startOfMonth()->toAtomString(),
|
||||||
'changefreq' => 'monthly',
|
'changefreq' => 'monthly',
|
||||||
'priority' => '0.5',
|
'priority' => '0.5',
|
||||||
@@ -40,8 +66,10 @@ class SitemapController extends Controller
|
|||||||
->where('is_homepage', false)
|
->where('is_homepage', false)
|
||||||
->get();
|
->get();
|
||||||
foreach ($pages as $page) {
|
foreach ($pages as $page) {
|
||||||
|
$pageUrl = url($page->slug);
|
||||||
$urls[] = [
|
$urls[] = [
|
||||||
'loc' => url($page->slug),
|
'loc' => $pageUrl,
|
||||||
|
'alternates' => $getAlternates($pageUrl),
|
||||||
'lastmod' => $page->updated_at->toAtomString(),
|
'lastmod' => $page->updated_at->toAtomString(),
|
||||||
'changefreq' => 'weekly',
|
'changefreq' => 'weekly',
|
||||||
'priority' => '0.7',
|
'priority' => '0.7',
|
||||||
@@ -52,8 +80,10 @@ class SitemapController extends Controller
|
|||||||
if (class_exists(Blog::class)) {
|
if (class_exists(Blog::class)) {
|
||||||
$posts = Blog::published()->get();
|
$posts = Blog::published()->get();
|
||||||
foreach ($posts as $post) {
|
foreach ($posts as $post) {
|
||||||
|
$postUrl = route('blog.show', $post->slug);
|
||||||
$urls[] = [
|
$urls[] = [
|
||||||
'loc' => route('blog.show', $post->slug),
|
'loc' => $postUrl,
|
||||||
|
'alternates' => $getAlternates($postUrl),
|
||||||
'lastmod' => $post->updated_at->toAtomString(),
|
'lastmod' => $post->updated_at->toAtomString(),
|
||||||
'changefreq' => 'weekly',
|
'changefreq' => 'weekly',
|
||||||
'priority' => '0.6',
|
'priority' => '0.6',
|
||||||
@@ -65,8 +95,10 @@ class SitemapController extends Controller
|
|||||||
if (class_exists(Product::class)) {
|
if (class_exists(Product::class)) {
|
||||||
$products = Product::where('is_active', true)->get();
|
$products = Product::where('is_active', true)->get();
|
||||||
foreach ($products as $product) {
|
foreach ($products as $product) {
|
||||||
|
$productUrl = route('products.show', $product->slug);
|
||||||
$urls[] = [
|
$urls[] = [
|
||||||
'loc' => route('products.show', $product->slug),
|
'loc' => $productUrl,
|
||||||
|
'alternates' => $getAlternates($productUrl),
|
||||||
'lastmod' => $product->updated_at->toAtomString(),
|
'lastmod' => $product->updated_at->toAtomString(),
|
||||||
'changefreq' => 'weekly',
|
'changefreq' => 'weekly',
|
||||||
'priority' => '0.7',
|
'priority' => '0.7',
|
||||||
|
|||||||
@@ -16,10 +16,25 @@ class SetLocale
|
|||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
// Session'dan locale'i al, yoksa varsayılan dil kodunu kullan
|
// Aktif dilleri veritabanından kontrol et
|
||||||
$locale = session('locale');
|
if (function_exists('available_language_codes')) {
|
||||||
|
$availableLocales = available_language_codes();
|
||||||
|
} else {
|
||||||
|
$availableLocales = ['tr', 'en', 'de', 'ar', 'se', 'ru'];
|
||||||
|
}
|
||||||
|
|
||||||
// Eğer session'da locale yoksa, varsayılan dil kodunu kullan
|
// Query parametresinden al, yoksa session'dan al
|
||||||
|
$queryLocale = $request->query('locale') ?? $request->query('lang');
|
||||||
|
$locale = null;
|
||||||
|
|
||||||
|
if ($queryLocale && in_array($queryLocale, $availableLocales)) {
|
||||||
|
$locale = $queryLocale;
|
||||||
|
session(['locale' => $locale]);
|
||||||
|
} else {
|
||||||
|
$locale = session('locale');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eğer locale hala atanmadıysa varsayılan dil kodunu kullan
|
||||||
if (!$locale) {
|
if (!$locale) {
|
||||||
if (function_exists('default_language_code')) {
|
if (function_exists('default_language_code')) {
|
||||||
$locale = default_language_code();
|
$locale = default_language_code();
|
||||||
@@ -28,14 +43,7 @@ class SetLocale
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Aktif dilleri veritabanından kontrol et
|
// Locale'i aktif diller arasında kontrol et ve ata
|
||||||
if (function_exists('available_language_codes')) {
|
|
||||||
$availableLocales = available_language_codes();
|
|
||||||
} else {
|
|
||||||
$availableLocales = ['tr', 'en'];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Locale'i aktif diller arasında kontrol et
|
|
||||||
if (in_array($locale, $availableLocales)) {
|
if (in_array($locale, $availableLocales)) {
|
||||||
App::setLocale($locale);
|
App::setLocale($locale);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ class CareerApplication extends Model
|
|||||||
'internship_end_date',
|
'internship_end_date',
|
||||||
'internship_total_days',
|
'internship_total_days',
|
||||||
'github_repo',
|
'github_repo',
|
||||||
|
'github_username',
|
||||||
'certificate_code',
|
'certificate_code',
|
||||||
'transcript_markdown',
|
'transcript_markdown',
|
||||||
'notebook_supervisor_signed',
|
'notebook_supervisor_signed',
|
||||||
|
|||||||
@@ -85,6 +85,13 @@ class AdminPanelProvider extends PanelProvider
|
|||||||
->sort(100); // En sonda görünsün
|
->sort(100); // En sonda görünsün
|
||||||
})->toArray()
|
})->toArray()
|
||||||
])
|
])
|
||||||
|
->navigationItems([
|
||||||
|
\Filament\Navigation\NavigationItem::make('Admin Stajyer Paneli')
|
||||||
|
->url('https://truncgil.com/stajyer/admin/panel')
|
||||||
|
->openUrlInNewTab()
|
||||||
|
->icon('heroicon-o-academic-cap')
|
||||||
|
->sort(1000),
|
||||||
|
])
|
||||||
->assets([
|
->assets([
|
||||||
\Filament\Support\Assets\Css::make('citrus', resource_path('css/citrus.css')),
|
\Filament\Support\Assets\Css::make('citrus', resource_path('css/citrus.css')),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -63,10 +63,42 @@ abstract class StructuredData
|
|||||||
$siteName = static::siteName();
|
$siteName = static::siteName();
|
||||||
|
|
||||||
$node = [
|
$node = [
|
||||||
'@type' => 'Organization',
|
'@type' => 'ProfessionalService',
|
||||||
'@id' => $siteUrl . '#organization',
|
'@id' => $siteUrl . '#organization',
|
||||||
'name' => $siteName,
|
'name' => $siteName,
|
||||||
'url' => $siteUrl,
|
'url' => $siteUrl,
|
||||||
|
'priceRange' => '$$',
|
||||||
|
'geo' => [
|
||||||
|
'@type' => 'GeoCoordinates',
|
||||||
|
'latitude' => 37.025704,
|
||||||
|
'longitude' => 37.296559,
|
||||||
|
],
|
||||||
|
'areaServed' => [
|
||||||
|
[
|
||||||
|
'@type' => 'AdministrativeArea',
|
||||||
|
'name' => 'Gaziantep',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'@type' => 'Country',
|
||||||
|
'name' => 'Turkey',
|
||||||
|
]
|
||||||
|
],
|
||||||
|
'knowsAbout' => [
|
||||||
|
'Yazılım Geliştirme',
|
||||||
|
'Web Tasarım',
|
||||||
|
'Mobil Uygulama Geliştirme',
|
||||||
|
'E-Ticaret Sistemleri',
|
||||||
|
'SEO Danışmanlığı',
|
||||||
|
'Gaziantep Yazılım Şirketleri'
|
||||||
|
],
|
||||||
|
'openingHoursSpecification' => [
|
||||||
|
[
|
||||||
|
'@type' => 'OpeningHoursSpecification',
|
||||||
|
'dayOfWeek' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
||||||
|
'opens' => '09:00',
|
||||||
|
'closes' => '18:00',
|
||||||
|
]
|
||||||
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
$logo = setting('site_logo');
|
$logo = setting('site_logo');
|
||||||
@@ -93,6 +125,10 @@ abstract class StructuredData
|
|||||||
$node['address'] = [
|
$node['address'] = [
|
||||||
'@type' => 'PostalAddress',
|
'@type' => 'PostalAddress',
|
||||||
'streetAddress' => $address,
|
'streetAddress' => $address,
|
||||||
|
'addressLocality' => 'Şahinbey',
|
||||||
|
'addressRegion' => 'Gaziantep',
|
||||||
|
'postalCode' => '27190',
|
||||||
|
'addressCountry' => 'TR',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('career_applications', function (Blueprint $table) {
|
||||||
|
$table->string('github_username')->nullable()->after('github_repo');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('career_applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('github_username');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
@import url(https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@1,300;1,400;1,500;1,600;1,700);
|
@import url(https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@1,300;1,400;1,500;1,600;1,700&display=swap);
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Space Grotesk';
|
font-family: 'Space Grotesk';
|
||||||
src: url(../../fonts/space/SpaceGrotesk-SemiBold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-SemiBold.woff) format('woff');
|
src: url(../../fonts/space/SpaceGrotesk-SemiBold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-SemiBold.woff) format('woff');
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Space Grotesk';
|
font-family: 'Space Grotesk';
|
||||||
src: url(../../fonts/space/SpaceGrotesk-Light.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Light.woff) format('woff');
|
src: url(../../fonts/space/SpaceGrotesk-Light.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Light.woff) format('woff');
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Space Grotesk';
|
font-family: 'Space Grotesk';
|
||||||
src: url(../../fonts/space/SpaceGrotesk-Bold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Bold.woff) format('woff');
|
src: url(../../fonts/space/SpaceGrotesk-Bold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Bold.woff) format('woff');
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Space Grotesk';
|
font-family: 'Space Grotesk';
|
||||||
src: url(../../fonts/space/SpaceGrotesk-Medium.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Medium.woff) format('woff');
|
src: url(../../fonts/space/SpaceGrotesk-Medium.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Medium.woff) format('woff');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Space Grotesk';
|
font-family: 'Space Grotesk';
|
||||||
src: url(../../fonts/space/SpaceGrotesk-Regular.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Regular.woff) format('woff');
|
src: url(../../fonts/space/SpaceGrotesk-Regular.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Regular.woff) format('woff');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
* {
|
* {
|
||||||
word-spacing: normal !important
|
word-spacing: normal !important
|
||||||
|
|||||||
@@ -3,70 +3,70 @@
|
|||||||
src: url(../../fonts/urbanist/Urbanist-BoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-BoldItalic.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-BoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-BoldItalic.woff) format('woff');
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff) format('woff');
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-Medium.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Medium.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-Medium.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Medium.woff) format('woff');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-MediumItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-MediumItalic.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-MediumItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-MediumItalic.woff) format('woff');
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-SemiBold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBold.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-SemiBold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBold.woff) format('woff');
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-Italic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Italic.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-Italic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Italic.woff) format('woff');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-Regular.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Regular.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-Regular.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Regular.woff) format('woff');
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-LightItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-LightItalic.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-LightItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-LightItalic.woff) format('woff');
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-Light.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Light.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-Light.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Light.woff) format('woff');
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: Urbanist;
|
font-family: Urbanist;
|
||||||
src: url(../../fonts/urbanist/Urbanist-Bold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Bold.woff) format('woff');
|
src: url(../../fonts/urbanist/Urbanist-Bold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Bold.woff) format('woff');
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block
|
font-display: swap;
|
||||||
}
|
}
|
||||||
* {
|
* {
|
||||||
word-spacing: normal !important
|
word-spacing: normal !important
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
src:url(Unicons.woff2) format("woff2"),url(Unicons.woff) format("woff");
|
src:url(Unicons.woff2) format("woff2"),url(Unicons.woff) format("woff");
|
||||||
font-weight:400;
|
font-weight:400;
|
||||||
font-style:normal;
|
font-style:normal;
|
||||||
font-display:block
|
font-display:swap;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family:Custom;
|
font-family:Custom;
|
||||||
src:url(../custom/Custom.woff2) format("woff2"),url(../custom/Custom.woff) format("woff");
|
src:url(../custom/Custom.woff2) format("woff2"),url(../custom/Custom.woff) format("woff");
|
||||||
font-weight:400;
|
font-weight:400;
|
||||||
font-style:normal;
|
font-style:normal;
|
||||||
font-display:block
|
font-display:swap;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
google-site-verification: googlef1M6GQLxVA5g7IaVY6kMQCoIrgADR6HjrOZu79CrXYc.html
|
||||||
+1
-31862
File diff suppressed because one or more lines are too long
+5
-1
@@ -1,2 +1,6 @@
|
|||||||
User-agent: *
|
User-agent: *
|
||||||
Disallow:
|
Allow: /
|
||||||
|
Disallow: /admin/
|
||||||
|
Disallow: /stajyer/admin/
|
||||||
|
|
||||||
|
Sitemap: https://truncgil.com/sitemap.xml
|
||||||
|
|||||||
+1
-157
@@ -1,157 +1 @@
|
|||||||
<x-filament-widgets::widget>
|
{{-- Bu bileşen görünümü devre dışı bırakılmıştır. --}}
|
||||||
<!-- DevExtreme Gantt CSS Dependencies -->
|
|
||||||
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx.light.css">
|
|
||||||
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx-gantt.css">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.fi-wi-intern-gantt-chart .demo-container {
|
|
||||||
height: 560px;
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
||||||
margin-top: 12px;
|
|
||||||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03);
|
|
||||||
}
|
|
||||||
.dark .fi-wi-intern-gantt-chart .demo-container {
|
|
||||||
background-color: #111827;
|
|
||||||
border-color: rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
.fi-wi-intern-gantt-chart #gantt {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<x-filament::section class="fi-wi-intern-gantt-chart">
|
|
||||||
<x-slot name="heading">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<span class="text-lg font-bold tracking-tight text-gray-950 dark:text-white">
|
|
||||||
Stajyer Gantt Şeması ve Takvimi
|
|
||||||
</span>
|
|
||||||
<span class="text-xs px-2.5 py-1 bg-emerald-50 text-emerald-600 dark:bg-emerald-950 dark:text-emerald-400 rounded-full font-semibold">
|
|
||||||
DevExtreme Görünümü
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</x-slot>
|
|
||||||
|
|
||||||
@if(count($interns) === 0)
|
|
||||||
<div class="flex flex-col items-center justify-center p-8 text-center bg-gray-50 dark:bg-gray-900/40 rounded-xl border border-dashed border-gray-200 dark:border-gray-800">
|
|
||||||
<div class="p-3 bg-gray-100 dark:bg-gray-800 rounded-full mb-3 text-gray-400">
|
|
||||||
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
|
||||||
Tarihleri belirlenmiş onaylı stajyer bulunamadı.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div
|
|
||||||
x-data="{
|
|
||||||
internsData: @js($interns),
|
|
||||||
loadScript(src) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
if (window.jQuery && src.includes('jquery')) { resolve(); return; }
|
|
||||||
if (window.DevExpress && src.includes('dx.all')) { resolve(); return; }
|
|
||||||
if (window.DevExpress && window.DevExpress.ui && window.DevExpress.ui.dxGantt && src.includes('dx-gantt')) { resolve(); return; }
|
|
||||||
|
|
||||||
const existing = document.querySelector(`script[src='${src}']`);
|
|
||||||
if (existing) {
|
|
||||||
if (existing.readyState === 'loaded' || existing.readyState === 'complete') {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
existing.addEventListener('load', resolve);
|
|
||||||
existing.addEventListener('error', reject);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const script = document.createElement('script');
|
|
||||||
script.src = src;
|
|
||||||
script.async = true;
|
|
||||||
script.onload = resolve;
|
|
||||||
script.onerror = () => reject(new Error('Failed to load script: ' + src));
|
|
||||||
document.head.appendChild(script);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
showError(msg) {
|
|
||||||
const errDiv = document.createElement('div');
|
|
||||||
errDiv.style.color = '#ef4444';
|
|
||||||
errDiv.style.padding = '20px';
|
|
||||||
errDiv.style.fontFamily = 'monospace';
|
|
||||||
errDiv.style.fontSize = '13px';
|
|
||||||
errDiv.innerHTML = '<strong>Gantt Load Error:</strong><br>' + msg;
|
|
||||||
this.$refs.ganttContainer.innerHTML = '';
|
|
||||||
this.$refs.ganttContainer.appendChild(errDiv);
|
|
||||||
},
|
|
||||||
async init() {
|
|
||||||
try {
|
|
||||||
await this.loadScript('https://code.jquery.com/jquery-3.7.1.min.js').catch(() => { throw new Error('jQuery failed to load'); });
|
|
||||||
await this.loadScript('https://cdn3.devexpress.com/jslib/23.2.5/js/dx.all.js').catch(() => { throw new Error('dx.all.js failed to load'); });
|
|
||||||
await this.loadScript('https://cdn3.devexpress.com/jslib/23.2.5/js/dx-gantt.min.js').catch(() => { throw new Error('dx-gantt.min.js failed to load'); });
|
|
||||||
|
|
||||||
if (window.jQuery && window.jQuery.fn.dxGantt) {
|
|
||||||
const formattedData = this.internsData.map(intern => ({
|
|
||||||
id: intern.id,
|
|
||||||
parentId: 0,
|
|
||||||
title: intern.name,
|
|
||||||
start: new Date(intern.raw_start),
|
|
||||||
end: new Date(intern.raw_end),
|
|
||||||
progress: 100
|
|
||||||
}));
|
|
||||||
|
|
||||||
jQuery(this.$refs.ganttContainer).dxGantt({
|
|
||||||
tasks: {
|
|
||||||
dataSource: formattedData,
|
|
||||||
},
|
|
||||||
editing: {
|
|
||||||
enabled: false,
|
|
||||||
},
|
|
||||||
validation: {
|
|
||||||
autoUpdateParentTasks: true,
|
|
||||||
},
|
|
||||||
toolbar: {
|
|
||||||
items: [
|
|
||||||
'collapseAll',
|
|
||||||
'expandAll',
|
|
||||||
'separator',
|
|
||||||
'zoomIn',
|
|
||||||
'zoomOut',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
columns: [{
|
|
||||||
dataField: 'title',
|
|
||||||
caption: 'Stajyer Adı',
|
|
||||||
width: 250,
|
|
||||||
}, {
|
|
||||||
dataField: 'start',
|
|
||||||
caption: 'Başlangıç Tarihi',
|
|
||||||
dataType: 'date',
|
|
||||||
format: 'dd.MM.yyyy',
|
|
||||||
width: 120,
|
|
||||||
}, {
|
|
||||||
dataField: 'end',
|
|
||||||
caption: 'Bitiş Tarihi',
|
|
||||||
dataType: 'date',
|
|
||||||
format: 'dd.MM.yyyy',
|
|
||||||
width: 120,
|
|
||||||
}],
|
|
||||||
scaleType: 'days',
|
|
||||||
taskListWidth: 490,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
throw new Error('DevExpress Gantt component jQuery plugin is not defined after loading scripts.');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
this.showError(e.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
class="dx-viewport demo-container"
|
|
||||||
>
|
|
||||||
<div id="gantt" x-ref="ganttContainer" style="height: 100%; width: 100%;"></div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</x-filament::section>
|
|
||||||
</x-filament-widgets::widget>
|
|
||||||
|
|||||||
@@ -11,15 +11,632 @@
|
|||||||
<!-- Statistics Section -->
|
<!-- Statistics Section -->
|
||||||
@include('front.career.partials.stats')
|
@include('front.career.partials.stats')
|
||||||
|
|
||||||
<!-- Gantt Chart Section -->
|
<!-- Tab Switcher Menu -->
|
||||||
@include('front.career.partials.gantt')
|
<div class="bg-white rounded-3xl p-4 shadow-xl border border-slate-100/50 mb-8">
|
||||||
|
<div class="flex flex-col sm:flex-row gap-3" role="tablist">
|
||||||
|
<button type="button" role="tab" aria-selected="true" data-tab-target="gantt-chart" class="admin-tab-btn active flex-1 flex items-center justify-center gap-3 py-4 px-6 rounded-2xl text-center font-bold text-sm transition-all border border-transparent cursor-pointer">
|
||||||
|
<i class="uil uil-schedule text-lg"></i>
|
||||||
|
<span>Gantt Şeması ve Takvimi</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<!-- Interns Table / Detailed List Section -->
|
<button type="button" role="tab" aria-selected="false" data-tab-target="intern-list" class="admin-tab-btn flex-1 flex items-center justify-center gap-3 py-4 px-6 rounded-2xl text-center font-bold text-sm transition-all border border-transparent text-slate-600 hover:bg-slate-50 cursor-pointer">
|
||||||
|
<i class="uil uil-list-ul text-lg"></i>
|
||||||
|
<span>Stajyer Listesi ve Detaylı Takip</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab Panels -->
|
||||||
|
<div id="gantt-chart-panel" class="admin-tab-panel space-y-6">
|
||||||
|
@include('front.career.partials.gantt')
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="intern-list-panel" class="admin-tab-panel hidden space-y-6">
|
||||||
@include('front.career.partials.list')
|
@include('front.career.partials.list')
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@include('front.career.partials.guide_modal')
|
@include('front.career.partials.guide_modal')
|
||||||
|
<!-- Intern Journal Tracking & Approval Modal -->
|
||||||
|
<div id="intern-journal-modal" class="fixed inset-0 z-[9998] hidden items-center justify-center bg-slate-900/60 backdrop-blur-sm transition-opacity duration-300">
|
||||||
|
<div class="bg-white rounded-3xl shadow-2xl border border-slate-100 max-w-4xl w-full mx-4 overflow-hidden transform scale-95 opacity-0 transition-all duration-300 flex flex-col max-h-[90vh]">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="px-6 py-4 bg-gradient-to-r from-blue-50 to-indigo-50/30 border-b border-slate-100 flex items-center justify-between flex-shrink-0">
|
||||||
|
<div>
|
||||||
|
<h4 id="ijm-name" class="font-bold text-slate-800 text-lg">Stajyer Defteri ve İnceleme</h4>
|
||||||
|
<p id="ijm-meta" class="text-xs text-slate-500 font-semibold mt-0.5"></p>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="closeInternJournalModal()" class="text-slate-400 hover:text-slate-600 transition-colors p-1.5 rounded-full hover:bg-slate-100/80">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Body -->
|
||||||
|
<div class="flex-grow p-6 overflow-y-auto no-scrollbar grid grid-cols-1 md:grid-cols-12 gap-6">
|
||||||
|
|
||||||
|
<!-- Left Sidebar: Progress & General Approvals (col-span-4) -->
|
||||||
|
<div class="md:col-span-4 space-y-6">
|
||||||
|
|
||||||
|
<!-- Progress Bar Card -->
|
||||||
|
<div class="bg-slate-50 rounded-2xl p-5 border border-slate-200/50">
|
||||||
|
<h5 class="font-bold text-slate-700 text-xs uppercase tracking-wider mb-4 flex items-center gap-1.5">
|
||||||
|
<i class="uil uil-chart-bar text-blue-600"></i>
|
||||||
|
<span>Staj İlerlemesi</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<!-- Journal Filling Progress -->
|
||||||
|
<div class="space-y-2 mb-4">
|
||||||
|
<div class="flex justify-between text-xs font-bold">
|
||||||
|
<span class="text-slate-500">Defter Doldurma</span>
|
||||||
|
<span id="ijm-fill-text" class="text-slate-700">0 / 0 Gün</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-200 h-2.5 rounded-full overflow-hidden">
|
||||||
|
<div id="ijm-fill-progress" class="bg-blue-600 h-full rounded-full transition-all duration-500" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Journal Approval Progress -->
|
||||||
|
<div class="space-y-2">
|
||||||
|
<div class="flex justify-between text-xs font-bold">
|
||||||
|
<span class="text-slate-500">Onaylı Günler</span>
|
||||||
|
<span id="ijm-approved-text" class="text-slate-700">0 / 0 Gün</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-200 h-2.5 rounded-full overflow-hidden">
|
||||||
|
<div id="ijm-approved-progress" class="bg-emerald-500 h-full rounded-full transition-all duration-500" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Overall Signatures Card -->
|
||||||
|
<div class="bg-slate-50 rounded-2xl p-5 border border-slate-200/50 space-y-4">
|
||||||
|
<h5 class="font-bold text-slate-700 text-xs uppercase tracking-wider flex items-center gap-1.5">
|
||||||
|
<i class="uil uil-signature text-indigo-600"></i>
|
||||||
|
<span>Resmi Onaylar / İmzalar</span>
|
||||||
|
</h5>
|
||||||
|
|
||||||
|
<!-- Supervisor Signature -->
|
||||||
|
<div class="p-3 bg-white rounded-xl border border-slate-100 flex flex-col gap-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-bold text-slate-600">Sorumlu İmzası</span>
|
||||||
|
<button type="button" id="ijm-btn-supervisor" onclick="toggleNotebookSignature('supervisor')" class="px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border transition-all"></button>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-slate-400 font-semibold" id="ijm-text-supervisor">İmzalanmadı</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Unit Signature -->
|
||||||
|
<div class="p-3 bg-white rounded-xl border border-slate-100 flex flex-col gap-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-bold text-slate-600">Birim Sorumlu İmzası</span>
|
||||||
|
<button type="button" id="ijm-btn-unit" onclick="toggleNotebookSignature('unit')" class="px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border transition-all"></button>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-slate-400 font-semibold" id="ijm-text-unit">İmzalanmadı</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notebook Approved -->
|
||||||
|
<div class="p-3 bg-white rounded-xl border border-slate-100 flex flex-col gap-2">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="text-xs font-bold text-slate-600">Genel Defter Onayı</span>
|
||||||
|
<button type="button" id="ijm-btn-approved" onclick="toggleNotebookSignature('approved')" class="px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border transition-all"></button>
|
||||||
|
</div>
|
||||||
|
<div class="text-[10px] text-slate-400 font-semibold" id="ijm-text-approved">Onaylanmadı</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Section: Carousel & Navigation (col-span-8) -->
|
||||||
|
<div class="md:col-span-8 flex flex-col space-y-4">
|
||||||
|
|
||||||
|
<!-- Carousel Controls -->
|
||||||
|
<div class="bg-slate-50 p-4 rounded-2xl border border-slate-200/50 flex items-center justify-between gap-3">
|
||||||
|
<button type="button" onclick="prevSlide()" class="p-2 bg-white hover:bg-slate-100 border border-slate-200 text-slate-600 hover:text-slate-900 rounded-xl transition-all flex items-center justify-center cursor-pointer">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M15 19l-7-7 7-7"/></svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="flex-grow flex items-center justify-center gap-3">
|
||||||
|
<span id="ijm-day-indicator" class="text-sm font-extrabold text-blue-600 bg-blue-50 px-3 py-1.5 rounded-xl">Gün: 0 / 0</span>
|
||||||
|
|
||||||
|
<!-- Quick Day Dropdown -->
|
||||||
|
<select id="ijm-day-select" onchange="goToSlide(this.value)" class="text-xs font-semibold text-slate-700 bg-white border border-slate-200 rounded-xl px-2.5 py-1.5 focus:outline-none focus:border-blue-400">
|
||||||
|
<!-- Options loaded dynamically -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" onclick="nextSlide()" class="p-2 bg-white hover:bg-slate-100 border border-slate-200 text-slate-600 hover:text-slate-900 rounded-xl transition-all flex items-center justify-center cursor-pointer">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Carousel Slide Container -->
|
||||||
|
<div class="bg-white rounded-2xl border border-slate-100 p-6 flex-grow flex flex-col shadow-sm min-h-[300px]">
|
||||||
|
<!-- Day Title & Date -->
|
||||||
|
<div class="flex items-center justify-between pb-3 border-b border-slate-100 mb-4 flex-shrink-0">
|
||||||
|
<div>
|
||||||
|
<span id="ijm-slide-title" class="text-sm font-extrabold text-slate-800">Seçili Gün</span>
|
||||||
|
<span id="ijm-slide-date" class="text-xs font-semibold text-slate-400 ml-2"></span>
|
||||||
|
</div>
|
||||||
|
<div id="ijm-slide-retroactive">
|
||||||
|
<!-- Retroactive badge -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide Content (Rich text HTML) -->
|
||||||
|
<div class="flex-grow overflow-y-auto no-scrollbar prose max-w-none text-slate-700 leading-relaxed text-sm" style="max-height: 250px;">
|
||||||
|
<div id="ijm-slide-content">
|
||||||
|
<!-- Day content loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Slide Actions / Daily approval status -->
|
||||||
|
<div class="pt-4 border-t border-slate-100 flex items-center justify-between mt-4 flex-shrink-0">
|
||||||
|
<div id="ijm-slide-status-badge">
|
||||||
|
<!-- Status badge -->
|
||||||
|
</div>
|
||||||
|
<button type="button" id="ijm-slide-action-btn" onclick="toggleActiveDayApproval()" class="px-4 py-2 text-white bg-blue-600 hover:bg-blue-700 rounded-xl font-bold text-xs shadow-lg shadow-blue-500/20 transition-all cursor-pointer">
|
||||||
|
Onayla
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<div class="px-6 py-4 bg-slate-50 border-t border-slate-100 flex items-center justify-end flex-shrink-0">
|
||||||
|
<button type="button" onclick="closeInternJournalModal()" class="px-5 py-2.5 bg-slate-800 hover:bg-slate-900 text-white font-bold rounded-xl text-xs transition-colors shadow-md cursor-pointer">Kapat</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@push('styles')
|
||||||
|
<style>
|
||||||
|
.admin-tab-btn {
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.admin-tab-btn:hover:not(.active) {
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
.admin-tab-btn.active {
|
||||||
|
background-color: #eff6ff;
|
||||||
|
border-color: #dbeafe;
|
||||||
|
color: #1e40af !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
let currentInternId = null;
|
||||||
|
let journalEntries = [];
|
||||||
|
let activeSlideIndex = 0;
|
||||||
|
let isUpdatingSignature = false;
|
||||||
|
let isUpdatingDayApproval = false;
|
||||||
|
|
||||||
|
function openInternJournalModal(internId) {
|
||||||
|
currentInternId = internId;
|
||||||
|
activeSlideIndex = 0;
|
||||||
|
|
||||||
|
const modal = document.getElementById('intern-journal-modal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
modal.classList.add('flex');
|
||||||
|
|
||||||
|
// Animate open
|
||||||
|
const card = modal.querySelector('.max-w-4xl');
|
||||||
|
if (card) {
|
||||||
|
setTimeout(() => {
|
||||||
|
card.classList.remove('scale-95', 'opacity-0');
|
||||||
|
card.classList.add('scale-100', 'opacity-100');
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadInternJournalDetails(internId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeInternJournalModal() {
|
||||||
|
const modal = document.getElementById('intern-journal-modal');
|
||||||
|
if (!modal) return;
|
||||||
|
|
||||||
|
const card = modal.querySelector('.max-w-4xl');
|
||||||
|
if (card) {
|
||||||
|
card.classList.remove('scale-100', 'opacity-100');
|
||||||
|
card.classList.add('scale-95', 'opacity-0');
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
modal.classList.remove('flex');
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadInternJournalDetails(internId) {
|
||||||
|
const slideContent = document.getElementById('ijm-slide-content');
|
||||||
|
if (slideContent) {
|
||||||
|
slideContent.innerHTML = `
|
||||||
|
<div class="flex items-center justify-center p-12">
|
||||||
|
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(`/stajyer/admin/journal-details?intern_id=${internId}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
alert(data.message || 'Veriler yüklenemedi.');
|
||||||
|
closeInternJournalModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Header details
|
||||||
|
const nameEl = document.getElementById('ijm-name');
|
||||||
|
if (nameEl) nameEl.textContent = data.intern.name;
|
||||||
|
|
||||||
|
let startF = data.intern.start_date ? formatDateStr(data.intern.start_date) : '-';
|
||||||
|
let endF = data.intern.end_date ? formatDateStr(data.intern.end_date) : '-';
|
||||||
|
const metaEl = document.getElementById('ijm-meta');
|
||||||
|
if (metaEl) metaEl.textContent = `${startF} - ${endF} (${data.intern.total_days} İş Günü)`;
|
||||||
|
|
||||||
|
// Setup Progress Bars
|
||||||
|
const fillPercent = data.intern.total_days > 0 ? (data.intern.filled_days / data.intern.total_days) * 100 : 0;
|
||||||
|
const fillTextEl = document.getElementById('ijm-fill-text');
|
||||||
|
if (fillTextEl) fillTextEl.textContent = `${data.intern.filled_days} / ${data.intern.total_days} Gün`;
|
||||||
|
const fillBarEl = document.getElementById('ijm-fill-progress');
|
||||||
|
if (fillBarEl) fillBarEl.style.width = `${fillPercent}%`;
|
||||||
|
|
||||||
|
// Approved days count
|
||||||
|
let approvedDaysCount = 0;
|
||||||
|
data.entries.forEach(e => {
|
||||||
|
if (e.filled && e.supervisor_approved) {
|
||||||
|
approvedDaysCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const approvedPercent = data.intern.total_days > 0 ? (approvedDaysCount / data.intern.total_days) * 100 : 0;
|
||||||
|
const appTextEl = document.getElementById('ijm-approved-text');
|
||||||
|
if (appTextEl) appTextEl.textContent = `${approvedDaysCount} / ${data.intern.total_days} Gün`;
|
||||||
|
const appBarEl = document.getElementById('ijm-approved-progress');
|
||||||
|
if (appBarEl) appBarEl.style.width = `${approvedPercent}%`;
|
||||||
|
|
||||||
|
// Setup Signatures
|
||||||
|
updateSignatureUI('supervisor', data.intern.notebook_supervisor_signed, data.intern.notebook_supervisor_name);
|
||||||
|
updateSignatureUI('unit', data.intern.notebook_unit_signed, data.intern.notebook_unit_name);
|
||||||
|
updateSignatureUI('approved', data.intern.notebook_approved, null);
|
||||||
|
|
||||||
|
// Setup carousel slide data
|
||||||
|
journalEntries = data.entries;
|
||||||
|
|
||||||
|
// Setup dropdown
|
||||||
|
const select = document.getElementById('ijm-day-select');
|
||||||
|
if (select) {
|
||||||
|
select.innerHTML = '';
|
||||||
|
journalEntries.forEach((entry, idx) => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = idx;
|
||||||
|
opt.textContent = `${entry.day_number}. Gün (${entry.formatted_date})` + (entry.filled ? ' [DOLU]' : ' [BOŞ]');
|
||||||
|
select.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set total indicator text
|
||||||
|
const indEl = document.getElementById('ijm-day-indicator');
|
||||||
|
if (indEl) indEl.textContent = `Gün: 1 / ${journalEntries.length}`;
|
||||||
|
|
||||||
|
// Render first slide
|
||||||
|
renderSlide(0);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('Staj detayları yüklenirken bir hata oluştu.');
|
||||||
|
closeInternJournalModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateStr(dateStr) {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const parts = dateStr.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
return `${parts[2]}.${parts[1]}.${parts[0]}`;
|
||||||
|
}
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSignatureUI(type, signed, name) {
|
||||||
|
const btn = document.getElementById(`ijm-btn-${type}`);
|
||||||
|
const label = document.getElementById(`ijm-text-${type}`);
|
||||||
|
if (!btn || !label) return;
|
||||||
|
|
||||||
|
if (type === 'supervisor') {
|
||||||
|
if (signed) {
|
||||||
|
btn.textContent = "İmzayı Kaldır";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-rose-200 bg-rose-50 text-rose-600 hover:bg-rose-100 transition-all cursor-pointer";
|
||||||
|
label.innerHTML = `<span class="text-green-600 font-extrabold"><i class="uil uil-check-circle mr-0.5"></i>İmzaladı: ${name || ''}</span>`;
|
||||||
|
} else {
|
||||||
|
btn.textContent = "İmzala";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 transition-all cursor-pointer";
|
||||||
|
label.textContent = "İmzalanmadı";
|
||||||
|
}
|
||||||
|
} else if (type === 'unit') {
|
||||||
|
if (signed) {
|
||||||
|
btn.textContent = "İmzayı Kaldır";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-rose-200 bg-rose-50 text-rose-600 hover:bg-rose-100 transition-all cursor-pointer";
|
||||||
|
label.innerHTML = `<span class="text-green-600 font-extrabold"><i class="uil uil-check-circle mr-0.5"></i>İmzaladı: ${name || ''}</span>`;
|
||||||
|
} else {
|
||||||
|
btn.textContent = "İmzala";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100 transition-all cursor-pointer";
|
||||||
|
label.textContent = "İmzalanmadı";
|
||||||
|
}
|
||||||
|
} else if (type === 'approved') {
|
||||||
|
if (signed) {
|
||||||
|
btn.textContent = "Onayı Kaldır";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-rose-200 bg-rose-50 text-rose-600 hover:bg-rose-100 transition-all cursor-pointer";
|
||||||
|
label.innerHTML = `<span class="text-green-600 font-extrabold"><i class="uil uil-check-circle mr-0.5"></i>Defter Onaylandı</span>`;
|
||||||
|
} else {
|
||||||
|
btn.textContent = "Onayla";
|
||||||
|
btn.className = "px-2.5 py-1 text-[10px] font-extrabold uppercase rounded-lg border border-emerald-200 bg-emerald-50 text-emerald-600 hover:bg-emerald-100 transition-all cursor-pointer";
|
||||||
|
label.textContent = "Onaylanmadı";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleNotebookSignature(type) {
|
||||||
|
if (isUpdatingSignature) return;
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
const btn = document.getElementById(`ijm-btn-${type}`);
|
||||||
|
if (!btn) return;
|
||||||
|
const isSignedCurrently = btn.textContent.includes('Kaldır');
|
||||||
|
const newSignState = !isSignedCurrently;
|
||||||
|
|
||||||
|
let promptName = '';
|
||||||
|
if (newSignState && (type === 'supervisor' || type === 'unit')) {
|
||||||
|
promptName = prompt("İmzalayan yetkili ismini giriniz veya onaylayınız:", @json(auth()->user()->name));
|
||||||
|
if (promptName === null) return; // cancelled
|
||||||
|
if (promptName.trim() === '') {
|
||||||
|
promptName = @json(auth()->user()->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isUpdatingSignature = true;
|
||||||
|
btn.style.opacity = '0.5';
|
||||||
|
|
||||||
|
fetch('/stajyer/admin/toggle-notebook-signature', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
intern_id: currentInternId,
|
||||||
|
type: type,
|
||||||
|
signed: newSignState ? 1 : 0,
|
||||||
|
name: promptName
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
const key = type === 'supervisor' ? 'notebook_supervisor_signed' : (type === 'unit' ? 'notebook_unit_signed' : 'notebook_approved');
|
||||||
|
const nameKey = type === 'supervisor' ? 'notebook_supervisor_name' : (type === 'unit' ? 'notebook_unit_name' : null);
|
||||||
|
|
||||||
|
updateSignatureUI(type, data.intern[key], data.intern[nameKey]);
|
||||||
|
} else {
|
||||||
|
alert(data.message || 'Hata oluştu.');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert('İşlem gerçekleştirilemedi.');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isUpdatingSignature = false;
|
||||||
|
btn.style.opacity = '1';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSlide(index) {
|
||||||
|
if (index < 0 || index >= journalEntries.length) return;
|
||||||
|
|
||||||
|
activeSlideIndex = index;
|
||||||
|
const entry = journalEntries[index];
|
||||||
|
|
||||||
|
// Update indicator & select dropdown
|
||||||
|
const indEl = document.getElementById('ijm-day-indicator');
|
||||||
|
if (indEl) indEl.textContent = `Gün: ${index + 1} / ${journalEntries.length}`;
|
||||||
|
const selEl = document.getElementById('ijm-day-select');
|
||||||
|
if (selEl) selEl.value = index;
|
||||||
|
|
||||||
|
// Title & Date
|
||||||
|
const titleEl = document.getElementById('ijm-slide-title');
|
||||||
|
if (titleEl) titleEl.textContent = `${entry.day_number}. Gün Raporu`;
|
||||||
|
const dateEl = document.getElementById('ijm-slide-date');
|
||||||
|
if (dateEl) dateEl.textContent = entry.formatted_date;
|
||||||
|
|
||||||
|
// Retroactive badge
|
||||||
|
const retroBadge = document.getElementById('ijm-slide-retroactive');
|
||||||
|
if (retroBadge) {
|
||||||
|
retroBadge.innerHTML = '';
|
||||||
|
if (entry.filled && entry.is_retroactive) {
|
||||||
|
retroBadge.innerHTML = `
|
||||||
|
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-rose-50 text-rose-700 text-[10px] font-extrabold uppercase border border-rose-100">
|
||||||
|
<span class="w-1.5 h-1.5 rounded-full bg-rose-500 animate-pulse"></span>
|
||||||
|
Geriye Dönük
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content area
|
||||||
|
const contentArea = document.getElementById('ijm-slide-content');
|
||||||
|
const badgeDiv = document.getElementById('ijm-slide-status-badge');
|
||||||
|
const actionBtn = document.getElementById('ijm-slide-action-btn');
|
||||||
|
if (!contentArea || !badgeDiv || !actionBtn) return;
|
||||||
|
|
||||||
|
if (!entry.filled) {
|
||||||
|
contentArea.innerHTML = `
|
||||||
|
<div class="flex flex-col items-center justify-center p-8 text-center bg-slate-50 rounded-2xl border border-dashed border-slate-200">
|
||||||
|
<i class="uil uil-file-slash text-3xl text-slate-400 mb-2"></i>
|
||||||
|
<p class="text-sm font-medium text-slate-500">Bu gün için henüz staj raporu yazılmamıştır.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
badgeDiv.innerHTML = `<span class="text-xs text-slate-400 font-semibold">Boş Rapor</span>`;
|
||||||
|
actionBtn.classList.add('hidden');
|
||||||
|
} else {
|
||||||
|
actionBtn.classList.remove('hidden');
|
||||||
|
const entryContent = entry.content || '<p class="text-slate-400 italic">Boş içerik.</p>';
|
||||||
|
contentArea.innerHTML = `<div class="rich-text-content prose max-w-none text-slate-700 leading-relaxed text-sm select-text">${entryContent}</div>`;
|
||||||
|
|
||||||
|
updateDayApprovalUI(entry.supervisor_approved, entry.supervisor_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDayApprovalUI(approved, name) {
|
||||||
|
const badgeDiv = document.getElementById('ijm-slide-status-badge');
|
||||||
|
const actionBtn = document.getElementById('ijm-slide-action-btn');
|
||||||
|
if (!badgeDiv || !actionBtn) return;
|
||||||
|
|
||||||
|
if (approved) {
|
||||||
|
badgeDiv.innerHTML = `
|
||||||
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-emerald-50 text-emerald-700 text-xs font-bold border border-emerald-100">
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>
|
||||||
|
Sorumlu Onayladı ${name ? `(${name})` : ''}
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
actionBtn.textContent = "Onayı Kaldır";
|
||||||
|
actionBtn.className = "px-4 py-2 text-white bg-rose-600 hover:bg-rose-700 rounded-xl font-bold text-xs shadow-lg shadow-rose-500/20 transition-all cursor-pointer";
|
||||||
|
} else {
|
||||||
|
badgeDiv.innerHTML = `
|
||||||
|
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-slate-100 text-slate-600 text-xs font-bold border border-slate-200">
|
||||||
|
Onay Bekliyor
|
||||||
|
</span>
|
||||||
|
`;
|
||||||
|
actionBtn.textContent = "Günü Onayla";
|
||||||
|
actionBtn.className = "px-4 py-2 text-white bg-emerald-600 hover:bg-emerald-700 rounded-xl font-bold text-xs shadow-lg shadow-emerald-500/20 transition-all cursor-pointer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleActiveDayApproval() {
|
||||||
|
if (isUpdatingDayApproval) return;
|
||||||
|
|
||||||
|
const entry = journalEntries[activeSlideIndex];
|
||||||
|
if (!entry || !entry.entry_id) return;
|
||||||
|
|
||||||
|
const actionBtn = document.getElementById('ijm-slide-action-btn');
|
||||||
|
if (!actionBtn) return;
|
||||||
|
isUpdatingDayApproval = true;
|
||||||
|
actionBtn.style.opacity = '0.5';
|
||||||
|
|
||||||
|
fetch('/stajyer/admin/toggle-approval', {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRF-TOKEN": "{{ csrf_token() }}"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
entry_id: entry.entry_id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Update local object
|
||||||
|
entry.supervisor_approved = data.status;
|
||||||
|
entry.supervisor_name = data.supervisor_name;
|
||||||
|
|
||||||
|
// Update UI
|
||||||
|
updateDayApprovalUI(data.status, data.supervisor_name);
|
||||||
|
|
||||||
|
// Recalculate and update approved progress bar
|
||||||
|
let approvedDaysCount = 0;
|
||||||
|
journalEntries.forEach(e => {
|
||||||
|
if (e.filled && e.supervisor_approved) {
|
||||||
|
approvedDaysCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const approvedPercent = journalEntries.length > 0 ? (approvedDaysCount / journalEntries.length) * 100 : 0;
|
||||||
|
|
||||||
|
const appTextEl = document.getElementById('ijm-approved-text');
|
||||||
|
if (appTextEl) appTextEl.textContent = `${approvedDaysCount} / ${journalEntries.length} Gün`;
|
||||||
|
const appBarEl = document.getElementById('ijm-approved-progress');
|
||||||
|
if (appBarEl) appBarEl.style.width = `${approvedPercent}%`;
|
||||||
|
} else {
|
||||||
|
alert(data.message || "Onay güncellenemedi.");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
alert("İşlem sırasında bir hata oluştu.");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
isUpdatingDayApproval = false;
|
||||||
|
actionBtn.style.opacity = '1';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevSlide() {
|
||||||
|
if (activeSlideIndex > 0) {
|
||||||
|
renderSlide(activeSlideIndex - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextSlide() {
|
||||||
|
if (activeSlideIndex < journalEntries.length - 1) {
|
||||||
|
renderSlide(activeSlideIndex + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToSlide(idx) {
|
||||||
|
renderSlide(parseInt(idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin Tab Switch Logic
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const adminTabButtons = document.querySelectorAll('.admin-tab-btn');
|
||||||
|
const adminTabPanels = document.querySelectorAll('.admin-tab-panel');
|
||||||
|
|
||||||
|
adminTabButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
// Deactivate all buttons
|
||||||
|
adminTabButtons.forEach(btn => {
|
||||||
|
btn.classList.remove('active', 'bg-blue-50', 'border-blue-100', 'text-blue-900');
|
||||||
|
btn.classList.add('text-slate-600', 'bg-transparent', 'border-transparent');
|
||||||
|
btn.setAttribute('aria-selected', 'false');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hide all panels
|
||||||
|
adminTabPanels.forEach(panel => {
|
||||||
|
panel.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activate clicked button
|
||||||
|
button.classList.add('active', 'bg-blue-50', 'border-blue-100', 'text-blue-900');
|
||||||
|
button.classList.remove('text-slate-600', 'bg-transparent', 'border-transparent');
|
||||||
|
button.setAttribute('aria-selected', 'true');
|
||||||
|
|
||||||
|
// Show target panel
|
||||||
|
const targetId = button.getAttribute('data-tab-target');
|
||||||
|
const targetPanel = document.getElementById(targetId + '-panel');
|
||||||
|
if (targetPanel) {
|
||||||
|
targetPanel.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Re-render DevExtreme Gantt chart if visible, to prevent rendering scale bugs
|
||||||
|
if (targetId === 'gantt-chart') {
|
||||||
|
const ganttEl = document.getElementById('gantt');
|
||||||
|
if (ganttEl) {
|
||||||
|
const ganttInstance = $(ganttEl).dxGantt('instance');
|
||||||
|
if (ganttInstance) {
|
||||||
|
ganttInstance.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -655,15 +655,23 @@
|
|||||||
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 mb-6">
|
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 mb-6">
|
||||||
<form action="{{ route('intern.save-github') }}" method="POST" class="space-y-4">
|
<form action="{{ route('intern.save-github') }}" method="POST" class="space-y-4">
|
||||||
@csrf
|
@csrf
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="github_repo" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Public Depo URL'si</label>
|
<label for="github_repo" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Public Depo URL'si</label>
|
||||||
<div class="flex flex-col sm:flex-row gap-3">
|
|
||||||
<input type="url" name="github_repo" id="github_repo" placeholder="https://github.com/kullaniciadi/depo-adi" value="{{ $intern->github_repo }}" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
|
<input type="url" name="github_repo" id="github_repo" placeholder="https://github.com/kullaniciadi/depo-adi" value="{{ $intern->github_repo }}" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
|
||||||
<button type="submit" class="px-5 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all shadow-md shadow-blue-500/10 flex-shrink-0">
|
|
||||||
{{ $intern->github_repo ? 'Güncelle' : 'Kaydet' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<span class="block text-xs text-slate-400 mt-2"><i class="uil uil-info-circle text-blue-600"></i> Deponuzun <strong>public (herkese açık)</strong> olması gerekmektedir.</span>
|
<div>
|
||||||
|
<label for="github_username" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Kullanıcı Adınız (Opsiyonel)</label>
|
||||||
|
<input type="text" name="github_username" id="github_username" placeholder="GitHub kullanıcı adınız (örn: Emresatil)" value="{{ $intern->github_username }}" class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pt-2 border-t border-slate-100">
|
||||||
|
<span class="text-xs text-slate-400 leading-normal max-w-xl">
|
||||||
|
<i class="uil uil-info-circle text-blue-600 text-sm"></i> Deponuzun <strong>public</strong> olması gerekir. Birden fazla kişi aynı projede çalışıyorsa, kendi commitlerinizi süzmek için kullanıcı adınızı yazmanız önerilir.
|
||||||
|
</span>
|
||||||
|
<button type="submit" class="px-5 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all shadow-md shadow-blue-500/10 flex-shrink-0 w-full sm:w-auto">
|
||||||
|
Bilgileri Kaydet
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -684,6 +692,7 @@
|
|||||||
$dayDate = $d['date'];
|
$dayDate = $d['date'];
|
||||||
$dayDateFormatted = $d['formatted_date'];
|
$dayDateFormatted = $d['formatted_date'];
|
||||||
$hasSaved = isset($savedEntries[$dayNum]) && trim($savedEntries[$dayNum]->content) !== '';
|
$hasSaved = isset($savedEntries[$dayNum]) && trim($savedEntries[$dayNum]->content) !== '';
|
||||||
|
$isApproved = isset($savedEntries[$dayNum]) && $savedEntries[$dayNum]->supervisor_approved;
|
||||||
@endphp
|
@endphp
|
||||||
<button type="button"
|
<button type="button"
|
||||||
onclick="selectNotebookDay({{ $idx }})"
|
onclick="selectNotebookDay({{ $idx }})"
|
||||||
@@ -692,13 +701,19 @@
|
|||||||
data-date="{{ $dayDate }}"
|
data-date="{{ $dayDate }}"
|
||||||
data-formatted-date="{{ $dayDateFormatted }}"
|
data-formatted-date="{{ $dayDateFormatted }}"
|
||||||
data-saved-content="{{ $savedEntries[$dayNum]->content ?? '' }}"
|
data-saved-content="{{ $savedEntries[$dayNum]->content ?? '' }}"
|
||||||
|
data-approved="{{ $isApproved ? '1' : '0' }}"
|
||||||
class="notebook-day-btn w-full flex items-center justify-between p-3 rounded-2xl border text-left transition-all @if($idx === 0) border-blue-200 bg-blue-50/50 @else border-slate-100 bg-white hover:bg-slate-50 @endif">
|
class="notebook-day-btn w-full flex items-center justify-between p-3 rounded-2xl border text-left transition-all @if($idx === 0) border-blue-200 bg-blue-50/50 @else border-slate-100 bg-white hover:bg-slate-50 @endif">
|
||||||
<div>
|
<div>
|
||||||
<span class="block text-xs font-extrabold @if($idx === 0) text-blue-700 @else text-slate-800 @endif">{{ $dayNum }}. Gün</span>
|
<span class="block text-xs font-extrabold @if($idx === 0) text-blue-700 @else text-slate-800 @endif">{{ $dayNum }}. Gün</span>
|
||||||
<span class="block text-[10px] text-slate-400 mt-0.5">{{ $dayDateFormatted }}</span>
|
<span class="block text-[10px] text-slate-400 mt-0.5">{{ $dayDateFormatted }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span id="badge-day-{{ $idx }}" class="inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase @if($hasSaved) bg-green-50 text-green-700 border border-green-200 @else bg-slate-50 text-slate-400 border border-slate-200 @endif">
|
<span id="badge-day-{{ $idx }}" class="inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase
|
||||||
{{ $hasSaved ? 'DOLU' : 'BOŞ' }}
|
@if($isApproved) bg-green-500 text-white border border-green-600
|
||||||
|
@elseif($hasSaved) bg-amber-50 text-amber-700 border border-amber-200
|
||||||
|
@else bg-slate-50 text-slate-400 border border-slate-200 @endif">
|
||||||
|
@if($isApproved) ONAYLANDI
|
||||||
|
@elseif($hasSaved) DOLU
|
||||||
|
@else BOŞ @endif
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
@endforeach
|
@endforeach
|
||||||
@@ -708,17 +723,25 @@
|
|||||||
<div class="lg:col-span-8 space-y-4">
|
<div class="lg:col-span-8 space-y-4">
|
||||||
<div class="bg-white border border-slate-100 rounded-2xl p-5 shadow-sm">
|
<div class="bg-white border border-slate-100 rounded-2xl p-5 shadow-sm">
|
||||||
<div class="flex items-center justify-between pb-3 border-b border-slate-100 mb-4">
|
<div class="flex items-center justify-between pb-3 border-b border-slate-100 mb-4">
|
||||||
<div>
|
<div class="flex items-center">
|
||||||
<span id="editor-day-title" class="text-sm font-extrabold text-blue-600 bg-blue-50 px-3 py-1 rounded-full">1. Gün</span>
|
<span id="editor-day-title" class="text-sm font-extrabold text-blue-600 bg-blue-50 px-3 py-1 rounded-full">1. Gün</span>
|
||||||
<span id="editor-day-date" class="text-xs font-semibold text-slate-400 ml-2"></span>
|
<span id="editor-day-date" class="text-xs font-semibold text-slate-400 ml-2"></span>
|
||||||
|
<span id="editor-day-status" class="ml-2"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
@if($intern->github_repo)
|
@if($intern->github_repo)
|
||||||
<button type="button" onclick="fillFromGithub()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-900 text-white font-extrabold rounded-lg text-[10px] transition-all flex items-center gap-1.5 shadow-sm">
|
<button type="button" onclick="fillFromGithub()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-900 text-white font-extrabold rounded-lg text-[10px] transition-all flex items-center gap-1.5 shadow-sm">
|
||||||
<i class="uil uil-github text-xs"></i>
|
<i class="uil uil-github text-xs"></i>
|
||||||
<span>Git Commitlerini Çek</span>
|
<span>Git Commitlerini Çek</span>
|
||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<button type="button" onclick="printActiveDay()" class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white font-extrabold rounded-lg text-[10px] transition-all flex items-center gap-1.5 shadow-sm">
|
||||||
|
<i class="uil uil-print text-xs"></i>
|
||||||
|
<span>Yazdır</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@@ -733,6 +756,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="git-commits-fallback-container" class="hidden p-4 bg-slate-50 border border-slate-200/60 rounded-2xl text-xs space-y-3">
|
||||||
|
<div class="flex justify-between items-center pb-2 border-b border-slate-200">
|
||||||
|
<span class="font-extrabold text-slate-700 flex items-center gap-1.5">
|
||||||
|
<i class="uil uil-info-circle text-blue-600 text-sm"></i>
|
||||||
|
<span>Seçilen güne ait otomatik commit bulunamadı. Son 10 commit'iniz:</span>
|
||||||
|
</span>
|
||||||
|
<button type="button" onclick="hideFallbackCommits()" class="text-slate-400 hover:text-slate-600 font-extrabold">Kapat</button>
|
||||||
|
</div>
|
||||||
|
<div id="git-commits-fallback-list" class="space-y-2 max-h-[150px] overflow-y-auto pr-1 no-scrollbar">
|
||||||
|
<!-- items dynamically populated -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<div id="save-status" class="text-xs font-semibold text-slate-400"></div>
|
<div id="save-status" class="text-xs font-semibold text-slate-400"></div>
|
||||||
<button type="button" id="save-btn" onclick="saveActiveDay()" class="px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs transition-all shadow-md shadow-blue-500/10">
|
<button type="button" id="save-btn" onclick="saveActiveDay()" class="px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs transition-all shadow-md shadow-blue-500/10">
|
||||||
@@ -794,6 +830,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Author Selection Modal -->
|
||||||
|
<div id="author-modal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
|
||||||
|
<div class="bg-white rounded-3xl p-6 max-w-md w-full shadow-2xl border border-slate-100/50 transform scale-95 opacity-0 transition-all duration-300" id="author-modal-card">
|
||||||
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
|
||||||
|
<i class="uil uil-users-alt text-blue-600"></i>
|
||||||
|
<span>GitHub Yazar Seçimi</span>
|
||||||
|
</h3>
|
||||||
|
<p class="text-xs text-slate-400 mt-1">Bu günün commitlerinde birden fazla yazar tespit edildi. Hangisi sizsiniz?</p>
|
||||||
|
</div>
|
||||||
|
<button onclick="closeAuthorModal()" class="text-slate-400 hover:text-slate-600">
|
||||||
|
<i class="uil uil-multiply text-xl"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="author-list-container" class="space-y-2 mb-4 max-h-[220px] overflow-y-auto pr-1 no-scrollbar">
|
||||||
|
<!-- Dynamically populated author options -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 mb-5 bg-slate-50 p-3 rounded-xl border border-slate-200/60">
|
||||||
|
<input type="checkbox" id="save-selected-author-pref" class="rounded text-blue-600 focus:ring-blue-500 w-4 h-4" checked />
|
||||||
|
<label for="save-selected-author-pref" class="text-xs text-slate-600 font-bold select-none cursor-pointer">
|
||||||
|
Seçtiğim kullanıcı adını profilime kaydet ve varsayılan yap
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<button type="button" onclick="closeAuthorModal()" class="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold rounded-xl text-xs transition-all">İptal</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@push('styles')
|
@push('styles')
|
||||||
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
|
||||||
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
|
||||||
@@ -855,56 +924,218 @@
|
|||||||
// Handle initial state of date logic
|
// Handle initial state of date logic
|
||||||
calculateEndDateFrontend();
|
calculateEndDateFrontend();
|
||||||
|
|
||||||
// Fetch Github commits in memory if repo is set
|
|
||||||
fetchAllCommitsInMemory();
|
|
||||||
|
|
||||||
// Select first day of the notebook workspace on load
|
// Select first day of the notebook workspace on load
|
||||||
selectNotebookDay(0);
|
selectNotebookDay(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
const githubRepoUrl = @json($intern->github_repo);
|
const githubRepoUrl = @json($intern->github_repo);
|
||||||
let allGithubCommits = {};
|
let githubUsername = @json($intern->github_username);
|
||||||
let activeDayIdx = 0;
|
let activeDayIdx = 0;
|
||||||
|
|
||||||
function fetchAllCommitsInMemory() {
|
let activeCommitsToProcess = [];
|
||||||
|
let activeCommitsDateVal = '';
|
||||||
|
|
||||||
|
function openAuthorModal(uniqueAuthors) {
|
||||||
|
const modal = document.getElementById('author-modal');
|
||||||
|
const card = document.getElementById('author-modal-card');
|
||||||
|
const container = document.getElementById('author-list-container');
|
||||||
|
if (!modal || !card || !container) return;
|
||||||
|
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// 1. Add "Hepsi" (All commits) option
|
||||||
|
const allBtn = document.createElement('button');
|
||||||
|
allBtn.type = 'button';
|
||||||
|
allBtn.className = "w-full p-3 rounded-xl border border-slate-200 hover:border-blue-400 hover:bg-blue-50/20 text-left transition-all font-extrabold text-xs text-slate-700 flex items-center justify-between";
|
||||||
|
allBtn.innerHTML = `
|
||||||
|
<span>Tüm Commitler (Filtreleme Yapma)</span>
|
||||||
|
<i class="uil uil-angle-right text-base text-slate-400"></i>
|
||||||
|
`;
|
||||||
|
allBtn.addEventListener('click', () => {
|
||||||
|
insertCommitsForAuthor(null);
|
||||||
|
});
|
||||||
|
container.appendChild(allBtn);
|
||||||
|
|
||||||
|
// 2. Add each unique author option
|
||||||
|
uniqueAuthors.forEach(author => {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.className = "w-full p-3 rounded-xl border border-slate-200 hover:border-blue-400 hover:bg-blue-50/20 text-left transition-all font-extrabold text-xs text-slate-700 flex items-center justify-between mt-2";
|
||||||
|
btn.innerHTML = `
|
||||||
|
<span>${author}</span>
|
||||||
|
<i class="uil uil-angle-right text-base text-slate-400"></i>
|
||||||
|
`;
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
insertCommitsForAuthor(author);
|
||||||
|
});
|
||||||
|
container.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
setTimeout(() => {
|
||||||
|
card.classList.remove('scale-95', 'opacity-0');
|
||||||
|
card.classList.add('scale-100', 'opacity-100');
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAuthorModal() {
|
||||||
|
const modal = document.getElementById('author-modal');
|
||||||
|
const card = document.getElementById('author-modal-card');
|
||||||
|
if (!modal || !card) return;
|
||||||
|
|
||||||
|
card.classList.remove('scale-100', 'opacity-100');
|
||||||
|
card.classList.add('scale-95', 'opacity-0');
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertCommitsForAuthor(author) {
|
||||||
|
closeAuthorModal();
|
||||||
|
|
||||||
|
const savePref = document.getElementById('save-selected-author-pref');
|
||||||
|
if (author && savePref && savePref.checked) {
|
||||||
|
saveGithubUsernamePreference(author);
|
||||||
|
}
|
||||||
|
|
||||||
|
let dayCommits = activeCommitsToProcess;
|
||||||
|
if (author) {
|
||||||
|
dayCommits = activeCommitsToProcess.filter(item => {
|
||||||
|
const commitAuthorName = item.commit.author.name;
|
||||||
|
const commitAuthorLogin = item.author ? item.author.login : null;
|
||||||
|
return (commitAuthorLogin && commitAuthorLogin.toLowerCase() === author.toLowerCase()) ||
|
||||||
|
(commitAuthorName && commitAuthorName.toLowerCase() === author.toLowerCase());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCommitsToEditor(dayCommits);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveGithubUsernamePreference(username) {
|
||||||
if (!githubRepoUrl) return;
|
if (!githubRepoUrl) return;
|
||||||
|
|
||||||
let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, '');
|
fetch("{{ route('intern.save-github') }}", {
|
||||||
repoClean = repoClean.replace(/\/$/, '');
|
method: "POST",
|
||||||
const parts = repoClean.split('/');
|
headers: {
|
||||||
if (parts.length < 2) return;
|
"Content-Type": "application/json",
|
||||||
|
"X-CSRF-TOKEN": "{{ csrf_token() }}"
|
||||||
const owner = parts[0];
|
},
|
||||||
const repo = parts[1].replace(/\.git$/i, '');
|
body: JSON.stringify({
|
||||||
|
github_repo: githubRepoUrl,
|
||||||
fetch(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=100`)
|
github_username: username
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) throw new Error('API Error');
|
|
||||||
return response.json();
|
|
||||||
})
|
})
|
||||||
.then(commits => {
|
})
|
||||||
if (!Array.isArray(commits)) return;
|
.then(response => response.json())
|
||||||
commits.forEach(item => {
|
.then(data => {
|
||||||
const dateStr = item.commit.author.date;
|
if (data.success) {
|
||||||
if (dateStr) {
|
githubUsername = username;
|
||||||
const d = new Date(dateStr);
|
const usernameInput = document.getElementById('github_username');
|
||||||
const formatter = new Intl.DateTimeFormat('fr-CA', {
|
if (usernameInput) {
|
||||||
|
usernameInput.value = username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn("Kullanıcı adı tercihi kaydedilemedi.", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCommitsToEditor(commitsList) {
|
||||||
|
if (commitsList.length === 0) {
|
||||||
|
alert("Bu yazar için seçilen güne ait commit bulunamadı.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let commitHtml = "<ul>";
|
||||||
|
const sorted = [...commitsList].reverse();
|
||||||
|
sorted.forEach(c => {
|
||||||
|
const msg = c.commit.message;
|
||||||
|
const sha = c.sha.substring(0, 7);
|
||||||
|
const authorDate = new Date(c.commit.author.date);
|
||||||
|
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
|
||||||
|
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
|
||||||
|
});
|
||||||
|
commitHtml += "</ul>";
|
||||||
|
|
||||||
|
const currentHtml = quill.root.innerHTML.trim();
|
||||||
|
const headerHtml = "<p><strong>Git Commit Mesajları:</strong></p>";
|
||||||
|
|
||||||
|
let newHtml = "";
|
||||||
|
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
|
||||||
|
newHtml = currentHtml + "<br><br>" + headerHtml + commitHtml;
|
||||||
|
} else {
|
||||||
|
newHtml = headerHtml + commitHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
quill.setContents([]);
|
||||||
|
quill.clipboard.dangerouslyPasteHTML(newHtml);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideFallbackCommits() {
|
||||||
|
const container = document.getElementById('git-commits-fallback-container');
|
||||||
|
if (container) {
|
||||||
|
container.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDateToIstanbul(d) {
|
||||||
|
try {
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
timeZone: 'Europe/Istanbul',
|
timeZone: 'Europe/Istanbul',
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: '2-digit',
|
month: '2-digit',
|
||||||
day: '2-digit'
|
day: '2-digit'
|
||||||
});
|
});
|
||||||
const dateOnly = formatter.format(d);
|
const parts = formatter.formatToParts(d);
|
||||||
if (!allGithubCommits[dateOnly]) {
|
const year = parts.find(p => p.type === 'year').value;
|
||||||
allGithubCommits[dateOnly] = [];
|
const month = parts.find(p => p.type === 'month').value;
|
||||||
|
const day = parts.find(p => p.type === 'day').value;
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
} catch (e) {
|
||||||
|
return d.toISOString().split('T')[0];
|
||||||
}
|
}
|
||||||
allGithubCommits[dateOnly].push(item);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
})
|
function updateEditorStatusBadge(isApproved, hasSaved) {
|
||||||
.catch(err => {
|
const statusSpan = document.getElementById('editor-day-status');
|
||||||
console.warn('Commits could not be pre-loaded in memory.', err);
|
if (!statusSpan) return;
|
||||||
});
|
|
||||||
|
if (isApproved) {
|
||||||
|
statusSpan.textContent = 'ONAYLANDI';
|
||||||
|
statusSpan.className = 'inline-flex px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-green-500 text-white border border-green-600 ml-2';
|
||||||
|
} else if (hasSaved) {
|
||||||
|
statusSpan.textContent = 'ONAY BEKLİYOR';
|
||||||
|
statusSpan.className = 'inline-flex px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-amber-50 text-amber-700 border border-amber-200 ml-2';
|
||||||
|
} else {
|
||||||
|
statusSpan.textContent = '';
|
||||||
|
statusSpan.className = 'hidden';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function printActiveDay() {
|
||||||
|
const btn = document.getElementById(`btn-day-${activeDayIdx}`);
|
||||||
|
if (!btn) return;
|
||||||
|
const dayNum = btn.getAttribute('data-day-num');
|
||||||
|
window.open(`{{ route('intern.print-journal') }}?size=a4&day=${dayNum}`, '_blank');
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendCommitToQuill(sha, time, msg) {
|
||||||
|
const escapedMsg = msg.replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
const commitHtml = `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${escapedMsg}</li>`;
|
||||||
|
|
||||||
|
const currentHtml = quill.root.innerHTML.trim();
|
||||||
|
const hasHeader = currentHtml.includes("Git Commit Mesajları:");
|
||||||
|
const headerHtml = hasHeader ? "" : "<p><strong>Git Commit Mesajları:</strong></p>";
|
||||||
|
|
||||||
|
let newHtml = "";
|
||||||
|
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
|
||||||
|
newHtml = currentHtml + "<br><br>" + headerHtml + "<ul>" + commitHtml + "</ul>";
|
||||||
|
} else {
|
||||||
|
newHtml = headerHtml + "<ul>" + commitHtml + "</ul>";
|
||||||
|
}
|
||||||
|
|
||||||
|
quill.setContents([]);
|
||||||
|
quill.clipboard.dangerouslyPasteHTML(newHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectNotebookDay(idx) {
|
function selectNotebookDay(idx) {
|
||||||
@@ -934,12 +1165,20 @@
|
|||||||
const dateVal = newBtn.getAttribute('data-date');
|
const dateVal = newBtn.getAttribute('data-date');
|
||||||
const dateFormatted = newBtn.getAttribute('data-formatted-date');
|
const dateFormatted = newBtn.getAttribute('data-formatted-date');
|
||||||
const savedContent = newBtn.getAttribute('data-saved-content');
|
const savedContent = newBtn.getAttribute('data-saved-content');
|
||||||
|
const isApproved = newBtn.getAttribute('data-approved') === '1';
|
||||||
|
|
||||||
document.getElementById('editor-day-title').textContent = `${dayNum}. Gün`;
|
document.getElementById('editor-day-title').textContent = `${dayNum}. Gün`;
|
||||||
document.getElementById('editor-day-date').textContent = dateFormatted;
|
document.getElementById('editor-day-date').textContent = dateFormatted;
|
||||||
|
|
||||||
quill.root.innerHTML = savedContent || '';
|
quill.setContents([]);
|
||||||
|
if (savedContent) {
|
||||||
|
quill.clipboard.dangerouslyPasteHTML(savedContent);
|
||||||
|
}
|
||||||
document.getElementById('save-status').textContent = '';
|
document.getElementById('save-status').textContent = '';
|
||||||
|
hideFallbackCommits();
|
||||||
|
|
||||||
|
const hasSaved = savedContent && savedContent.trim() !== '' && savedContent !== '<p><br></p>';
|
||||||
|
updateEditorStatusBadge(isApproved, hasSaved);
|
||||||
|
|
||||||
// Future validation
|
// Future validation
|
||||||
const dateParts = dateVal.split('-');
|
const dateParts = dateVal.split('-');
|
||||||
@@ -979,34 +1218,166 @@
|
|||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
|
|
||||||
const dateVal = btn.getAttribute('data-date');
|
const dateVal = btn.getAttribute('data-date');
|
||||||
const dayCommits = allGithubCommits[dateVal] || [];
|
const formattedDate = btn.getAttribute('data-formatted-date');
|
||||||
|
|
||||||
if (dayCommits.length === 0) {
|
if (!githubRepoUrl) {
|
||||||
alert("Bu staj gününe ait GitHub commit'i bulunamadı. Lütfen commit attığınızdan ve tarihin doğru olduğundan emin olun.");
|
alert("Lütfen önce GitHub depo URL'nizi kaydedin.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let commitHtml = "<ul>";
|
let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, '');
|
||||||
const sorted = [...dayCommits].reverse();
|
repoClean = repoClean.replace(/\/$/, '');
|
||||||
sorted.forEach(c => {
|
const parts = repoClean.split('/');
|
||||||
|
if (parts.length < 2) {
|
||||||
|
alert("Geçersiz GitHub URL'si.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const owner = parts[0];
|
||||||
|
const repo = parts[1].replace(/\.git$/i, '');
|
||||||
|
let branch = '';
|
||||||
|
|
||||||
|
// Parse branch if /tree/{branch} or /commits/{branch} is used
|
||||||
|
if (parts.length >= 4 && (parts[2] === 'tree' || parts[2] === 'commits')) {
|
||||||
|
branch = parts.slice(3).join('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
const gitBtn = document.querySelector('button[onclick="fillFromGithub()"]');
|
||||||
|
const originalContent = gitBtn.innerHTML;
|
||||||
|
gitBtn.disabled = true;
|
||||||
|
gitBtn.innerHTML = `<i class="uil uil-spinner text-xs animate-spin"></i> <span>Çekiliyor...</span>`;
|
||||||
|
|
||||||
|
hideFallbackCommits();
|
||||||
|
|
||||||
|
fetch(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=100${branch ? `&sha=${branch}` : ''}`)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 404) {
|
||||||
|
throw new Error("Depo bulunamadı veya herkese açık (public) değil.");
|
||||||
|
} else if (response.status === 403) {
|
||||||
|
throw new Error("GitHub API istek limitine ulaşıldı. Lütfen daha sonra tekrar deneyin.");
|
||||||
|
} else {
|
||||||
|
throw new Error(`GitHub API Hatası (Kod: ${response.status})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(commits => {
|
||||||
|
if (!Array.isArray(commits)) {
|
||||||
|
throw new Error("Geçersiz API yanıtı.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter commits by the selected date
|
||||||
|
const dayCommits = commits.filter(item => {
|
||||||
|
const dateStr = item.commit.author.date;
|
||||||
|
if (!dateStr) return false;
|
||||||
|
const d = new Date(dateStr);
|
||||||
|
const dateOnly = formatDateToIstanbul(d);
|
||||||
|
return dateOnly === dateVal;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (dayCommits.length === 0) {
|
||||||
|
// No commits found for this date. Show fallback list of latest 10 commits.
|
||||||
|
const fallbackContainer = document.getElementById('git-commits-fallback-container');
|
||||||
|
const fallbackList = document.getElementById('git-commits-fallback-list');
|
||||||
|
|
||||||
|
if (fallbackContainer && fallbackList) {
|
||||||
|
fallbackList.innerHTML = '';
|
||||||
|
|
||||||
|
const latestCommits = commits.slice(0, 10);
|
||||||
|
if (latestCommits.length === 0) {
|
||||||
|
fallbackList.innerHTML = `<p class="text-slate-400 italic">Depoda hiç commit bulunamadı.</p>`;
|
||||||
|
} else {
|
||||||
|
latestCommits.forEach(c => {
|
||||||
const msg = c.commit.message;
|
const msg = c.commit.message;
|
||||||
const sha = c.sha.substring(0, 7);
|
const sha = c.sha.substring(0, 7);
|
||||||
const authorDate = new Date(c.commit.author.date);
|
const authorDate = new Date(c.commit.author.date);
|
||||||
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
|
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
|
||||||
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
|
const commitDateOnly = formatDateToIstanbul(authorDate);
|
||||||
});
|
// format date to d.m.Y
|
||||||
commitHtml += "</ul>";
|
const [cy, cm, cd] = commitDateOnly.split('-');
|
||||||
|
const formattedCommitDate = `${cd}.${cm}.${cy}`;
|
||||||
|
|
||||||
const currentHtml = quill.root.innerHTML.trim();
|
const row = document.createElement('div');
|
||||||
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
|
row.className = "flex items-center justify-between gap-3 p-2 hover:bg-slate-100 rounded-lg border border-slate-100 transition-colors";
|
||||||
if (confirm("Mevcut rapor içeriğinizin sonuna bu günün commitlerini eklemek ister misiniz? (Hayır derseniz mevcut içerik silinip commitler yazılır.)")) {
|
|
||||||
quill.root.innerHTML = currentHtml + "<br><br>" + commitHtml;
|
const infoDiv = document.createElement('div');
|
||||||
|
infoDiv.className = "flex-grow min-w-0";
|
||||||
|
|
||||||
|
const timeSpan = document.createElement('span');
|
||||||
|
timeSpan.className = "font-bold text-slate-700 select-all";
|
||||||
|
timeSpan.textContent = `[${formattedCommitDate} ${time}] `;
|
||||||
|
|
||||||
|
const shaSpan = document.createElement('span');
|
||||||
|
shaSpan.className = "text-slate-500 font-semibold select-all";
|
||||||
|
shaSpan.textContent = `(${sha}) `;
|
||||||
|
|
||||||
|
const msgSpan = document.createElement('span');
|
||||||
|
msgSpan.className = "text-slate-600 truncate block sm:inline-block max-w-full font-medium ml-1";
|
||||||
|
msgSpan.textContent = msg;
|
||||||
|
|
||||||
|
infoDiv.appendChild(timeSpan);
|
||||||
|
infoDiv.appendChild(shaSpan);
|
||||||
|
infoDiv.appendChild(msgSpan);
|
||||||
|
|
||||||
|
const addBtn = document.createElement('button');
|
||||||
|
addBtn.type = 'button';
|
||||||
|
addBtn.className = "px-2 py-1 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white font-extrabold rounded transition-all flex-shrink-0 text-[10px]";
|
||||||
|
addBtn.textContent = "Ekle";
|
||||||
|
addBtn.addEventListener('click', () => {
|
||||||
|
appendCommitToQuill(sha, `${formattedCommitDate} ${time}`, msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
row.appendChild(infoDiv);
|
||||||
|
row.appendChild(addBtn);
|
||||||
|
fallbackList.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fallbackContainer.classList.remove('hidden');
|
||||||
|
alert(`Bu staj gününe (${formattedDate}) ait otomatik GitHub commit'i bulunamadı. Son 10 commit'iniz aşağıda listelenmiştir, rapora eklemek istediklerinizi seçebilirsiniz.`);
|
||||||
} else {
|
} else {
|
||||||
quill.root.innerHTML = commitHtml;
|
alert(`Bu staj gününe (${formattedDate}) ait GitHub commit'i bulunamadı. Lütfen commit attığınızdan ve tarihin doğru olduğundan emin olun.`);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store these commits in memory for processing
|
||||||
|
activeCommitsToProcess = dayCommits;
|
||||||
|
activeCommitsDateVal = dateVal;
|
||||||
|
|
||||||
|
// Get unique authors
|
||||||
|
const authorsSet = new Set();
|
||||||
|
dayCommits.forEach(item => {
|
||||||
|
const login = item.author ? item.author.login : null;
|
||||||
|
const name = item.commit.author.name;
|
||||||
|
if (login) authorsSet.add(login);
|
||||||
|
if (name) authorsSet.add(name);
|
||||||
|
});
|
||||||
|
const uniqueAuthors = Array.from(authorsSet);
|
||||||
|
|
||||||
|
// If there are multiple authors
|
||||||
|
if (uniqueAuthors.length > 1) {
|
||||||
|
// If we have a saved githubUsername that matches one of the unique authors
|
||||||
|
const matchedAuthor = uniqueAuthors.find(a => githubUsername && a.toLowerCase() === githubUsername.toLowerCase());
|
||||||
|
if (matchedAuthor) {
|
||||||
|
// Automatically filter and insert without modal
|
||||||
|
insertCommitsForAuthor(matchedAuthor);
|
||||||
|
} else {
|
||||||
|
// Show modal to choose
|
||||||
|
openAuthorModal(uniqueAuthors);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quill.root.innerHTML = commitHtml;
|
// Only 1 author, insert directly
|
||||||
|
renderCommitsToEditor(dayCommits);
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert("Commitler çekilirken hata oluştu: " + err.message);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
gitBtn.disabled = false;
|
||||||
|
gitBtn.innerHTML = originalContent;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveActiveDay() {
|
function saveActiveDay() {
|
||||||
@@ -1043,17 +1414,21 @@
|
|||||||
statusText.className = "text-xs font-semibold text-green-600";
|
statusText.className = "text-xs font-semibold text-green-600";
|
||||||
|
|
||||||
btn.setAttribute('data-saved-content', contentVal);
|
btn.setAttribute('data-saved-content', contentVal);
|
||||||
|
btn.setAttribute('data-approved', '0');
|
||||||
|
|
||||||
const badge = document.getElementById(`badge-day-${activeDayIdx}`);
|
const badge = document.getElementById(`badge-day-${activeDayIdx}`);
|
||||||
|
const hasContent = contentVal.trim() !== '' && contentVal !== '<p><br></p>';
|
||||||
if (badge) {
|
if (badge) {
|
||||||
if (contentVal.trim() !== '') {
|
if (hasContent) {
|
||||||
badge.textContent = 'DOLU';
|
badge.textContent = 'DOLU';
|
||||||
badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-green-50 text-green-700 border border-green-200";
|
badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-amber-50 text-amber-700 border border-amber-200";
|
||||||
} else {
|
} else {
|
||||||
badge.textContent = 'BOŞ';
|
badge.textContent = 'BOŞ';
|
||||||
badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-slate-50 text-slate-400 border border-slate-200";
|
badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-slate-50 text-slate-400 border border-slate-200";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateEditorStatusBadge(false, hasContent);
|
||||||
} else {
|
} else {
|
||||||
statusText.textContent = data.message || "Kaydedilemedi.";
|
statusText.textContent = data.message || "Kaydedilemedi.";
|
||||||
statusText.className = "text-xs font-semibold text-red-600";
|
statusText.className = "text-xs font-semibold text-red-600";
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<tr class="border-b border-slate-100 text-xs font-extrabold uppercase tracking-wider text-slate-400">
|
<tr class="border-b border-slate-100 text-xs font-extrabold uppercase tracking-wider text-slate-400">
|
||||||
<th class="pb-4">Stajyer</th>
|
<th class="pb-4">Stajyer</th>
|
||||||
<th class="pb-4">Staj Tarihleri</th>
|
<th class="pb-4">Staj Tarihleri</th>
|
||||||
|
<th class="pb-4">Defter İlerlemesi</th>
|
||||||
<th class="pb-4">Durum</th>
|
<th class="pb-4">Durum</th>
|
||||||
<th class="pb-4">Belgeler</th>
|
<th class="pb-4">Belgeler</th>
|
||||||
<th class="pb-4">GitHub & Repo</th>
|
<th class="pb-4">GitHub & Repo</th>
|
||||||
@@ -22,7 +23,10 @@
|
|||||||
<tr class="hover:bg-slate-50/50 transition-colors">
|
<tr class="hover:bg-slate-50/50 transition-colors">
|
||||||
<!-- Name & Info -->
|
<!-- Name & Info -->
|
||||||
<td class="py-4">
|
<td class="py-4">
|
||||||
<div class="font-bold text-slate-800 text-sm">{{ $intern->name }}</div>
|
<div class="font-bold text-slate-800 text-sm cursor-pointer hover:underline hover:text-blue-600 flex items-center gap-1.5" onclick="openInternJournalModal({{ $intern->id }})">
|
||||||
|
<span>{{ $intern->name }}</span>
|
||||||
|
<span class="inline-flex px-1.5 py-0.5 rounded-md bg-blue-50 text-blue-600 text-[9px] font-extrabold uppercase border border-blue-100 tracking-wider">Defteri İncele</span>
|
||||||
|
</div>
|
||||||
<div class="text-xs text-slate-400 mt-0.5 flex flex-col gap-0.5">
|
<div class="text-xs text-slate-400 mt-0.5 flex flex-col gap-0.5">
|
||||||
<span><i class="uil uil-envelope mr-1"></i>{{ $intern->email }}</span>
|
<span><i class="uil uil-envelope mr-1"></i>{{ $intern->email }}</span>
|
||||||
@if($intern->phone)
|
@if($intern->phone)
|
||||||
@@ -43,6 +47,30 @@
|
|||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
<!-- Defter İlerlemesi -->
|
||||||
|
<td class="py-4">
|
||||||
|
@if($intern->internship_total_days)
|
||||||
|
@php
|
||||||
|
$filledDays = $intern->journalEntries->filter(function($entry) {
|
||||||
|
return !empty(trim($entry->content));
|
||||||
|
})->count();
|
||||||
|
$progressPercent = ($filledDays / $intern->internship_total_days) * 100;
|
||||||
|
if ($progressPercent > 100) $progressPercent = 100;
|
||||||
|
@endphp
|
||||||
|
<div class="flex flex-col gap-1 max-w-[130px]">
|
||||||
|
<div class="flex justify-between text-[10px] font-extrabold">
|
||||||
|
<span class="text-slate-400">Doldurulan</span>
|
||||||
|
<span class="text-blue-600">{{ $filledDays }} / {{ $intern->internship_total_days }} Gün</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-100 h-2 rounded-full overflow-hidden border border-slate-200/40">
|
||||||
|
<div class="bg-blue-600 h-full rounded-full transition-all duration-300" style="width: {{ $progressPercent }}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<span class="text-slate-300 italic text-xs">Belirlenmemiş</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
|
||||||
<!-- Status -->
|
<!-- Status -->
|
||||||
<td class="py-4">
|
<td class="py-4">
|
||||||
<span class="inline-flex px-2.5 py-1 rounded-full text-[10px] font-extrabold uppercase tracking-wider
|
<span class="inline-flex px-2.5 py-1 rounded-full text-[10px] font-extrabold uppercase tracking-wider
|
||||||
|
|||||||
@@ -331,8 +331,8 @@
|
|||||||
<span>Panele Dön</span>
|
<span>Panele Dön</span>
|
||||||
</a>
|
</a>
|
||||||
<div style="border-left: 1px solid #e2e8f0; margin: 0 5px; height: 35px;"></div>
|
<div style="border-left: 1px solid #e2e8f0; margin: 0 5px; height: 35px;"></div>
|
||||||
<a href="?size=a4{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}" class="btn {{ $size === 'a4' ? 'btn-primary' : 'btn-secondary' }}">A4 Boyutu</a>
|
<a href="?size=a4{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}{{ request()->has('day') ? '&day='.request('day') : '' }}" class="btn {{ $size === 'a4' ? 'btn-primary' : 'btn-secondary' }}">A4 Boyutu</a>
|
||||||
<a href="?size=a5{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}" class="btn {{ $size === 'a5' ? 'btn-primary' : 'btn-secondary' }}">A5 Boyutu</a>
|
<a href="?size=a5{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}{{ request()->has('day') ? '&day='.request('day') : '' }}" class="btn {{ $size === 'a5' ? 'btn-primary' : 'btn-secondary' }}">A5 Boyutu</a>
|
||||||
<button onclick="window.print()" class="btn btn-primary">
|
<button onclick="window.print()" class="btn btn-primary">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
|
||||||
<span>Yazdır / PDF Kaydet</span>
|
<span>Yazdır / PDF Kaydet</span>
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
<link rel="preload" href="{{ asset('html/assets/css/fonts/urbanist.css') }}" as="style" onload="this.rel='stylesheet'">
|
<link rel="preload" href="{{ asset('html/assets/css/fonts/urbanist.css') }}" as="style" onload="this.rel='stylesheet'">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate">
|
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
|
<noscript><link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" rel="stylesheet"></noscript>
|
||||||
@stack('styles')
|
@stack('styles')
|
||||||
<style>
|
<style>
|
||||||
.navbar.navbar-light.fixed .btn:not(.btn-expand):not(.btn-gradient) {
|
.navbar.navbar-light.fixed .btn:not(.btn-expand):not(.btn-gradient) {
|
||||||
|
|||||||
@@ -17,16 +17,35 @@ $favicon_path = setting('site_favicon');
|
|||||||
}
|
}
|
||||||
@endphp
|
@endphp
|
||||||
@php
|
@php
|
||||||
$seo_canonical = $meta['canonical'] ?? url()->current();
|
$seo_canonical = $meta['canonical'] ?? null;
|
||||||
|
if (!$seo_canonical) {
|
||||||
|
$seo_canonical = url()->current();
|
||||||
|
$localeQuery = request()->query('locale') ?? request()->query('lang');
|
||||||
|
if ($localeQuery && function_exists('available_language_codes') && in_array($localeQuery, available_language_codes())) {
|
||||||
|
$seo_canonical .= '?locale=' . $localeQuery;
|
||||||
|
}
|
||||||
|
}
|
||||||
$seo_robots = $meta['robots'] ?? 'index, follow';
|
$seo_robots = $meta['robots'] ?? 'index, follow';
|
||||||
$seo_og_type = $meta['og_type'] ?? 'website';
|
$seo_og_type = $meta['og_type'] ?? 'website';
|
||||||
$seo_og_locale = str_replace('_', '-', $meta['locale'] ?? app()->getLocale());
|
$seo_og_locale = str_replace('_', '-', $meta['locale'] ?? app()->getLocale());
|
||||||
|
|
||||||
|
$activeLanguages = class_exists(\App\Models\Language::class)
|
||||||
|
? \App\Models\Language::where('is_active', true)->get()
|
||||||
|
: collect([]);
|
||||||
|
$currentUrl = url()->current();
|
||||||
@endphp
|
@endphp
|
||||||
<title>{{ $seo_title }}</title>
|
<title>{{ $seo_title }}</title>
|
||||||
<meta name="description" content="{{ $seo_description }}">
|
<meta name="description" content="{{ $seo_description }}">
|
||||||
<meta name="robots" content="{{ $seo_robots }}">
|
<meta name="robots" content="{{ $seo_robots }}">
|
||||||
<link rel="canonical" href="{{ $seo_canonical }}">
|
<link rel="canonical" href="{{ $seo_canonical }}">
|
||||||
|
|
||||||
|
@foreach($activeLanguages as $lang)
|
||||||
|
<link rel="alternate" hreflang="{{ $lang->code }}" href="{{ $currentUrl . '?locale=' . $lang->code }}">
|
||||||
|
@endforeach
|
||||||
|
@if($activeLanguages->count() > 0)
|
||||||
|
<link rel="alternate" hreflang="x-default" href="{{ $currentUrl }}">
|
||||||
|
@endif
|
||||||
|
|
||||||
<!-- Open Graph / Facebook -->
|
<!-- Open Graph / Facebook -->
|
||||||
<meta property="og:type" content="{{ $seo_og_type }}">
|
<meta property="og:type" content="{{ $seo_og_type }}">
|
||||||
<meta property="og:url" content="{{ $seo_canonical }}">
|
<meta property="og:url" content="{{ $seo_canonical }}">
|
||||||
@@ -58,6 +77,15 @@ $favicon_path = setting('site_favicon');
|
|||||||
|
|
||||||
<link rel="icon" href="{{ $favicon_path ? (str_starts_with($favicon_path, 'http') ? $favicon_path : asset($favicon_path)) : asset('assets/img/favicon.png') }}">
|
<link rel="icon" href="{{ $favicon_path ? (str_starts_with($favicon_path, 'http') ? $favicon_path : asset($favicon_path)) : asset('assets/img/favicon.png') }}">
|
||||||
@stack('head')
|
@stack('head')
|
||||||
|
@if(request()->routeIs('homepage'))
|
||||||
|
@php
|
||||||
|
$hero_bg_preload = setting('hero_bg') ?: './assets/img/photos/bg16.webp';
|
||||||
|
if ($hero_bg_preload && !str_starts_with($hero_bg_preload, 'http')) {
|
||||||
|
$hero_bg_preload = asset($hero_bg_preload);
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
<link rel="preload" href="{{ $hero_bg_preload }}" as="image" fetchpriority="high">
|
||||||
|
@endif
|
||||||
<link rel="stylesheet" href="{{ asset('html/style.css') }}">
|
<link rel="stylesheet" href="{{ asset('html/style.css') }}">
|
||||||
<link rel="preload" href="{{ asset('assets/css/icon.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
<link rel="preload" href="{{ asset('assets/css/icon.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
<noscript><link rel="stylesheet" href="{{ asset('assets/css/icon.css') }}"></noscript>
|
<noscript><link rel="stylesheet" href="{{ asset('assets/css/icon.css') }}"></noscript>
|
||||||
@@ -70,8 +98,10 @@ $favicon_path = setting('site_favicon');
|
|||||||
<link rel="preload" href="{{ asset('assets/css/fonts/space.css') }}" as="style" onload="this.rel='stylesheet'">
|
<link rel="preload" href="{{ asset('assets/css/fonts/space.css') }}" as="style" onload="this.rel='stylesheet'">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Caveat:wght@600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate">
|
<noscript><link href="https://fonts.googleapis.com/css2?family=Caveat:wght@600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet"></noscript>
|
||||||
|
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||||
|
<noscript><link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" rel="stylesheet"></noscript>
|
||||||
|
|
||||||
<!-- Site Verification -->
|
<!-- Site Verification -->
|
||||||
@if(setting('seo_google_search_console'))
|
@if(setting('seo_google_search_console'))
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
{!! '<' . '?xml version="1.0" encoding="UTF-8"?' . '>' !!}
|
{!! '<' . '?xml version="1.0" encoding="UTF-8"?' . '>' !!}
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||||
|
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||||
@foreach($urls as $url)
|
@foreach($urls as $url)
|
||||||
<url>
|
<url>
|
||||||
<loc>{{ $url['loc'] }}</loc>
|
<loc>{{ $url['loc'] }}</loc>
|
||||||
|
@if(!empty($url['alternates']))
|
||||||
|
@foreach($url['alternates'] as $alt)
|
||||||
|
<xhtml:link rel="alternate" hreflang="{{ $alt['hreflang'] }}" href="{{ $alt['href'] }}"/>
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
<lastmod>{{ $url['lastmod'] }}</lastmod>
|
<lastmod>{{ $url['lastmod'] }}</lastmod>
|
||||||
<changefreq>{{ $url['changefreq'] }}</changefreq>
|
<changefreq>{{ $url['changefreq'] }}</changefreq>
|
||||||
<priority>{{ $url['priority'] }}</priority>
|
<priority>{{ $url['priority'] }}</priority>
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ Route::post('/stajyer/admin/giris', [\App\Http\Controllers\CareerController::cla
|
|||||||
Route::get('/stajyer/admin/panel', [\App\Http\Controllers\CareerController::class, 'internAdminDashboard'])->name('intern.admin.dashboard');
|
Route::get('/stajyer/admin/panel', [\App\Http\Controllers\CareerController::class, 'internAdminDashboard'])->name('intern.admin.dashboard');
|
||||||
Route::get('/stajyer/admin/journal-entry', [\App\Http\Controllers\CareerController::class, 'getJournalEntry'])->name('intern.admin.get-journal-entry');
|
Route::get('/stajyer/admin/journal-entry', [\App\Http\Controllers\CareerController::class, 'getJournalEntry'])->name('intern.admin.get-journal-entry');
|
||||||
Route::post('/stajyer/admin/toggle-approval', [\App\Http\Controllers\CareerController::class, 'toggleJournalApproval'])->name('intern.admin.toggle-journal-approval');
|
Route::post('/stajyer/admin/toggle-approval', [\App\Http\Controllers\CareerController::class, 'toggleJournalApproval'])->name('intern.admin.toggle-journal-approval');
|
||||||
|
Route::get('/stajyer/admin/journal-details', [\App\Http\Controllers\CareerController::class, 'getInternJournalDetails'])->name('intern.admin.get-journal-details');
|
||||||
|
Route::post('/stajyer/admin/toggle-notebook-signature', [\App\Http\Controllers\CareerController::class, 'toggleNotebookSignature'])->name('intern.admin.toggle-notebook-signature');
|
||||||
Route::post('/stajyer/admin/cikis', [\App\Http\Controllers\CareerController::class, 'internAdminLogout'])->name('intern.admin.logout');
|
Route::post('/stajyer/admin/cikis', [\App\Http\Controllers\CareerController::class, 'internAdminLogout'])->name('intern.admin.logout');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user