46 Commits

Author SHA1 Message Date
Ümit Tunç 42fcc5759f fix: set font-display to swap for all custom fonts and remove obsolete Tailwind CSS base styles 2026-07-04 01:12:02 +03:00
Ümit Tunç 161d1c86bc feat: implement multilingual support for sitemap and SEO, update structured data, and improve locale middleware configuration 2026-07-04 00:59:55 +03:00
Ümit Tunç 4af8900a78 feat: add external admin dashboard link to navigation and remove Gantt chart widget from intern applications page 2026-07-02 10:40:10 +03:00
Ümit Tunç abcf105d07 feat: add intern panel navigation link and user password reset CLI command 2026-07-02 10:32:02 +03:00
Ümit Tunç d0292af20c feat: add GitHub username field and implement commit filtering by author in intern dashboard 2026-07-01 23:46:12 +03:00
Ümit Tunç 26670fcca5 feat: implement internship journal approval status tracking and add single-day printing functionality 2026-07-01 23:36:26 +03:00
Ümit Tunç f557d4b91c feat: replace eager in-memory GitHub commit fetching with on-demand retrieval and add a fallback UI for missing commits. 2026-07-01 23:34:42 +03:00
Ümit Tunç 09e92506fd feat: implement tabbed interface to toggle between Gantt chart and intern list in admin dashboard 2026-07-01 23:15:36 +03:00
Ümit Tunç 129dae4570 feat: add journal progress tracking UI for internship applications in the career dashboard 2026-07-01 23:06:13 +03:00
Ümit Tunç f0a9180a54 feat: add journal detail tracking, progress reporting, and notebook signing functionality for intern management 2026-07-01 23:04:23 +03:00
Ümit Tunç a6c250c13e refactor: simplify internship approval process by removing unit approval and adding supervisor name tracking 2026-07-01 22:37:08 +03:00
Ümit Tunç f84f78a138 feat: add approval system for internship journal entries including database fields, admin toggle functionality, and rich-text rendering 2026-07-01 21:58:56 +03:00
Ümit Tunç d9c1c825d9 style: increase container maximum width to 1440px in intern dashboard view 2026-07-01 17:49:06 +03:00
Ümit Tunç 13f8f09524 refactor: move tab logic and implement Quill editor and GitHub commit integration in scratch_test.js 2026-07-01 17:46:57 +03:00
Ümit Tunç 351b9064af feat: integrate Quill editor for internship journal entries, add retroactive field support, and enforce future date restrictions 2026-07-01 17:43:22 +03:00
Ümit Tunç edc843fa1c feat: restrict verification page view to accepted applications only 2026-07-01 17:40:06 +03:00
Ümit Tunç 8cf9e0b191 style: update career portal branding, logo assets, and dashboard text visibility 2026-07-01 17:25:52 +03:00
Ümit Tunç 44852bdc95 feat: add download icon and text to signed internship form button in intern dashboard 2026-07-01 17:21:39 +03:00
Ümit Tunç 7b70621c5a feat: implement interactive intern dashboard journal features and correct resource component namespace 2026-07-01 17:20:19 +03:00
Ümit Tunç 4c5cf1d355 feat: implement internship journal entry system with printable reports and approval workflow 2026-07-01 17:16:41 +03:00
Ümit Tunç 5b46b4ac9e refactor: restrict intern dashboard updates to pending status and automate username assignment from email 2026-07-01 17:06:11 +03:00
Ümit Tunç 32c556a87b feat: add internship workflow guide modal to dashboards and visualize current date in Gantt chart 2026-06-26 16:42:24 +03:00
Ümit Tunç aa6858bc0f chore: reorder DevExtreme script dependencies in gantt partial 2026-06-26 16:01:17 +03:00
Ümit Tunç 04e62ea9b8 refactor: modularize admin dashboard by extracting components into separate partial views 2026-06-26 16:00:10 +03:00
Ümit Tunç 66366aa8b8 refactor: modularize admin dashboard view with partials and update intern parent ID mapping for gantt chart rendering 2026-06-26 15:59:59 +03:00
Ümit Tunç 0e558fd1dc feat: implement intern admin portal with login and dashboard management functionality 2026-06-26 15:54:21 +03:00
Ümit Tunç 01be250e6c feat: add summer background images to assets directory 2026-06-25 16:19:07 +03:00
Ümit Tunç 6614d13be2 refactor: update service cards to full-width mobile layout and apply light theme glass styling 2026-06-23 11:24:06 +03:00
Ümit Tunç 61480399b3 refactor: wrap service cards in anchor tags and update styling for a glassmorphism effect 2026-06-23 11:23:15 +03:00
Ümit Tunç e3e7ac7f18 style: add mobile-specific background image styling to hero section 2026-06-23 11:10:28 +03:00
Ümit Tunç f1c7e57e43 style: refine services card responsive typography and layout styling 2026-06-23 11:06:06 +03:00
Ümit Tunç ad50102c42 feat: add localized routes for privacy policy pages 2026-06-23 10:57:52 +03:00
Ümit Tunç 007da1227d fix: update intern gantt chart scale type from weeks to days 2026-06-16 17:26:32 +03:00
Ümit Tunç 58ae406b36 feat: replace custom gantt chart implementation with DevExtreme widget 2026-06-16 17:14:54 +03:00
Ümit Tunç 794f9556de feat: implement custom responsive gantt chart UI for intern applications widget 2026-06-16 15:28:20 +03:00
Ümit Tunç 6be7f4442a feat: add Gantt chart widget to visualize accepted intern schedules in the admin panel 2026-06-16 15:27:28 +03:00
Ümit Tunç 9361a01c80 feat: update intern dashboard to display signed status and prevent modifications for approved internship forms 2026-06-10 21:02:03 +03:00
Ümit Tunç f9abe3c02a feat: add visual progress timeline to the intern application dashboard 2026-06-10 20:47:57 +03:00
Ümit Tunç efc6b911e7 fix: clear password field state in intern application resource form 2026-06-10 20:44:09 +03:00
Ümit Tunç 793a843be7 fix: remove authorized signature section from application verification view 2026-06-08 19:39:03 +03:00
Ümit Tunç 742303eb73 refactor: remove decorative borders and normalize print layout styling for PDF generation 2026-06-08 19:35:08 +03:00
Ümit Tunç cfce7b8b3f style: convert certificate text elements to uppercase and remove redundant classes 2026-06-08 19:25:34 +03:00
Ümit Tunç 13d12e4f8f style: update career certificate typography with new Cinzel font class and layout adjustments 2026-06-08 19:19:16 +03:00
Ümit Tunç ba6431e500 refactor: remove print button and update decorative underline styling in career verification page 2026-06-08 19:19:11 +03:00
Ümit Tunç eca57bf9cc style: add text-glow-white utility class and update career verification UI typography colors 2026-06-08 19:15:46 +03:00
Ümit Tunç 89b81e6ee8 style: refine verification banner and button UI for improved layout and spacing 2026-06-08 18:53:25 +03:00
44 changed files with 4653 additions and 32269 deletions
@@ -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;
}
}
@@ -107,13 +107,17 @@ class InternApplicationResource extends Resource
TextInput::make('username') TextInput::make('username')
->label('Kullanıcı Adı') ->label('Kullanıcı Adı')
->unique(ignoreRecord: true) ->default(fn ($record) => $record?->email)
->nullable(), ->disabled()
->dehydrated()
->autocomplete('new-username'),
TextInput::make('password') TextInput::make('password')
->label('Şifre') ->label('Şifre')
->password() ->password()
->revealable() ->revealable()
->autocomplete('new-password')
->formatStateUsing(fn () => null)
->dehydrateStateUsing(fn ($state) => filled($state) ? Hash::make($state) : null) ->dehydrateStateUsing(fn ($state) => filled($state) ? Hash::make($state) : null)
->dehydrated(fn ($state) => filled($state)) ->dehydrated(fn ($state) => filled($state))
->placeholder('Şifreyi değiştirmek istemiyorsanız boş bırakın') ->placeholder('Şifreyi değiştirmek istemiyorsanız boş bırakın')
@@ -136,6 +140,12 @@ class InternApplicationResource extends Resource
->disk('public') ->disk('public')
->directory('signed_interns') ->directory('signed_interns')
->downloadable() ->downloadable()
->live()
->afterStateUpdated(function ($state, Set $set) {
if ($state) {
$set('status', 'accepted');
}
})
->nullable(), ->nullable(),
DatePicker::make('internship_start_date') DatePicker::make('internship_start_date')
@@ -177,6 +187,150 @@ class InternApplicationResource extends Resource
->columnSpanFull() ->columnSpanFull()
]), ]),
Tab::make('Staj Defteri & Onay')
->icon('heroicon-o-book-open')
->schema([
\Filament\Forms\Components\Placeholder::make('notebook_view')
->label('Doldurulan Staj Defteri')
->content(function ($record) {
if (!$record) return 'Henüz başvuru bulunmamaktadır.';
$days = \App\Http\Controllers\CareerController::getInternshipDates($record->internship_start_date, $record->internship_total_days);
if (empty($days)) return 'Staj başlangıç tarihi veya süresi girilmemiş.';
$saved = $record->journalEntries()->get()->keyBy('day_number');
$html = '<style>
.rich-text-content p { margin-bottom: 8px; }
.rich-text-content ul { list-style-type: disc; padding-left: 20px; margin-bottom: 8px; }
.rich-text-content ol { list-style-type: decimal; padding-left: 20px; margin-bottom: 8px; }
.rich-text-content li { margin-bottom: 4px; }
</style>';
$html .= '<div class="space-y-4" style="max-height: 400px; overflow-y: auto; padding-right: 10px; border: 1px solid #cbd5e1; border-radius: 8px; padding: 15px;">';
foreach ($days as $d) {
$dayNum = $d['day_number'];
$dateF = $d['formatted_date'];
$entry = $saved->get($dayNum);
$content = $entry ? $entry->content : '';
$isRetro = $entry ? $entry->is_retroactive : false;
$updatedAt = $entry ? $entry->updated_at->format('d.m.Y H:i') : null;
$html .= '<div style="margin-bottom: 12px; padding: 12px; border: 1px solid #e2e8f0; border-radius: 8px; background: #f8fafc;">';
$html .= ' <div style="display:flex; justify-content:between; font-size:11px; font-weight:700; color:#475569; border-bottom:1px solid #e2e8f0; padding-bottom:6px; margin-bottom:8px;">';
$html .= ' <span style="font-weight: 800; color: #2563eb;">' . $dayNum . '. Gün Raporu</span>';
if ($isRetro) {
$html .= ' <span style="margin-left: 10px; background: #fee2e2; color: #991b1b; padding: 1px 6px; border-radius: 4px; font-size: 9px; font-weight: 800;">GERİYE DÖNÜK KAYIT</span>';
}
if ($updatedAt) {
$html .= ' <span style="margin-left: auto;">Son Güncelleme: ' . $updatedAt . '</span>';
} else {
$html .= ' <span style="margin-left: auto;">' . $dateF . '</span>';
}
$html .= ' </div>';
$cleanContent = $content ? strip_tags($content, ['p', 'strong', 'ul', 'li', 'em', 'br', 'b', 'i', 'ol', 'span']) : '<em style="color:#94a3b8;">Rapor yazılmamış</em>';
$html .= ' <div class="rich-text-content" style="font-size:12px; color:#1e293b; line-height:1.5;">' . $cleanContent . '</div>';
if ($entry && trim($content) !== '') {
$supApproved = $entry->supervisor_approved;
$supName = $entry->supervisor_name;
$html .= ' <div style="display:flex; align-items:center; gap:12px; margin-top:12px; padding-top:10px; border-top:1px dashed #e2e8f0; font-size:11px;">';
// Supervisor approval
$supBg = $supApproved ? '#d1fae5' : '#f1f5f9';
$supColor = $supApproved ? '#065f46' : '#64748b';
$supText = $supApproved ? 'Sorumlu Onayladı' . ($supName ? ' (Onaylayan: ' . e($supName) . ')' : '') : 'Sorumlu Onayı Bekliyor';
$html .= ' <span id="sup-badge-' . $entry->id . '" style="background:' . $supBg . '; color:' . $supColor . '; padding: 2px 8px; border-radius: 4px; font-weight: 700;">' . $supText . '</span>';
// Buttons
$supBtnText = $supApproved ? 'Onayı Kaldır' : 'Onayla';
$supBtnBg = $supApproved ? '#ef4444' : '#2563eb';
$html .= ' <button type="button" onclick="toggleApproval(' . $entry->id . ', this)" style="margin-left:auto; background:' . $supBtnBg . '; color:white; border:none; padding:4px 10px; border-radius:6px; font-weight:bold; cursor:pointer; font-size:10px;">Sorumlu ' . $supBtnText . '</button>';
$html .= ' </div>';
}
$html .= '</div>';
}
$html .= '</div>';
// JS handler
$html .= '
<script>
if (typeof window.toggleApproval !== "function") {
window.toggleApproval = function(entryId, btn) {
btn.disabled = true;
btn.style.opacity = "0.5";
fetch("' . route('intern.admin.toggle-journal-approval') . '", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": "' . csrf_token() . '"
},
body: JSON.stringify({
entry_id: entryId
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
const badge = document.getElementById("sup-badge-" + entryId);
if (data.status) {
badge.style.background = "#d1fae5";
badge.style.color = "#065f46";
badge.textContent = "Sorumlu Onayladı (Onaylayan: " + data.supervisor_name + ")";
btn.textContent = "Sorumlu Onayı Kaldır";
btn.style.background = "#ef4444";
} else {
badge.style.background = "#f1f5f9";
badge.style.color = "#64748b";
badge.textContent = "Sorumlu Onayı Bekliyor";
btn.textContent = "Sorumlu Onayla";
btn.style.background = "#2563eb";
}
} else {
alert(data.message || "Bir hata oluştu.");
}
})
.catch(err => {
console.error(err);
alert("Bağlantı hatası oluştu.");
})
.finally(() => {
btn.disabled = false;
btn.style.opacity = "1";
});
};
}
</script>
';
// Add preview buttons
$html .= '<div style="margin-top: 15px; display: flex; gap: 10px;">';
$html .= ' <a href="' . route('intern.print-journal') . '?size=a4&intern_id=' . $record->id . '" target="_blank" style="display:inline-flex; align-items:center; justify-content:center; padding: 8px 16px; background:#2563eb; color:white; border-radius:8px; font-weight:bold; font-size:12px; text-decoration:none; box-shadow: 0 1px 3px rgba(37, 99, 235, 0.2);">A4 Defteri Önizle / Yazdır</a>';
$html .= ' <a href="' . route('intern.print-journal') . '?size=a5&intern_id=' . $record->id . '" target="_blank" style="display:inline-flex; align-items:center; justify-content:center; padding: 8px 16px; background:#4b5563; color:white; border-radius:8px; font-weight:bold; font-size:12px; text-decoration:none; box-shadow: 0 1px 3px rgba(75, 85, 99, 0.2);">A5 Defteri Önizle / Yazdır</a>';
$html .= '</div>';
return new \Illuminate\Support\HtmlString($html);
})
->columnSpanFull(),
\Filament\Schemas\Components\Section::make('Onay ve İmza Bilgileri')
->schema([
\Filament\Forms\Components\Toggle::make('notebook_supervisor_signed')
->label('Staj Sorumlusu İmzala / Onayla')
->live(),
TextInput::make('notebook_supervisor_name')
->label('Staj Sorumlusu Adı / Ünvanı')
->placeholder('Örn: Alperen Trunç')
->default('Alperen Trunç'),
\Filament\Forms\Components\Toggle::make('notebook_approved')
->label('Staj Defterini Genel Olarak Onayla')
->columnSpanFull(),
])->columns(2),
]),
Tab::make('Sertifika & Transkript') Tab::make('Sertifika & Transkript')
->icon('heroicon-o-academic-cap') ->icon('heroicon-o-academic-cap')
->schema([ ->schema([
@@ -355,4 +509,5 @@ class InternApplicationResource extends Resource
'edit' => Pages\EditInternApplication::route('/{record}/edit'), 'edit' => Pages\EditInternApplication::route('/{record}/edit'),
]; ];
} }
} }
@@ -0,0 +1,2 @@
<?php
// Bu bileşen devre dışı bırakılmıştır.
+419 -5
View File
@@ -122,8 +122,13 @@ class CareerController extends Controller
} }
$intern = CareerApplication::findOrFail(session('intern_id')); $intern = CareerApplication::findOrFail(session('intern_id'));
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
return view('front.career.intern_dashboard', [ return view('front.career.intern_dashboard', [
'intern' => $intern, 'intern' => $intern,
'days' => $days,
'savedEntries' => $savedEntries,
'meta' => [ 'meta' => [
'title' => 'Stajyer Paneli', 'title' => 'Stajyer Paneli',
'description' => 'Belgelerinizi buradan indirebilirsiniz.', 'description' => 'Belgelerinizi buradan indirebilirsiniz.',
@@ -186,6 +191,7 @@ class CareerController extends Controller
'internship_start_date' => $request->internship_start_date, 'internship_start_date' => $request->internship_start_date,
'internship_end_date' => $endDate->format('Y-m-d'), 'internship_end_date' => $endDate->format('Y-m-d'),
'internship_total_days' => $daysToAdd, 'internship_total_days' => $daysToAdd,
'status' => 'waiting_document',
]); ]);
return redirect()->back()->with('success', 'İmzalanacak staj formunuz ve staj tarihleri başarıyla kaydedildi.'); return redirect()->back()->with('success', 'İmzalanacak staj formunuz ve staj tarihleri başarıyla kaydedildi.');
@@ -202,31 +208,51 @@ 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()
{ {
if (!session()->has('intern_id')) { if (request()->has('intern_id') && auth()->check() && auth()->user()->hasRole('super_admin')) {
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.'); $internId = request('intern_id');
} else {
if (!session()->has('intern_id')) {
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
}
$internId = session('intern_id');
} }
$intern = CareerApplication::findOrFail(session('intern_id'));
$intern = CareerApplication::findOrFail($internId);
$repo = $intern->github_repo; $repo = $intern->github_repo;
if (!$repo) { if (!$repo) {
return redirect()->back()->with('error', 'Lütfen önce Github deposunu tanımlayın.'); return redirect()->back()->with('error', 'Lütfen önce Github deposunu tanımlayın.');
} }
@@ -319,12 +345,400 @@ class CareerController extends Controller
->where('type', 'internship') ->where('type', 'internship')
->firstOrFail(); ->firstOrFail();
$days = self::getInternshipDates($application->internship_start_date, $application->internship_total_days);
$savedEntries = $application->journalEntries()->get()->keyBy('day_number');
return view('front.career.verify', [ return view('front.career.verify', [
'application' => $application, 'application' => $application,
'days' => $days,
'savedEntries' => $savedEntries,
'meta' => [ 'meta' => [
'title' => 'Staj Bitirme Sertifikası Doğrulama - ' . $application->name, 'title' => 'Staj Bitirme Sertifikası Doğrulama - ' . $application->name,
'description' => $application->name . ' isimli stajyerimizin staj bitirme sertifikası ve performans raporu doğrulama sayfası.', 'description' => $application->name . ' isimli stajyerimizin staj bitirme sertifikası ve performans raporu doğrulama sayfası.',
] ]
]); ]);
} }
public function internAdminLoginForm()
{
if (auth()->check() && auth()->user()->hasRole('super_admin')) {
return redirect()->route('intern.admin.dashboard');
}
return view('front.career.admin_login', [
'meta' => [
'title' => 'Yönetici Girişi - Staj Takip',
'description' => 'Stajyerleri yönetmek için giriş yapın.',
]
]);
}
public function internAdminLogin(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required|string',
]);
if (auth()->attempt($request->only('email', 'password'))) {
if (auth()->user()->hasRole('super_admin')) {
return redirect()->route('intern.admin.dashboard')->with('success', 'Yönetici girişi başarıyla gerçekleştirildi.');
}
auth()->logout();
return redirect()->back()
->withInput($request->only('email'))
->withErrors([
'email' => 'Bu panele erişmek için super_admin yetkinizin olması gerekir.',
]);
}
return redirect()->back()
->withInput($request->only('email'))
->withErrors([
'email' => 'Girdiğiniz bilgiler eşleşmedi veya hatalı.',
]);
}
public function internAdminDashboard()
{
if (!auth()->check() || !auth()->user()->hasRole('super_admin')) {
return redirect()->route('intern.admin.login')->with('error', 'Lütfen önce yönetici olarak giriş yapın.');
}
$allInterns = CareerApplication::where('type', 'internship')
->with(['journalEntries'])
->orderBy('created_at', 'desc')
->get();
$ganttInterns = CareerApplication::where('type', 'internship')
->where('status', 'accepted')
->whereNotNull('internship_start_date')
->whereNotNull('internship_end_date')
->orderBy('internship_start_date', 'asc')
->get()
->map(function ($intern) {
return [
'id' => $intern->id,
'parentId' => null,
'title' => $intern->name,
'start' => $intern->internship_start_date,
'end' => $intern->internship_end_date,
'progress' => 100
];
});
return view('front.career.admin_dashboard', [
'allInterns' => $allInterns,
'ganttInterns' => $ganttInterns,
'meta' => [
'title' => 'Staj Yönetici Paneli',
'description' => 'Stajyer bilgilerini ve takvimlerini izleyin.',
]
]);
}
public function internAdminLogout()
{
auth()->logout();
return redirect()->route('intern.admin.login')->with('success', 'Yönetici oturumu sonlandırıldı.');
}
public static function getInternshipDates($startDate, $totalDays)
{
if (!$startDate || !$totalDays || $totalDays <= 0) {
return [];
}
$dates = [];
$temp = \Carbon\Carbon::parse($startDate);
$daysToAdd = intval($totalDays);
$count = 0;
while ($count < $daysToAdd) {
if ($temp->isWeekend()) {
$temp->addDay();
continue;
}
$dates[] = [
'day_number' => $count + 1,
'date' => $temp->format('Y-m-d'),
'formatted_date' => $temp->format('d.m.Y'),
];
$temp->addDay();
$count++;
}
return $dates;
}
public function saveJournalEntry(Request $request)
{
if (!session()->has('intern_id')) {
return response()->json(['success' => false, 'message' => 'Lütfen giriş yapın.'], 401);
}
$intern = CareerApplication::findOrFail(session('intern_id'));
$request->validate([
'day_number' => 'required|integer|min:1',
'date' => 'required|date',
'content' => 'nullable|string',
]);
$dateObj = \Carbon\Carbon::parse($request->date);
// 1. Prevent future entries
if ($dateObj->isFuture()) {
return response()->json(['success' => false, 'message' => 'İleriye dönük staj günleri için defter doldurulamaz.'], 422);
}
// 2. Prevent weekend entries
if ($dateObj->isWeekend()) {
return response()->json(['success' => false, 'message' => 'Hafta sonu günlerine staj günlüğü girilemez.'], 422);
}
// 3. Determine if retroactive
$isRetroactive = $dateObj->lt(\Carbon\Carbon::today());
$entry = \App\Models\InternshipJournalEntry::updateOrCreate([
'career_application_id' => $intern->id,
'day_number' => $request->day_number,
'date' => $request->date,
], [
'content' => $request->content,
'is_retroactive' => $isRetroactive,
'supervisor_approved' => false,
'supervisor_name' => null,
]);
return response()->json([
'success' => true,
'message' => $request->day_number . '. Gün kaydı başarıyla kaydedildi.' . ($isRetroactive ? ' (Geriye Dönük Kayıt)' : ''),
'entry' => $entry
]);
}
public function printJournal(Request $request)
{
if (request()->has('intern_id') && auth()->check() && auth()->user()->hasRole('super_admin')) {
$internId = request('intern_id');
} else {
if (!session()->has('intern_id')) {
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
}
$internId = session('intern_id');
}
$intern = CareerApplication::findOrFail($internId);
$size = $request->query('size', 'a4');
if (!in_array($size, ['a4', 'a5'])) {
$size = 'a4';
}
$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');
return view('front.career.print', [
'intern' => $intern,
'days' => $days,
'savedEntries' => $savedEntries,
'size' => $size,
]);
}
public function getJournalEntry(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',
'date' => 'required|date',
]);
$intern = \App\Models\CareerApplication::findOrFail($request->intern_id);
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
$dayNum = null;
$dateFormatted = null;
foreach ($days as $d) {
if ($d['date'] === $request->date) {
$dayNum = $d['day_number'];
$dateFormatted = $d['formatted_date'];
break;
}
}
if (!$dayNum) {
return response()->json([
'success' => false,
'message' => 'Seçilen tarih staj dönemi dışındadır veya haftasonudur.'
], 422);
}
$entry = $intern->journalEntries()->where('date', $request->date)->first();
return response()->json([
'success' => true,
'intern_name' => $intern->name,
'day_number' => $dayNum,
'date' => $request->date,
'date_formatted' => $dateFormatted,
'entry' => $entry ? [
'id' => $entry->id,
'content' => $entry->content,
'is_retroactive' => $entry->is_retroactive,
'supervisor_approved' => $entry->supervisor_approved,
'supervisor_name' => $entry->supervisor_name,
] : null
]);
}
public function toggleJournalApproval(Request $request)
{
if (!auth()->check()) {
return response()->json(['success' => false, 'message' => 'Yetkisiz işlem.'], 403);
}
$request->validate([
'entry_id' => 'required|integer|exists:internship_journal_entries,id',
]);
$entry = \App\Models\InternshipJournalEntry::findOrFail($request->entry_id);
$entry->supervisor_approved = !$entry->supervisor_approved;
if ($entry->supervisor_approved) {
$entry->supervisor_name = auth()->user()->name;
} else {
$entry->supervisor_name = null;
}
$entry->save();
return response()->json([
'success' => true,
'status' => $entry->supervisor_approved,
'supervisor_name' => $entry->supervisor_name,
'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,
]
]);
}
} }
+38 -6
View File
@@ -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',
+19 -11
View File
@@ -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'];
}
// 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 session'da locale yoksa, varsayılan dil kodunu kullan // 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 {
+25 -5
View File
@@ -30,18 +30,35 @@ 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_name',
'notebook_unit_signed',
'notebook_unit_name',
'notebook_approved',
]; ];
/**
* Get the journal entries for the internship application.
*/
public function journalEntries()
{
return $this->hasMany(InternshipJournalEntry::class);
}
protected static function booted() protected static function booted()
{ {
static::saving(function ($model) { static::saving(function ($model) {
if ($model->type === 'internship' && !$model->certificate_code) { if ($model->type === 'internship') {
do { $model->username = $model->email;
$code = 'TRN-' . date('Y') . '-' . strtoupper(\Illuminate\Support\Str::random(4)) . '-' . strtoupper(\Illuminate\Support\Str::random(4)); if (!$model->certificate_code) {
} while (static::where('certificate_code', $code)->exists()); do {
$model->certificate_code = $code; $code = 'TRN-' . date('Y') . '-' . strtoupper(\Illuminate\Support\Str::random(4)) . '-' . strtoupper(\Illuminate\Support\Str::random(4));
} while (static::where('certificate_code', $code)->exists());
$model->certificate_code = $code;
}
} }
}); });
} }
@@ -55,6 +72,9 @@ class CareerApplication extends Model
{ {
return [ return [
'password' => 'hashed', 'password' => 'hashed',
'notebook_supervisor_signed' => 'boolean',
'notebook_unit_signed' => 'boolean',
'notebook_approved' => 'boolean',
]; ];
} }
} }
+36
View File
@@ -0,0 +1,36 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class InternshipJournalEntry extends Model
{
use HasFactory;
protected $fillable = [
'career_application_id',
'day_number',
'date',
'content',
'is_retroactive',
'supervisor_approved',
'unit_approved',
'supervisor_name',
];
protected $casts = [
'is_retroactive' => 'boolean',
'supervisor_approved' => 'boolean',
'unit_approved' => 'boolean',
];
/**
* Get the career application that owns this journal entry.
*/
public function careerApplication()
{
return $this->belongsTo(CareerApplication::class);
}
}
@@ -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')),
]); ]);
+37 -1
View File
@@ -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',
]; ];
} }
@@ -0,0 +1,29 @@
<?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('internship_journal_entries', function (Blueprint $table) {
$table->boolean('supervisor_approved')->default(false);
$table->boolean('unit_approved')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('internship_journal_entries', function (Blueprint $table) {
$table->dropColumn(['supervisor_approved', 'unit_approved']);
});
}
};
@@ -0,0 +1,36 @@
<?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::create('internship_journal_entries', function (Blueprint $table) {
$table->id();
$table->foreignId('career_application_id')
->constrained('career_applications')
->onDelete('cascade');
$table->integer('day_number');
$table->date('date');
$table->text('content')->nullable();
$table->timestamps();
$table->unique(['career_application_id', 'day_number'], 'journal_app_day_unique');
$table->unique(['career_application_id', 'date'], 'journal_app_date_unique');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('internship_journal_entries');
}
};
@@ -0,0 +1,38 @@
<?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->boolean('notebook_supervisor_signed')->default(false);
$table->string('notebook_supervisor_name')->nullable();
$table->boolean('notebook_unit_signed')->default(false);
$table->string('notebook_unit_name')->nullable();
$table->boolean('notebook_approved')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('career_applications', function (Blueprint $table) {
$table->dropColumn([
'notebook_supervisor_signed',
'notebook_supervisor_name',
'notebook_unit_signed',
'notebook_unit_name',
'notebook_approved'
]);
});
}
};
@@ -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('internship_journal_entries', function (Blueprint $table) {
$table->boolean('is_retroactive')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('internship_journal_entries', function (Blueprint $table) {
$table->dropColumn('is_retroactive');
});
}
};
@@ -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('internship_journal_entries', function (Blueprint $table) {
$table->string('supervisor_name')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('internship_journal_entries', function (Blueprint $table) {
$table->dropColumn('supervisor_name');
});
}
};
@@ -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 -1
View File
@@ -84,7 +84,7 @@ return [
'reviewed' => 'İncelendi', 'reviewed' => 'İncelendi',
'rejected' => 'Reddedildi', 'rejected' => 'Reddedildi',
'accepted' => 'Kabul Edildi', 'accepted' => 'Kabul Edildi',
'waiting_document' => 'Belge Bekleniyor', 'waiting_document' => 'İmzalı Staj Formu Bekleniyor',
// Wizard and Mermaid // Wizard and Mermaid
'step_1_title' => '1. Şartlar & Koşullar', 'step_1_title' => '1. Şartlar & Koşullar',
+6 -6
View File
@@ -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
+10 -10
View File
@@ -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
+2 -2
View File
@@ -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;
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 816 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

@@ -0,0 +1 @@
google-site-verification: googlef1M6GQLxVA5g7IaVY6kMQCoIrgADR6HjrOZu79CrXYc.html
+1 -31862
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -1,2 +1,6 @@
User-agent: * User-agent: *
Disallow: Allow: /
Disallow: /admin/
Disallow: /stajyer/admin/
Sitemap: https://truncgil.com/sitemap.xml
@@ -0,0 +1 @@
{{-- Bu bileşen görünümü devre dışı bırakılmıştır. --}}
@@ -0,0 +1,642 @@
@extends('layouts.site')
@section('content')
<section class="wrapper bg-[#f0f7ff] py-12">
<div class="container px-4">
<div class="max-w-7xl mx-auto">
<!-- Welcome Section -->
@include('front.career.partials.welcome')
<!-- Statistics Section -->
@include('front.career.partials.stats')
<!-- Tab Switcher Menu -->
<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>
<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')
</div>
</div>
</div>
</section>
@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
@@ -0,0 +1,87 @@
@extends('layouts.site')
@section('content')
<section class="wrapper bg-[#f0f7ff] min-h-[70vh] flex items-center py-12">
<div class="container px-4">
<div class="max-w-md mx-auto bg-white/80 backdrop-blur-md rounded-3xl shadow-2xl border border-slate-100/50 overflow-hidden">
<div class="p-8 md:p-10">
<div class="text-center mb-8">
<div class="inline-flex w-16 h-16 rounded-full bg-red-50 text-[#e31e24] items-center justify-center mb-4">
<i class="uil uil-shield text-3xl"></i>
</div>
<h1 class="text-3xl font-extrabold text-slate-900 tracking-tight">Yönetici Girişi</h1>
<p class="text-sm text-slate-500 mt-2">Staj takip sistemini yönetmek için e-posta ve şifrenizle giriş yapın.</p>
</div>
@if($errors->any())
<div class="bg-red-50 border-l-4 border-[#e31e24] p-4 rounded-xl mb-6">
<div class="flex">
<div class="flex-shrink-0">
<i class="uil uil-exclamation-triangle text-[#e31e24] text-xl"></i>
</div>
<div class="ml-3">
<p class="text-sm text-red-700 font-medium">
{{ $errors->first() }}
</p>
</div>
</div>
</div>
@endif
@if(session('success'))
<div class="bg-green-50 border-l-4 border-green-500 p-4 rounded-xl mb-6">
<div class="flex">
<div class="flex-shrink-0">
<i class="uil uil-check-circle text-green-500 text-xl"></i>
</div>
<div class="ml-3">
<p class="text-sm text-green-700 font-medium">
{{ session('success') }}
</p>
</div>
</div>
</div>
@endif
<form action="{{ route('intern.admin.login.submit') }}" method="POST" class="space-y-6">
@csrf
<div class="space-y-2">
<label for="email" class="text-sm font-bold text-slate-700 block">E-posta</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 text-slate-400">
<i class="uil uil-envelope"></i>
</span>
<input type="email" name="email" id="email" class="w-full pl-11 pr-5 py-3.5 rounded-xl border border-slate-200 focus:ring-4 focus:ring-red-500/10 focus:border-[#e31e24] outline-none transition-all placeholder-slate-300" placeholder="E-posta adresinizi girin" value="{{ old('email') }}" required>
</div>
</div>
<div class="space-y-2">
<label for="password" class="text-sm font-bold text-slate-700 block">Şifre</label>
<div class="relative">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 text-slate-400">
<i class="uil uil-key-skeleton-alt"></i>
</span>
<input type="password" name="password" id="password" class="w-full pl-11 pr-5 py-3.5 rounded-xl border border-slate-200 focus:ring-4 focus:ring-red-500/10 focus:border-[#e31e24] outline-none transition-all placeholder-slate-300" placeholder="Şifrenizi girin" required>
</div>
</div>
<button type="submit" class="w-full py-4 bg-[#e31e24] hover:bg-[#c4191f] text-white font-bold rounded-xl transition-all shadow-lg shadow-red-500/20 flex items-center justify-center gap-2 mt-4 hover:-translate-y-0.5">
<span>Giriş Yap</span>
<i class="uil uil-arrow-right text-xl"></i>
</button>
</form>
</div>
</div>
</div>
</section>
@push('styles')
<script src="https://cdn.tailwindcss.com"></script>
<style>
.shadow-2xl {
box-shadow: 0 1.5rem 4rem rgba(30, 41, 59, 0.08) !important;
}
</style>
@endpush
@endsection
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,338 @@
<!-- Gantt Chart Card -->
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50 mb-8">
<div class="flex items-center justify-between mb-6 pb-4 border-b border-slate-100">
<h3 class="font-bold text-slate-800 text-lg flex items-center gap-2">
<i class="uil uil-schedule text-blue-600 text-xl"></i>
<span>Stajyer Gantt Şeması ve Takvimi</span>
</h3>
<span class="text-xs px-2.5 py-1 bg-blue-50 text-blue-600 rounded-full font-semibold">DevExtreme Görünümü</span>
</div>
@if($ganttInterns->isEmpty())
<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-calendar-slash text-4xl text-slate-300 mb-2"></i>
<p class="text-sm font-medium text-slate-500">Tarihleri belirlenmiş onaylı stajyer bulunamadı.</p>
</div>
@else
<!-- Gantt Container -->
<div class="dx-viewport demo-container" style="height: 480px; overflow: hidden; border-radius: 16px; border: 1px solid rgba(0,0,0,0.05);">
<div id="gantt" style="height: 100%; width: 100%;"></div>
</div>
@endif
</div>
<!-- Dynamic Journal Modal -->
<div id="journal-modal" class="fixed inset-0 z-[9999] 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-2xl w-full mx-4 overflow-hidden transform scale-95 opacity-0 transition-all duration-300 flex flex-col max-h-[90vh]">
<!-- Modal 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">
<div>
<h4 id="modal-title" class="font-bold text-slate-800 text-lg">Staj Raporu Detayı</h4>
<p id="modal-subtitle" class="text-xs text-slate-500 font-semibold mt-0.5"></p>
</div>
<button type="button" onclick="closeJournalModal()" 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>
<!-- Modal Body -->
<div class="p-6 overflow-y-auto flex-grow prose max-w-none text-slate-700 leading-relaxed text-sm no-scrollbar">
<div id="modal-content" class="min-h-[100px] flex flex-col justify-center">
<!-- Content gets loaded here -->
</div>
</div>
<!-- Modal Footer -->
<div class="px-6 py-4 bg-slate-50/50 border-t border-slate-100 flex items-center justify-between">
<div id="modal-status-badge">
<!-- Status badge -->
</div>
<div class="flex gap-3">
<button type="button" onclick="closeJournalModal()" class="px-4 py-2 border border-slate-200 text-slate-600 hover:bg-slate-50 rounded-xl font-bold text-xs transition-colors">Kapat</button>
<button type="button" id="modal-action-btn" onclick="handleModalAction()" class="hidden 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"></button>
</div>
</div>
</div>
</div>
@push('styles')
<!-- DevExtreme Gantt CSS Dependencies -->
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx.fluent.blue.light.css">
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx-gantt.min.css">
<style>
.current-time-line {
background-color: rgba(239, 68, 68, 0.4) !important;
border-left: 2px dashed #ef4444 !important;
width: 2px !important;
}
.rich-text-content p { margin-bottom: 8px; }
.rich-text-content ul { list-style-type: disc !important; padding-left: 20px !important; margin-bottom: 8px !important; }
.rich-text-content ol { list-style-type: decimal !important; padding-left: 20px !important; margin-bottom: 8px !important; }
.rich-text-content li { margin-bottom: 4px !important; }
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
</style>
<script>
tailwind = {
config: {
corePlugins: {
preflight: false,
}
}
}
</script>
<script src="https://cdn.tailwindcss.com"></script>
@endpush
@push('scripts')
<!-- Gantt Chart Javascript Dependencies & Initialization -->
@if(!$ganttInterns->isEmpty())
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn3.devexpress.com/jslib/23.2.5/js/dx-gantt.min.js"></script>
<script src="https://cdn3.devexpress.com/jslib/23.2.5/js/dx.all.js"></script>
<script>
$(function() {
const ganttData = @json($ganttInterns);
const formattedData = ganttData.map(intern => ({
id: intern.id,
title: intern.title,
start: new Date(intern.start),
end: new Date(intern.end),
progress: 0
}));
let dayColumnsMap = {};
$('#gantt').dxGantt({
tasks: {
dataSource: formattedData,
},
editing: {
enabled: false,
},
validation: {
autoUpdateParentTasks: true,
},
toolbar: {
items: [
'collapseAll',
'expandAll',
'separator',
'zoomIn',
'zoomOut',
],
},
columns: [{
dataField: 'title',
caption: 'Stajyer Adı',
width: 200,
}, {
dataField: 'start',
caption: 'Başlangıç Tarihi',
dataType: 'date',
format: 'dd.MM.yyyy',
width: 110,
}, {
dataField: 'end',
caption: 'Bitiş Tarihi',
dataType: 'date',
format: 'dd.MM.yyyy',
width: 110,
}],
scaleType: 'days',
taskListWidth: 420,
stripLines: [{
title: 'Bugün',
start: new Date(),
cssClass: 'current-time-line'
}],
onScaleCellPrepared: function(e) {
if (e.scaleType === 'days') {
const $el = $(e.scaleElement || e.element);
const left = $el.position().left;
const width = $el.outerWidth();
const dateStr = e.startDate.toISOString().substring(0, 10);
dayColumnsMap[dateStr] = {
left: left,
right: left + width,
date: e.startDate,
dateStr: dateStr
};
}
}
});
// Grid task area cell click listener
$(document).on('click', '#gantt', function(e) {
const $target = $(e.target);
if ($target.closest('.dx-gantt-task-area').length > 0 || $target.closest('.dx-gantt-ts-area').length > 0) {
const ganttInstance = $('#gantt').dxGantt('instance');
const selectedKey = ganttInstance.option("selectedRowKey");
if (!selectedKey) {
alert("Lütfen önce sol taraftan stajyeri seçin, ardından tıklamak istediğiniz güne tıklayın.");
return;
}
const $taskArea = $('.dx-gantt-task-area').first();
if (!$taskArea.length) return;
const scrollLeft = ganttInstance._ganttView._taskAreaContainer.scrollLeft;
const clickX = e.pageX - $taskArea.offset().left;
const totalX = scrollLeft + clickX;
const cols = Object.values(dayColumnsMap);
const clickedCol = cols.find(col => totalX >= col.left && totalX <= col.right);
if (clickedCol) {
showDailyJournalPopup(selectedKey, clickedCol.dateStr);
}
}
});
});
let activeEntryId = null;
function showDailyJournalPopup(internId, dateStr) {
const modal = document.getElementById('journal-modal');
const contentDiv = document.getElementById('modal-content');
const titleH = document.getElementById('modal-title');
const subtitleP = document.getElementById('modal-subtitle');
const badgeDiv = document.getElementById('modal-status-badge');
const actionBtn = document.getElementById('modal-action-btn');
modal.classList.remove('hidden');
modal.classList.add('flex');
setTimeout(() => {
modal.firstElementChild.classList.remove('scale-95', 'opacity-0');
modal.firstElementChild.classList.add('scale-100', 'opacity-100');
}, 50);
contentDiv.innerHTML = `
<div class="flex items-center justify-center p-8">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
</div>
`;
titleH.textContent = "Yükleniyor...";
subtitleP.textContent = "";
badgeDiv.innerHTML = "";
actionBtn.classList.add('hidden');
activeEntryId = null;
fetch(`/stajyer/admin/journal-entry?intern_id=${internId}&date=${dateStr}`)
.then(res => res.json())
.then(data => {
if (!data.success) {
contentDiv.innerHTML = `<p class="text-slate-500 italic text-center p-8">${data.message}</p>`;
titleH.textContent = "Bilgi";
return;
}
titleH.textContent = `${data.intern_name}`;
subtitleP.textContent = `${data.day_number}. Gün Raporu — ${data.date_formatted}`;
if (!data.entry) {
contentDiv.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>
`;
return;
}
activeEntryId = data.entry.id;
const entryContent = data.entry.content || '<p class="text-slate-400 italic">Boş içerik.</p>';
let contentHtml = '';
if (data.entry.is_retroactive) {
contentHtml += `
<div class="mb-4 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 Kayıt
</div>
`;
}
contentHtml += `<div class="rich-text-content prose max-w-none text-slate-700 leading-relaxed text-sm select-text">${entryContent}</div>`;
contentDiv.innerHTML = contentHtml;
updateModalStatus(data.entry.supervisor_approved, data.entry.supervisor_name);
})
.catch(err => {
console.error(err);
contentDiv.innerHTML = `<p class="text-red-500 text-center p-8 font-semibold">Veriler yüklenirken bir hata oluştu.</p>`;
titleH.textContent = "Hata";
});
}
function updateModalStatus(approved, name) {
const badgeDiv = document.getElementById('modal-status-badge');
const actionBtn = document.getElementById('modal-action-btn');
actionBtn.classList.remove('hidden');
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 = "Raporu 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 handleModalAction() {
if (!activeEntryId) return;
const actionBtn = document.getElementById('modal-action-btn');
actionBtn.disabled = 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: activeEntryId
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
updateModalStatus(data.status, data.supervisor_name);
} else {
alert(data.message || "Onay güncellenemedi.");
}
})
.catch(err => {
console.error(err);
alert("İşlem sırasında bir hata oluştu.");
})
.finally(() => {
actionBtn.disabled = false;
actionBtn.style.opacity = "1";
});
}
function closeJournalModal() {
const modal = document.getElementById('journal-modal');
modal.firstElementChild.classList.add('scale-95', 'opacity-0');
modal.firstElementChild.classList.remove('scale-100', 'opacity-100');
setTimeout(() => {
modal.classList.add('hidden');
modal.classList.remove('flex');
}, 200);
}
</script>
@endif
@endpush
@@ -0,0 +1,132 @@
<!-- Stajyer Rehberi Modal -->
<div id="guideModal" class="fixed inset-0 z-[9999] hidden flex items-center justify-center p-4">
<!-- Backdrop -->
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-md transition-opacity duration-300" onclick="toggleGuideModal(false)"></div>
<!-- Modal Content Card -->
<div class="relative bg-white rounded-3xl w-full max-w-2xl max-h-[85vh] overflow-y-auto shadow-2xl border border-slate-100/50 transform scale-95 opacity-0 transition-all duration-300 flex flex-col" id="guideModalCard">
<!-- Modal Header -->
<div class="sticky top-0 bg-white z-10 px-6 py-5 border-b border-slate-100 flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-red-50 text-[#e31e24] flex items-center justify-center text-xl">
<i class="uil uil-book-open"></i>
</div>
<div>
<h3 class="font-bold text-slate-800 text-lg text-white">Stajyer Çalışma & Git Workflow Rehberi</h3>
<p class="text-xs text-slate-400 font-semibold mt-0.5">Başarılı bir staj süreci için uymanız gereken kurallar</p>
</div>
</div>
<button onclick="toggleGuideModal(false)" class="w-8 h-8 rounded-lg bg-slate-50 hover:bg-slate-100 text-slate-400 hover:text-slate-600 flex items-center justify-center transition-colors">
<i class="uil uil-times text-xl"></i>
</button>
</div>
<!-- Modal Body -->
<div class="p-6 md:p-8 space-y-6">
<!-- Bullet 1: GitHub Public Repo -->
<div class="flex gap-4 items-start">
<div class="w-8 h-8 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center flex-shrink-0 mt-1 font-bold text-xs">
1
</div>
<div>
<h4 class="font-extrabold text-slate-800 text-sm">GitHub Public Repo Oluşturma</h4>
<p class="text-xs text-slate-500 mt-1 leading-relaxed">
Staj süresince geliştireceğiniz projeler için GitHub üzerinde <strong>public (herkese açık)</strong> bir repository oluşturmanız ve bu repo bağlantısını stajyer panelinize kaydetmeniz gerekmektedir.
</p>
</div>
</div>
<!-- Bullet 2: Implementation Plan -->
<div class="flex gap-4 items-start">
<div class="w-8 h-8 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center flex-shrink-0 mt-1 font-bold text-xs">
2
</div>
<div>
<h4 class="font-extrabold text-slate-800 text-sm">İlk Adım: Implementation Plan</h4>
<p class="text-xs text-slate-500 mt-1 leading-relaxed">
Deponuzu oluşturduktan sonra ilk commit olarak ana dizinde <code>implementation_plan.md</code> adında bir dosya hazırlamalısınız. Bu plan, projenizin mimarisini, hedeflerinizi ve yapacağınız işlerin genel haritasını içermelidir.
</p>
</div>
</div>
<!-- Bullet 3: Daily Commits & Intern Journal -->
<div class="flex gap-4 items-start">
<div class="w-8 h-8 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center flex-shrink-0 mt-1 font-bold text-xs">
3
</div>
<div>
<h4 class="font-extrabold text-slate-800 text-sm">Günlük Commit Zorunluluğu (Staj Defteri)</h4>
<p class="text-xs text-slate-500 mt-1 leading-relaxed">
Projeyi geliştirirken yaptığınız günlük commitler otomatik olarak staj günlüğünüze işlenir ve staj defterinizin içeriğini oluşturur. <strong>Eğer o gün commit gönderilmemişse, o gün çalışılmamış sayılacaktır.</strong>
</p>
</div>
</div>
<!-- Bullet 4: Daily Standup Meetings -->
<div class="flex gap-4 items-start">
<div class="w-8 h-8 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center flex-shrink-0 mt-1 font-bold text-xs">
4
</div>
<div>
<h4 class="font-extrabold text-slate-800 text-sm">Daily Standup Toplantıları</h4>
<p class="text-xs text-slate-500 mt-1 leading-relaxed">
Her günü düzenli olarak daily standup (günlük durum değerlendirme) toplantısı yapılacaktır. Toplantılarda dünün özeti, bugünün planları ve karşılaşılan engeller paylaşılır. <strong>Tüm stajyerlerin katılımı zorunludur.</strong>
</p>
</div>
</div>
<!-- Bullet 5: Git Workflow & Commit Culture -->
<div class="flex gap-4 items-start">
<div class="w-8 h-8 rounded-full bg-blue-50 text-blue-600 flex items-center justify-center flex-shrink-0 mt-1 font-bold text-xs">
5
</div>
<div>
<h4 class="font-extrabold text-slate-800 text-sm">Git Workflow & Temiz Kod Kültürü</h4>
<p class="text-xs text-slate-500 mt-1 leading-relaxed">
Anlamlı commit mesajları yazmaya özen gösterin (örn. <code>feat: add login validation</code>). Kodlarınızı mantıksal parçalara ayırarak commit atın ve Git versiyon kontrol standartlarına uyun.
</p>
</div>
</div>
</div>
<!-- Modal Footer -->
<div class="sticky bottom-0 bg-slate-50 z-10 px-6 py-4 border-t border-slate-100/50 flex items-center justify-end gap-3 rounded-b-3xl">
<button onclick="toggleGuideModal(false)" class="px-5 py-2.5 bg-slate-850 hover:bg-slate-900 text-white font-extrabold rounded-xl text-xs transition-colors shadow-md shadow-slate-800/10">
Anladım, Kapat
</button>
</div>
</div>
</div>
<script>
function toggleGuideModal(show) {
const modal = document.getElementById('guideModal');
const card = document.getElementById('guideModalCard');
if (!modal || !card) return;
if (show) {
modal.classList.remove('hidden');
setTimeout(() => {
card.classList.remove('scale-95', 'opacity-0');
card.classList.add('scale-100', 'opacity-100');
}, 10);
} else {
card.classList.remove('scale-100', 'opacity-100');
card.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
}
}
// Handle ESC key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
toggleGuideModal(false);
}
});
</script>
@@ -0,0 +1,139 @@
<!-- Interns Table & Management List -->
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50">
<h3 class="font-bold text-slate-800 text-lg mb-6 pb-4 border-b border-slate-100 flex items-center gap-2">
<i class="uil uil-list-ul text-[#e31e24] text-xl"></i>
<span>Stajyer Listesi ve Detaylı Takip</span>
</h3>
<div class="overflow-x-auto">
<table class="w-full text-left border-collapse">
<thead>
<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">Staj Tarihleri</th>
<th class="pb-4">Defter İlerlemesi</th>
<th class="pb-4">Durum</th>
<th class="pb-4">Belgeler</th>
<th class="pb-4">GitHub & Repo</th>
<th class="pb-4">Kayıt Tarihi</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-100">
@foreach($allInterns as $intern)
<tr class="hover:bg-slate-50/50 transition-colors">
<!-- Name & Info -->
<td class="py-4">
<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">
<span><i class="uil uil-envelope mr-1"></i>{{ $intern->email }}</span>
@if($intern->phone)
<span><i class="uil uil-phone mr-1"></i>{{ $intern->phone }}</span>
@endif
</div>
</td>
<!-- Internship Dates -->
<td class="py-4 text-xs font-semibold text-slate-600">
@if($intern->internship_start_date && $intern->internship_end_date)
<div class="flex flex-col">
<span>{{ \Carbon\Carbon::parse($intern->internship_start_date)->format('d.m.Y') }} - {{ \Carbon\Carbon::parse($intern->internship_end_date)->format('d.m.Y') }}</span>
<span class="text-blue-500 font-extrabold mt-0.5">{{ $intern->internship_total_days }} İş Günü</span>
</div>
@else
<span class="text-slate-300 italic">Belirlenmemiş</span>
@endif
</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 -->
<td class="py-4">
<span class="inline-flex px-2.5 py-1 rounded-full text-[10px] font-extrabold uppercase tracking-wider
@if($intern->status === 'accepted') bg-green-50 text-green-700 border border-green-200
@elseif($intern->status === 'rejected') bg-red-50 text-red-700 border border-red-200
@elseif($intern->status === 'reviewed') bg-blue-50 text-blue-700 border border-blue-200
@elseif($intern->status === 'waiting_document') bg-amber-50 text-amber-700 border border-amber-200
@else bg-slate-50 text-slate-500 border border-slate-200 @endif">
@if($intern->status === 'accepted') Kabul Edildi
@elseif($intern->status === 'rejected') Reddedildi
@elseif($intern->status === 'reviewed') İncelendi
@elseif($intern->status === 'waiting_document') Evrak Bekleniyor
@else Beklemede @endif
</span>
</td>
<!-- Documents -->
<td class="py-4 space-y-1">
<div class="flex flex-wrap gap-1">
@if($intern->cv_path)
<a href="{{ Storage::disk('public')->url($intern->cv_path) }}" target="_blank" class="px-2 py-1 bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold text-[10px] rounded-lg transition-all flex items-center gap-1">
<i class="uil uil-file-alt"></i> CV
</a>
@endif
@if($intern->to_be_signed_internship_form_path)
<a href="{{ Storage::disk('public')->url($intern->to_be_signed_internship_form_path) }}" target="_blank" class="px-2 py-1 bg-blue-50 hover:bg-blue-100 text-blue-600 font-bold text-[10px] rounded-lg transition-all flex items-center gap-1">
<i class="uil uil-file-upload"></i> İmzalanacak Form
</a>
@endif
@if($intern->signed_internship_form_path)
<a href="{{ Storage::disk('public')->url($intern->signed_internship_form_path) }}" target="_blank" class="px-2 py-1 bg-green-50 hover:bg-green-100 text-green-600 font-bold text-[10px] rounded-lg transition-all flex items-center gap-1">
<i class="uil uil-file-check-alt"></i> İmzalı Form
</a>
@endif
</div>
</td>
<!-- GitHub Repository -->
<td class="py-4">
@if($intern->github_repo)
<div class="flex flex-col gap-1">
<a href="{{ $intern->github_repo }}" target="_blank" class="text-blue-600 hover:text-blue-800 font-extrabold text-xs inline-flex items-center gap-1">
<i class="uil uil-github"></i> Depoyu
</a>
<!-- If they have repo, we can fetch their journal commits or trigger the controller download markdown -->
<a href="{{ route('intern.download-journal') }}?intern_id={{ $intern->id }}" class="px-2 py-1 bg-emerald-50 hover:bg-emerald-100 text-emerald-600 font-bold text-[10px] rounded-lg transition-all flex items-center justify-center gap-1 w-max">
<i class="uil uil-arrow-down-tray"></i> Günlüğü İndir (.md)
</a>
</div>
@else
<span class="text-slate-300 italic">Tanımlanmamış</span>
@endif
</td>
<!-- Register Date -->
<td class="py-4 text-xs font-semibold text-slate-400">
{{ \Carbon\Carbon::parse($intern->created_at)->format('d.m.Y H:i') }}
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@@ -0,0 +1,42 @@
<!-- Stats Grid -->
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-white p-6 rounded-3xl border border-slate-100 shadow-lg flex items-center gap-4">
<div class="w-12 h-12 rounded-2xl bg-blue-50 text-blue-600 flex items-center justify-center text-2xl">
<i class="uil uil-users-alt"></i>
</div>
<div>
<span class="text-xs font-bold text-slate-400 block uppercase">Toplam Stajyer</span>
<span class="text-2xl font-extrabold text-slate-800">{{ $allInterns->count() }}</span>
</div>
</div>
<div class="bg-white p-6 rounded-3xl border border-slate-100 shadow-lg flex items-center gap-4">
<div class="w-12 h-12 rounded-2xl bg-emerald-50 text-emerald-600 flex items-center justify-center text-2xl">
<i class="uil uil-check-circle"></i>
</div>
<div>
<span class="text-xs font-bold text-slate-400 block uppercase">Kabul Edilen</span>
<span class="text-2xl font-extrabold text-slate-800">{{ $allInterns->where('status', 'accepted')->count() }}</span>
</div>
</div>
<div class="bg-white p-6 rounded-3xl border border-slate-100 shadow-lg flex items-center gap-4">
<div class="w-12 h-12 rounded-2xl bg-amber-50 text-amber-600 flex items-center justify-center text-2xl">
<i class="uil uil-hourglass-half"></i>
</div>
<div>
<span class="text-xs font-bold text-slate-400 block uppercase">Evrak Bekleyen</span>
<span class="text-2xl font-extrabold text-slate-800">{{ $allInterns->where('status', 'waiting_document')->count() }}</span>
</div>
</div>
<div class="bg-white p-6 rounded-3xl border border-slate-100 shadow-lg flex items-center gap-4">
<div class="w-12 h-12 rounded-2xl bg-indigo-50 text-indigo-600 flex items-center justify-center text-2xl">
<i class="uil uil-github-alt"></i>
</div>
<div>
<span class="text-xs font-bold text-slate-400 block uppercase">Git Tanımlayan</span>
<span class="text-2xl font-extrabold text-slate-800">{{ $allInterns->whereNotNull('github_repo')->count() }}</span>
</div>
</div>
</div>
@@ -0,0 +1,25 @@
<!-- Top header / Welcome card -->
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50 flex flex-col md:flex-row justify-between items-start md:items-center gap-6 mb-8">
<div>
<span class="text-xs uppercase font-extrabold tracking-widest text-[#e31e24] bg-red-50 px-3 py-1.5 rounded-full">Yönetici Paneli</span>
<h1 class="text-3xl font-extrabold text-slate-900 tracking-tight mt-3">Hoş Geldiniz, Yöneticimiz</h1>
<p class="text-sm text-slate-500 mt-1">Stajyer başvurularını, takvimlerini ve proje depolarını buradan takip edebilirsiniz.</p>
</div>
<div class="flex gap-3 flex-wrap">
<button onclick="toggleGuideModal(true)" class="px-5 py-3 rounded-xl bg-blue-50 hover:bg-blue-100 text-blue-600 font-extrabold text-sm transition-all flex items-center gap-2 border border-blue-100">
<i class="uil uil-book-open text-lg"></i>
<span>Stajyer Rehberi</span>
</button>
<a href="{{ route('homepage') }}" class="px-5 py-3 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold text-sm transition-all flex items-center gap-2 border border-slate-200/50">
<i class="uil uil-home text-lg"></i>
<span>Ana Sayfa</span>
</a>
<form action="{{ route('intern.admin.logout') }}" method="POST">
@csrf
<button type="submit" class="px-5 py-3 rounded-xl bg-red-50 hover:bg-red-100 text-[#e31e24] font-bold text-sm transition-all flex items-center gap-2 border border-red-100">
<i class="uil uil-sign-out-alt text-lg"></i>
<span>Çıkış Yap</span>
</button>
</form>
</div>
</div>
@@ -0,0 +1,442 @@
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Staj Defteri - {{ $intern->name }}</title>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Outfit', 'Segoe UI', sans-serif;
color: #1e293b;
background-color: #f8fafc;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
/* Page setup depending on chosen size */
@page {
@if($size === 'a5')
size: A5 portrait;
margin: 10mm;
@else
size: A4 portrait;
margin: 15mm;
@endif
}
.notebook-page {
background-color: #ffffff;
border: 1px solid #e2e8f0;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
page-break-after: always;
break-after: page;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
@if($size === 'a5')
width: 148mm;
height: 210mm;
padding: 12mm;
font-size: 11px;
@else
width: 210mm;
height: 297mm;
padding: 20mm;
font-size: 13px;
@endif
margin: 20px auto;
}
/* Adjustments for actual print mode */
@media print {
body {
background-color: #ffffff;
}
.notebook-page {
border: none;
box-shadow: none;
margin: 0;
width: 100%;
height: 100%;
}
.no-print {
display: none !important;
}
}
/* Top control bar */
.control-bar {
background: #ffffff;
border-bottom: 1px solid #e2e8f0;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 4px rgba(0,0,0,0.02);
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 18px;
border-radius: 10px;
font-size: 13px;
font-weight: 700;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
}
.btn-primary {
background-color: #2563eb;
color: #ffffff;
}
.btn-primary:hover {
background-color: #1d4ed8;
}
.btn-secondary {
background-color: #f1f5f9;
color: #475569;
border-color: #cbd5e1;
}
.btn-secondary:hover {
background-color: #e2e8f0;
}
.control-title {
font-weight: 800;
font-size: 16px;
color: #0f172a;
}
.control-actions {
display: flex;
gap: 10px;
}
/* Page Layout Styles */
.header-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 15px;
}
.header-table td {
border: 1px solid #cbd5e1;
padding: 8px 12px;
vertical-align: middle;
}
.logo-cell {
width: 150px;
text-align: center;
}
.logo-img {
max-height: 40px;
max-width: 100%;
}
.company-title {
font-weight: 800;
font-size: 12px;
color: #0f172a;
line-height: 1.3;
text-transform: uppercase;
}
.doc-title {
font-weight: 800;
color: #2563eb;
text-align: right;
font-size: 14px;
}
.meta-table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
.meta-table td {
border: 1px solid #cbd5e1;
padding: 6px 10px;
font-size: 11px;
}
.meta-label {
font-weight: 700;
color: #475569;
background-color: #f8fafc;
width: 25%;
}
.meta-value {
font-weight: 600;
color: #0f172a;
}
.day-banner {
background: linear-gradient(135deg, #eff6ff, #dbeafe);
border: 1px solid #bfdbfe;
border-radius: 8px;
padding: 10px 15px;
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.day-badge {
font-weight: 800;
color: #1d4ed8;
font-size: 14px;
}
.day-date {
font-weight: 700;
color: #475569;
font-size: 12px;
}
.content-area {
flex-grow: 1;
border: 1px solid #cbd5e1;
border-radius: 8px;
padding: 20px;
background: #ffffff;
margin-bottom: 20px;
min-height: 250px;
line-height: 1.6;
color: #334155;
font-size: 13px;
}
.content-area ul {
list-style-type: disc !important;
padding-left: 20px !important;
margin-top: 8px !important;
margin-bottom: 8px !important;
}
.content-area ol {
list-style-type: decimal !important;
padding-left: 20px !important;
margin-top: 8px !important;
margin-bottom: 8px !important;
}
.content-area li {
margin-bottom: 4px !important;
}
.empty-content {
color: #94a3b8;
font-style: italic;
text-align: center;
padding-top: 50px;
}
.footer-section {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: auto;
border-t: 1px solid #e2e8f0;
padding-top: 15px;
}
.qr-block {
display: flex;
align-items: center;
gap: 10px;
}
.qr-img {
width: 100px;
height: 100px;
border: 1px solid #e2e8f0;
padding: 2px;
border-radius: 4px;
}
.qr-text {
font-size: 9px;
color: #64748b;
max-width: 120px;
line-height: 1.3;
font-weight: 500;
}
.signatures-container {
display: flex;
gap: 20px;
}
.signature-box {
border: 1px solid #cbd5e1;
border-radius: 6px;
padding: 8px 12px;
width: 150px;
text-align: center;
font-size: 9px;
background: #f8fafc;
min-height: 70px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.sig-title {
font-weight: 700;
color: #475569;
border-bottom: 1px solid #e2e8f0;
padding-bottom: 4px;
margin-bottom: 4px;
text-transform: uppercase;
}
.sig-status {
font-weight: 800;
color: #059669;
margin: 6px 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.sig-status.waiting {
color: #d97706;
}
.sig-badge {
background-color: #d1fae5;
color: #065f46;
padding: 1px 6px;
border-radius: 4px;
font-size: 7px;
font-weight: 800;
display: inline-block;
margin-top: 2px;
}
.sig-badge.waiting {
background-color: #fef3c7;
color: #92400e;
}
.sig-name {
font-weight: 700;
color: #1e293b;
}
</style>
</head>
<body>
<!-- Control Bar -->
<div class="control-bar no-print">
<div class="control-title">Staj Defteri Önizleme ({{ $size === 'a5' ? 'A5 Boyutu' : 'A4 Boyutu' }})</div>
<div class="control-actions">
<a href="{{ route('intern.dashboard') }}" class="btn btn-secondary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
<span>Panele Dön</span>
</a>
<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') : '' }}{{ 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') : '' }}{{ 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">
<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>
</button>
</div>
</div>
<!-- Render Each Day -->
@foreach($days as $day)
@php
$entry = $savedEntries->get($day['day_number']);
$dayNum = $day['day_number'];
$dayDate = $day['date'];
$dayDateFormatted = $day['formatted_date'];
// Build the verification QR code URL pointing to the verification page
$verifyUrl = route('internship.verify', $intern->certificate_code) . '?day=' . $dayNum;
$qrCodeUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=' . urlencode($verifyUrl);
@endphp
<div class="notebook-page">
<div>
<!-- Header Card -->
<table class="header-table">
<tr>
<td class="logo-cell">
<img src="{{ asset('assets/img/truncgil-yatay.svg') }}" class="logo-img" alt="Logo">
</td>
<td>
<div class="company-title">TRUNÇGİL TEKNOLOJİ</div>
<div style="font-size: 8px; color: #64748b; margin-top: 2px;">Gaziantep Teknopark, Şahinbey / Gaziantep</div>
</td>
<td style="text-align: right;">
<div class="doc-title">STAJ GÜNLÜK RAPORU</div>
<div style="font-size: 8px; color: #64748b; margin-top: 2px;">Staj Defteri Sayfası</div>
</td>
</tr>
</table>
<!-- Student & Metadata Grid -->
<table class="meta-table">
<tr>
<td class="meta-label">Öğrenci Ad Soyad</td>
<td class="meta-value">{{ $intern->name }}</td>
<td class="meta-label">Staj Türü / Deposu</td>
<td class="meta-value">{{ $intern->github_repo ? 'GitHub Projeli Staj' : 'Genel Staj' }}</td>
</tr>
<tr>
<td class="meta-label">Staj Başlangıç</td>
<td class="meta-value">{{ \Carbon\Carbon::parse($intern->internship_start_date)->format('d.m.Y') }}</td>
<td class="meta-label">Toplam İş Günü</td>
<td class="meta-value">{{ $intern->internship_total_days }} Gün</td>
</tr>
</table>
<!-- Day Header Banner -->
<div class="day-banner">
<span class="day-badge">{{ $dayNum }}. Gün Raporu</span>
<span class="day-date"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block; vertical-align:middle; margin-right:4px;"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>{{ $dayDateFormatted }}</span>
</div>
<!-- Content Area -->
<div class="content-area">
@if($entry && trim($entry->content))
{!! $entry->content !!}
@else
<div class="empty-content">Bu gün için herhangi bir staj raporu girilmemiştir.</div>
@endif
</div>
</div>
<!-- Footer & Signatures Block -->
<div class="footer-section">
<!-- QR Code Block -->
<div class="qr-block">
<img src="{{ $qrCodeUrl }}" class="qr-img" alt="QR Code">
<div class="qr-text">
<strong>KOD DOĞRULAMA</strong><br>
Bu staj sayfası webden doğrulanabilir. Doğrulamak için QR kodu taratın.
</div>
</div>
<!-- Signatures -->
<div class="signatures-container">
<!-- Supervisor -->
<div class="signature-box">
<div class="sig-title">Staj Sorumlusu</div>
@if($entry && $entry->supervisor_approved)
<div class="sig-status">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color: #059669;"><polyline points="20 6 9 17 4 12"/></svg>
<span class="sig-badge">E-ONAYLI</span>
</div>
<div class="sig-name">{{ $entry->supervisor_name ?: ($intern->notebook_supervisor_name ?: 'Alperen Trunç') }}</div>
@else
<div class="sig-status waiting">
<span class="sig-badge waiting">İMZA BEKLENİYOR</span>
</div>
<div class="sig-name">&nbsp;</div>
@endif
</div>
</div>
</div>
</div>
@endforeach
</body>
</html>
+192 -54
View File
@@ -3,33 +3,28 @@
@section('content') @section('content')
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700;800;900&family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,500;0,700;1,400&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@500;700;800;900&family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,500;0,700;1,400&display=swap" rel="stylesheet">
@if($application->status === 'accepted' && $application->notebook_approved)
<div class="bg-slate-100 py-12 no-print"> <div class="bg-slate-100 py-12 no-print">
<div class="container max-w-6xl mx-auto px-4"> <div class="container max-w-6xl mx-auto px-4">
<!-- Verification Banner (e-Devlet Style) --> <!-- Verification Banner (e-Devlet Style) -->
<div class="bg-emerald-50 border-l-4 border-emerald-500 p-6 rounded-2xl shadow-md mb-8 flex flex-col md:flex-row items-center justify-between gap-6"> <div class="bg-emerald-50 border-l-4 border-emerald-500 p-4 rounded-xl shadow-sm mb-6 flex flex-col lg:flex-row lg:items-center justify-between gap-4">
<div class="flex items-center gap-4"> <div class="flex items-center gap-3 flex-1 min-w-0">
<div class="w-12 h-12 rounded-full bg-emerald-500 text-white flex items-center justify-center flex-shrink-0 shadow-lg shadow-emerald-500/20"> <div class="w-10 h-10 rounded-full bg-emerald-500 text-white flex items-center justify-center flex-shrink-0 shadow-md shadow-emerald-500/10">
<i class="uil uil-shield-check text-2xl"></i> <i class="uil uil-shield-check text-xl"></i>
</div> </div>
<div> <div class="text-sm leading-normal">
<h3 class="text-lg font-bold text-slate-800">Güvenli Belge Doğrulama</h3> <span class="font-bold text-slate-800">Güvenli Belge Doğrulama:</span>
<p class="text-sm text-slate-500 mt-1">Bu staj sertifikası ve transkripti, Trunçgil Teknopark sistemi üzerinden dijital olarak imzalanıp onaylanmıştır.</p> <span class="text-slate-600 ml-1">Bu staj sertifikası ve transkripti, Trunçgil Teknopark sistemi üzerinden dijital olarak imzalanıp onaylanmıştır.</span>
</div> </div>
</div> </div>
<div class="flex items-center gap-3 flex-wrap"> <div class="flex-shrink-0">
<!-- PDF Download Button --> <!-- PDF Download Button -->
<button onclick="downloadPDF()" id="pdf-btn" class="px-6 py-3 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-xl transition-all shadow-lg shadow-orange-500/25 flex items-center gap-2"> <button onclick="downloadPDF()" id="pdf-btn" class="px-4 py-2 bg-orange-600 hover:bg-orange-700 text-white text-sm font-bold rounded-lg transition-all shadow-sm flex items-center gap-1.5 whitespace-nowrap">
<i class="uil uil-file-download text-lg" id="pdf-icon"></i> <i class="uil uil-file-download text-base" id="pdf-icon"></i>
<span class="spinner-border spinner-border-sm hidden animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full" id="pdf-spinner" role="status"></span> <span class="spinner-border spinner-border-sm hidden animate-spin w-3.5 h-3.5 border-2 border-white border-t-transparent rounded-full" id="pdf-spinner" role="status"></span>
<span id="pdf-btn-text">PDF Olarak İndir</span>
</button>
<!-- Print Button -->
<button onclick="window.print()" class="px-6 py-3 bg-slate-900 hover:bg-slate-800 text-white font-bold rounded-xl transition-all shadow-md flex items-center gap-2">
<i class="uil uil-print text-lg"></i>
<span>Yazdır</span>
</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
@@ -47,8 +42,6 @@
</div> </div>
<!-- Thin Minimal Orange/Slate Inner Border --> <!-- Thin Minimal Orange/Slate Inner Border -->
<div class="absolute inset-6 border border-slate-200/50 rounded-[1.5rem] pointer-events-none z-10"></div>
<div class="absolute inset-[28px] border-2 border-orange-500/15 rounded-[1.3rem] pointer-events-none z-10"></div>
<div class="relative z-10 flex flex-col justify-between h-full min-h-[580px] md:min-h-[540px]"> <div class="relative z-10 flex flex-col justify-between h-full min-h-[580px] md:min-h-[540px]">
@@ -59,24 +52,23 @@
<img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Teknoloji" class="h-16 md:h-20 object-contain"> <img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Teknoloji" class="h-16 md:h-20 object-contain">
</div> </div>
<div class="w-1/3 text-right"> <div class="w-1/3 text-right">
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest block">Verifikasyon No</span> <span class="text-[10px] font-bold text-slate-400 tracking-widest block">VERİFİKASYON NO</span>
<span class="text-xs font-mono font-bold text-slate-800">{{ $application->certificate_code }}</span> <span class="text-xs font-mono font-bold text-slate-800">{{ $application->certificate_code }}</span>
</div> </div>
</div> </div>
<!-- Main Content --> <!-- Main Content -->
<div class="text-center my-auto px-4"> <div class="text-center my-auto px-4">
<h1 class="font-serif text-3xl md:text-5xl text-slate-900 uppercase mt-8 mb-2" style="font-weight: 900; letter-spacing: 0.04em;">Staj Bitirme Sertifikası</h1> <h1 class="font-cinzel text-3xl md:text-5xl text-slate-900 mt-8 mb-2" style="font-weight: 900; letter-spacing: 0.04em;">STAJ BİTİRME<br>SERTİFİKASI</h1>
<div class="w-24 h-[3px] bg-orange-500 mx-auto mb-8"></div>
<p class="text-slate-400 text-xs md:text-sm uppercase tracking-wider mb-2">Sayın</p> <p class="text-slate-400 text-xs md:text-sm tracking-wider mb-0">SAYIN</p>
<h2 class="font-serif text-2xl md:text-4xl font-extrabold text-slate-900 tracking-wide mb-6">{{ $application->name }}</h2> <h2 class="font-cinzel text-2xl md:text-4xl font-extrabold text-slate-900 tracking-wide mb-6 mt-0">{{ $application->name }}</h2>
<div class="max-w-3xl mx-auto text-slate-700 leading-relaxed text-sm md:text-base space-y-4"> <div class="max-w-3xl mx-auto text-slate-700 leading-relaxed text-sm md:text-base space-y-4">
<p> <p>
<strong>{{ \Carbon\Carbon::parse($application->internship_start_date)->format('d.m.Y') }}</strong> - <strong>{{ \Carbon\Carbon::parse($application->internship_start_date)->format('d.m.Y') }}</strong> -
<strong>{{ \Carbon\Carbon::parse($application->internship_end_date)->format('d.m.Y') }}</strong> tarihleri arasında, <strong>{{ \Carbon\Carbon::parse($application->internship_end_date)->format('d.m.Y') }}</strong> tarihleri arasında,
<strong>Trunçgil Teknoloji</strong> bünyesinde gerçekleştirdiği uzaktan staj programını başarıyla tamamlayarak bu belgeyi almaya hak kazanmıştır. <strong>Trunçgil Teknoloji</strong> bünyesinde gerçekleştirdiği staj programını başarıyla tamamlayarak bu belgeyi almaya hak kazanmıştır.
</p> </p>
<p class="text-xs md:text-sm text-slate-500 max-w-2xl mx-auto italic font-serif"> <p class="text-xs md:text-sm text-slate-500 max-w-2xl mx-auto italic font-serif">
Gösterdiği üstün gayret, sorumluluk bilinci, teknik beceriler ve ekip çalışmasına sağladığı değerli katkılardan dolayı teşekkür eder, profesyonel kariyerinde başarılar dileriz. Gösterdiği üstün gayret, sorumluluk bilinci, teknik beceriler ve ekip çalışmasına sağladığı değerli katkılardan dolayı teşekkür eder, profesyonel kariyerinde başarılar dileriz.
@@ -90,28 +82,18 @@
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data={{ urlencode(route('internship.verify', $application->certificate_code)) }}" alt="QR Code" class="w-28 h-28 border border-slate-200 p-1 rounded-xl bg-white shadow-sm"> <img src="https://api.qrserver.com/v1/create-qr-code/?size=150x150&data={{ urlencode(route('internship.verify', $application->certificate_code)) }}" alt="QR Code" class="w-28 h-28 border border-slate-200 p-1 rounded-xl bg-white shadow-sm">
<div class="text-left hidden sm:block"> <div class="text-left hidden sm:block">
<span class="text-[8px] font-bold text-slate-400 uppercase tracking-wider block">Dijital Doğrulama</span> <span class="text-[8px] font-bold text-slate-400 tracking-wider block">DİJİTAL DOĞRULAMA</span>
<span class="text-[9px] text-slate-500 block">Karekod okutularak belgenin güncelliği e-Devlet stilinde doğrulanabilir.</span> <span class="text-[9px] text-slate-500 block">Karekod okutularak belgenin güncelliği doğrulanabilir.</span>
</div> </div>
</div> </div>
<!-- Middle: Date --> <!-- Middle: Date -->
<div class="text-center"> <div class="text-center">
<span class="text-[9px] text-slate-400 block uppercase tracking-wider">Düzenlenme Tarihi</span> <span class="text-[9px] text-slate-600 block tracking-wider text-glow-white font-semibold">DÜZENLENME TARİHİ</span>
<span class="text-xs font-bold text-slate-700">{{ \Carbon\Carbon::parse($application->internship_end_date)->format('d.m.Y') }}</span> <span class="text-xs font-bold text-slate-800 text-glow-white">{{ \Carbon\Carbon::parse($application->internship_end_date)->format('d.m.Y') }}</span>
</div> </div>
<!-- Right: Authorized Signature -->
<div class="text-right">
<div class="inline-block text-center pr-2">
<!-- Digital Signature Stamp Concept -->
<div class="h-10 flex items-center justify-center mb-1">
<span class="font-sans text-slate-800 font-bold text-sm tracking-wider">Trunçgil Teknoloji</span>
</div>
<span class="font-bold text-slate-800 block text-xs">Onay Makamı</span>
<span class="text-[9px] text-slate-400 block">Staj Koordinatörlüğü</span>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -129,15 +111,15 @@
<!-- Header --> <!-- Header -->
<div class="flex justify-between items-center pb-2 border-b border-slate-100 mb-3"> <div class="flex justify-between items-center pb-2 border-b border-slate-100 mb-3">
<div> <div>
<h2 class="font-serif font-bold text-slate-900 uppercase transcript-header-title">Staj Akademik Transkripti</h2> <h2 class="font-serif font-bold text-slate-900 transcript-header-title">STAJ AKADEMİK TRANSKRİPTİ</h2>
<p class="text-slate-400 tracking-wider uppercase mt-0.5 transcript-header-subtitle">Ve Performans Değerlendirme Raporu</p> <p class="text-slate-400 tracking-wider mt-0.5 transcript-header-subtitle">VE PERFORMANS DEĞERLENDİRME RAPORU</p>
</div> </div>
<img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Logo" class="h-6 object-contain"> <img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Logo" class="h-6 object-contain">
</div> </div>
<!-- Student Info block --> <!-- Student Info block -->
<div class="bg-slate-50 rounded-xl p-2.5 border border-slate-100 mb-3"> <div class="bg-slate-50 rounded-xl p-2.5 border border-slate-100 mb-3">
<h3 class="font-bold text-slate-400 uppercase tracking-widest mb-1.5 student-info-title">Öğrenci / Stajyer Bilgileri</h3> <h3 class="font-bold text-slate-400 tracking-widest mb-1.5 student-info-title">ÖĞRENCİ / STAJYER BİLGİLERİ</h3>
<div class="grid grid-cols-1 md:grid-cols-2 gap-y-1 gap-x-8 student-info-grid"> <div class="grid grid-cols-1 md:grid-cols-2 gap-y-1 gap-x-8 student-info-grid">
<div class="flex justify-between border-b border-slate-200/50 pb-1"> <div class="flex justify-between border-b border-slate-200/50 pb-1">
<span class="text-slate-500">Adı Soyadı:</span> <span class="text-slate-500">Adı Soyadı:</span>
@@ -169,7 +151,7 @@
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data={{ urlencode(route('internship.verify', $application->certificate_code)) }}" alt="QR Code" class="w-14 h-14 border border-slate-200 p-0.5 rounded-lg bg-white"> <img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data={{ urlencode(route('internship.verify', $application->certificate_code)) }}" alt="QR Code" class="w-14 h-14 border border-slate-200 p-0.5 rounded-lg bg-white">
<div> <div>
<span class="font-bold text-slate-400 uppercase tracking-wider block verify-code-label">Güvenli Doğrulama Kodu</span> <span class="font-bold text-slate-400 tracking-wider block verify-code-label">GÜVENLİ DOĞRULAMA KODU</span>
<span class="font-mono font-bold text-slate-800 block verify-code">{{ $application->certificate_code }}</span> <span class="font-mono font-bold text-slate-800 block verify-code">{{ $application->certificate_code }}</span>
</div> </div>
</div> </div>
@@ -182,8 +164,97 @@
</div> </div>
</div> </div>
<!-- ================= PAGE 3: STAJ DEFTERİ (PORTRAIT A4) ================= -->
@if($application->notebook_approved || $application->notebook_supervisor_signed)
<div class="notebook-container-wrapper overflow-hidden w-full flex justify-center items-start mt-8">
<div id="notebook-node" class="transcript-page bg-white rounded-[2rem] shadow-2xl p-6 md:p-8 relative overflow-hidden mx-auto">
<!-- Thin border decor for notebook -->
<div class="absolute inset-4 border border-slate-100 rounded-[1.5rem] pointer-events-none"></div>
<div class="relative z-10 flex flex-col justify-between h-full text-slate-700">
<div>
<!-- Header -->
<div class="flex justify-between items-center pb-2 border-b border-slate-100 mb-3">
<div>
<h2 class="font-serif font-bold text-slate-900 transcript-header-title">ONAYLI STAJ DEFTERİ RAPORU</h2>
<p class="text-slate-400 tracking-wider mt-0.5 transcript-header-subtitle">DİJİTAL İMZALI VE ONAYLI STAJ RAPORLARI</p>
</div>
<img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Logo" class="h-6 object-contain">
</div>
<!-- Signatures Information -->
<div class="bg-emerald-50 border border-emerald-200 rounded-xl p-3 mb-4 flex justify-between items-center">
<div>
<span class="text-xs font-extrabold text-emerald-800 uppercase block tracking-wider">Dijital İmza Durumu</span>
<span class="text-[10px] text-emerald-600 font-semibold block mt-0.5">Bu staj defteri yetkililer tarafından elektronik olarak imzalanmıştır.</span>
</div>
<div class="flex gap-2">
@if($application->notebook_supervisor_signed)
<span class="inline-flex px-2 py-1 rounded bg-emerald-100 text-emerald-800 text-[8px] font-extrabold uppercase border border-emerald-200">
Sorumlu: {{ $application->notebook_supervisor_name ?: 'Alperen Trunç' }}
</span>
@endif
</div>
</div>
<!-- Notebook Days content overview -->
<div class="overflow-y-auto max-h-[700px] pr-2 no-scrollbar space-y-4">
@foreach($days as $day)
@php
$entry = $savedEntries->get($day['day_number']);
@endphp
@if($entry && trim($entry->content))
<div class="p-3 bg-slate-50 border border-slate-100 rounded-xl">
<div class="flex justify-between items-center pb-1.5 border-b border-slate-200/50 mb-1.5">
<span class="text-xs font-extrabold text-slate-800">
{{ $day['day_number'] }}. Gün Raporu
@if($entry->supervisor_approved)
<span class="ml-2 inline-flex px-1.5 py-0.5 rounded bg-emerald-100 text-emerald-800 text-[8px] font-extrabold uppercase border border-emerald-200">Sorumlu Onaylı{{ $entry->supervisor_name ? ' (' . $entry->supervisor_name . ')' : '' }}</span>
@endif
</span>
<span class="text-[10px] text-slate-400 font-bold">{{ $day['formatted_date'] }}</span>
</div>
<div class="text-xs text-slate-600 leading-relaxed verify-log-content" style="font-size: 11px;">{!! $entry->content !!}</div>
</div>
@endif
@endforeach
</div>
</div>
<!-- Footer -->
<div class="w-full flex justify-between items-center pt-2 border-t border-slate-100 gap-4 mt-auto">
<div class="flex items-center gap-3">
<img src="https://api.qrserver.com/v1/create-qr-code/?size=120x120&data={{ urlencode(route('internship.verify', $application->certificate_code)) }}" alt="QR Code" class="w-14 h-14 border border-slate-200 p-0.5 rounded-lg bg-white">
<div>
<span class="font-bold text-slate-400 tracking-wider block verify-code-label">GÜVENLİ DOĞRULAMA KODU</span>
<span class="font-mono font-bold text-slate-800 block verify-code">{{ $application->certificate_code }}</span>
</div>
</div>
<div class="text-right text-slate-400 footer-address">
Trunçgil Teknoloji San. ve Tic. Ltd. Şti.<br>
Gaziantep Üniversitesi Teknopark No: 4A
</div>
</div>
</div>
</div>
</div>
@endif
</div> </div>
</div> </div>
@else
<div class="min-h-screen bg-slate-100 flex items-center justify-center p-6 no-print">
<div class="bg-white rounded-3xl p-8 max-w-md w-full shadow-2xl border border-slate-100 text-center">
<div class="w-16 h-16 rounded-full bg-red-50 text-[#e31e24] flex items-center justify-center text-3xl mx-auto mb-4">
<i class="uil uil-exclamation-triangle"></i>
</div>
<h1 class="text-xl font-bold text-slate-800">Doğrulanamayan Belge</h1>
<p class="text-xs text-slate-500 mt-2 leading-relaxed">Görüntülemeye çalıştığınız staj kaydı henüz onaylanmamıştır veya aktif değildir. Detaylı bilgi için kurum yetkilileri ile iletişime geçebilirsiniz.</p>
<a href="/" class="mt-6 inline-flex px-5 py-2.5 bg-slate-800 hover:bg-slate-900 text-white font-bold rounded-xl text-xs transition-all shadow-md">Anasayfaya Dön</a>
</div>
</div>
@endif
@push('styles') @push('styles')
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
@@ -194,6 +265,21 @@
/* Global scale factor for transcript page fonts. Adjust this easily (e.g. 1.0, 1.10, 1.20) */ /* Global scale factor for transcript page fonts. Adjust this easily (e.g. 1.0, 1.10, 1.20) */
--transcript-font-scale: 1.30; --transcript-font-scale: 1.30;
} }
.verify-log-content ul {
list-style-type: disc !important;
padding-left: 18px !important;
margin-top: 5px !important;
margin-bottom: 5px !important;
}
.verify-log-content ol {
list-style-type: decimal !important;
padding-left: 18px !important;
margin-top: 5px !important;
margin-bottom: 5px !important;
}
.verify-log-content li {
margin-bottom: 3px !important;
}
body { body {
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
@@ -201,6 +287,13 @@
.font-serif { .font-serif {
font-family: 'Cinzel', 'Playfair Display', Georgia, serif; font-family: 'Cinzel', 'Playfair Display', Georgia, serif;
} }
.font-cinzel {
font-family: 'Cinzel', serif !important;
}
.text-glow-white {
text-shadow: 0 0 10px #ffffff, 0 0 6px #ffffff, 0 0 3px #ffffff;
}
/* Transcript typography helpers scaled globally */ /* Transcript typography helpers scaled globally */
.transcript-header-title { .transcript-header-title {
@@ -375,6 +468,7 @@
padding: 15mm !important; padding: 15mm !important;
box-shadow: none !important; box-shadow: none !important;
border: none !important; border: none !important;
border-radius: 0 !important;
box-sizing: border-box !important; box-sizing: border-box !important;
page-break-after: always !important; page-break-after: always !important;
break-after: page !important; break-after: page !important;
@@ -397,6 +491,7 @@
padding: 10mm !important; padding: 10mm !important;
box-shadow: none !important; box-shadow: none !important;
border: none !important; border: none !important;
border-radius: 0 !important;
box-sizing: border-box !important; box-sizing: border-box !important;
overflow: hidden !important; overflow: hidden !important;
} }
@@ -435,6 +530,7 @@
function scaleAllDocuments() { function scaleAllDocuments() {
scaleDocument('.certificate-container-wrapper', '#certificate-node', 1122, 794); scaleDocument('.certificate-container-wrapper', '#certificate-node', 1122, 794);
scaleDocument('.transcript-container-wrapper', '#transcript-node', 794, 1122); scaleDocument('.transcript-container-wrapper', '#transcript-node', 794, 1122);
scaleDocument('.notebook-container-wrapper', '#notebook-node', 794, 1122);
} }
window.addEventListener('resize', scaleAllDocuments); window.addEventListener('resize', scaleAllDocuments);
@@ -464,22 +560,40 @@
const certEl = document.getElementById('certificate-node'); const certEl = document.getElementById('certificate-node');
const transEl = document.getElementById('transcript-node'); const transEl = document.getElementById('transcript-node');
const notebookEl = document.getElementById('notebook-node');
// Force elements to normal scale prior to capturing for PDF // Force elements to normal scale prior to capturing for PDF
const origCertTransform = certEl.style.transform; const origCertTransform = certEl.style.transform;
const origCertWidth = certEl.style.width; const origCertWidth = certEl.style.width;
const origCertHeight = certEl.style.height; const origCertHeight = certEl.style.height;
const origCertRadius = certEl.style.borderRadius;
const origTransTransform = transEl.style.transform; const origTransTransform = transEl.style.transform;
const origTransWidth = transEl.style.width; const origTransWidth = transEl.style.width;
const origTransHeight = transEl.style.height; const origTransHeight = transEl.style.height;
const origTransRadius = transEl.style.borderRadius;
let origNotebookTransform, origNotebookWidth, origNotebookHeight, origNotebookRadius;
if (notebookEl) {
origNotebookTransform = notebookEl.style.transform;
origNotebookWidth = notebookEl.style.width;
origNotebookHeight = notebookEl.style.height;
origNotebookRadius = notebookEl.style.borderRadius;
notebookEl.style.transform = 'none';
notebookEl.style.width = '794px';
notebookEl.style.height = '1122px';
notebookEl.style.borderRadius = '0';
}
certEl.style.transform = 'none'; certEl.style.transform = 'none';
certEl.style.width = '1122px'; certEl.style.width = '1122px';
certEl.style.height = '794px'; certEl.style.height = '794px';
certEl.style.borderRadius = '0';
transEl.style.transform = 'none'; transEl.style.transform = 'none';
transEl.style.width = '794px'; transEl.style.width = '794px';
transEl.style.height = '1122px'; transEl.style.height = '1122px';
transEl.style.borderRadius = '0';
// Render Page 1 (Landscape) // Render Page 1 (Landscape)
const certCanvas = await html2canvas(certEl, { const certCanvas = await html2canvas(certEl, {
scale: 2, scale: 2,
@@ -488,7 +602,7 @@
logging: false logging: false
}); });
const certImgData = certCanvas.toDataURL('image/jpeg', 1.0); const certImgData = certCanvas.toDataURL('image/jpeg', 1.0);
// Initialize landscape A4 document // Initialize landscape A4 document
const pdf = new jsPDF({ const pdf = new jsPDF({
orientation: 'landscape', orientation: 'landscape',
@@ -496,7 +610,7 @@
format: 'a4' format: 'a4'
}); });
pdf.addImage(certImgData, 'JPEG', 0, 0, 297, 210); pdf.addImage(certImgData, 'JPEG', 0, 0, 297, 210);
// Render Page 2 (Portrait) // Render Page 2 (Portrait)
const transCanvas = await html2canvas(transEl, { const transCanvas = await html2canvas(transEl, {
scale: 2, scale: 2,
@@ -505,23 +619,47 @@
logging: false logging: false
}); });
const transImgData = transCanvas.toDataURL('image/jpeg', 1.0); const transImgData = transCanvas.toDataURL('image/jpeg', 1.0);
// Calculate exact height using aspect ratio to prevent vertical squishing // Calculate exact height using aspect ratio to prevent vertical squishing
const transWidth = 210; const transWidth = 210;
const transHeight = (transCanvas.height * transWidth) / transCanvas.width; const transHeight = (transCanvas.height * transWidth) / transCanvas.width;
// Add portrait page to PDF // Add portrait page to PDF
pdf.addPage('a4', 'portrait'); pdf.addPage('a4', 'portrait');
pdf.addImage(transImgData, 'JPEG', 0, 0, transWidth, transHeight); pdf.addImage(transImgData, 'JPEG', 0, 0, transWidth, transHeight);
// Render Page 3 (Notebook) if it exists
if (notebookEl) {
const notebookCanvas = await html2canvas(notebookEl, {
scale: 2,
useCORS: true,
backgroundColor: '#ffffff',
logging: false
});
const notebookImgData = notebookCanvas.toDataURL('image/jpeg', 1.0);
const notebookHeight = (notebookCanvas.height * transWidth) / notebookCanvas.width;
pdf.addPage('a4', 'portrait');
pdf.addImage(notebookImgData, 'JPEG', 0, 0, transWidth, notebookHeight);
}
// Restore styles // Restore styles
certEl.style.transform = origCertTransform; certEl.style.transform = origCertTransform;
certEl.style.width = origCertWidth; certEl.style.width = origCertWidth;
certEl.style.height = origCertHeight; certEl.style.height = origCertHeight;
certEl.style.borderRadius = origCertRadius;
transEl.style.transform = origTransTransform; transEl.style.transform = origTransTransform;
transEl.style.width = origTransWidth; transEl.style.width = origTransWidth;
transEl.style.height = origTransHeight; transEl.style.height = origTransHeight;
transEl.style.borderRadius = origTransRadius;
if (notebookEl) {
notebookEl.style.transform = origNotebookTransform;
notebookEl.style.width = origNotebookWidth;
notebookEl.style.height = origNotebookHeight;
notebookEl.style.borderRadius = origNotebookRadius;
}
// Trigger Save File // Trigger Save File
pdf.save('{{ str()->slug($application->name) }}-staj-belgesi.pdf'); pdf.save('{{ str()->slug($application->name) }}-staj-belgesi.pdf');
+2 -1
View File
@@ -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) {
+33 -3
View File
@@ -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'))
+7 -1
View File
@@ -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>
@@ -78,4 +78,27 @@
left: 50%; left: 50%;
transform: translateX(-50%) !important; transform: translateX(-50%) !important;
} }
@media (max-width: 767.98px) {
.image-wrapper {
background-image: none !important;
position: relative !important;
}
.image-wrapper::before {
content: "" !important;
position: absolute !important;
top: 0 !important;
left: 0 !important;
width: 100% !important;
height: 100vh !important;
background-image: linear-gradient(rgba(30, 34, 40, 0.45), rgba(30, 34, 40, 0.45)), url('{{ $hero_bg }}') !important;
background-size: cover !important;
background-position: center center !important;
background-repeat: no-repeat !important;
z-index: -1 !important;
}
.image-wrapper .divider {
display: none !important;
}
}
</style> </style>
@@ -1,55 +1,51 @@
<section class="wrapper !bg-[#ffffff]"> <section class="wrapper !bg-[#ffffff]">
<div class="container"> <div class="container">
<div class="flex flex-wrap mx-[-15px] xl:mx-[-12.5px] lg:mx-[-12.5px] md:mx-[-12.5px] !mb-[4.5rem] !mt-[-9rem] xl:!mb-[7rem] lg:!mb-[7rem] md:!mb-[7rem]"> <div class="flex flex-wrap glass-card-row !mb-[4.5rem] !mt-[-9rem] xl:!mb-[7rem] lg:!mb-[7rem] md:!mb-[7rem]">
<div class="md:w-6/12 lg:w-6/12 xl:w-3/12 w-full flex-[0_0_auto] max-w-full !px-[15px] xl:!px-[12.5px] lg:!px-[12.5px] md:!px-[12.5px] !mt-[25px]"> <div class="w-full md:w-6/12 lg:w-6/12 xl:w-3/12 flex-[0_0_auto] max-w-full glass-card-col">
<div class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col"> <a href="{{ route('page.show', ['slug' => 'yapay-zeka']) }}" class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col">
<div class="card-body flex-[1_1_auto] p-[40px] flex flex-col"> <div class="card-body glass-card-body flex-[1_1_auto] flex flex-col">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="svg-inject icon-svg !w-[2.2rem] !h-[2.2rem] solid-mono text-[#e668b3] text-fuchsia !mb-3"><path class="fill-primary" d="M152 0H29.33A29.32 29.32 0 000 29.33v165.33A29.32 29.32 0 0029.33 224h78.72l2.35-13.12a29.71 29.71 0 018.11-15.68l62.83-62.72V29.33A29.33 29.33 0 00152 0zM42.67 42.67h42.67a10.67 10.67 0 110 21.33H42.66a10.67 10.67 0 010-21.33zM96 149.33H42.67a10.67 10.67 0 110-21.33H96a10.67 10.67 0 110 21.33zm42.67-42.66h-96a10.67 10.67 0 010-21.34h96a10.67 10.67 0 110 21.34z"></path><path class="fill-secondary" d="M133.63 256a8 8 0 01-7.89-9.38l5.67-32.06a8 8 0 012.22-4.27l79.2-79.2c9.73-9.75 19.28-7.12 24.51-1.89l13.2 13.2a18.69 18.69 0 010 26.4l-79.2 79.2a7.83 7.83 0 01-4.27 2.22l-32 5.67a10.71 10.71 0 01-1.44.11zm32.05-13.65z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" class="svg-inject icon-svg !w-[1.8rem] !h-[1.8rem] md:!w-[2.2rem] md:!h-[2.2rem] solid-mono text-[#e668b3] text-fuchsia !mb-2 md:!mb-3"><path class="fill-primary" d="M152 0H29.33A29.32 29.32 0 000 29.33v165.33A29.32 29.32 0 0029.33 224h78.72l2.35-13.12a29.71 29.71 0 018.11-15.68l62.83-62.72V29.33A29.33 29.33 0 00152 0zM42.67 42.67h42.67a10.67 10.67 0 110 21.33H42.66a10.67 10.67 0 010-21.33zM96 149.33H42.67a10.67 10.67 0 110-21.33H96a10.67 10.67 0 110 21.33zm42.67-42.66h-96a10.67 10.67 0 010-21.34h96a10.67 10.67 0 110 21.34z"></path><path class="fill-secondary" d="M133.63 256a8 8 0 01-7.89-9.38l5.67-32.06a8 8 0 012.22-4.27l79.2-79.2c9.73-9.75 19.28-7.12 24.51-1.89l13.2 13.2a18.69 18.69 0 010 26.4l-79.2 79.2a7.83 7.83 0 01-4.27 2.22l-32 5.67a10.71 10.71 0 01-1.44.11zm32.05-13.65z"></path></svg>
<h4>{!! t('Yapay Zeka') !!}</h4> <h4 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Yapay Zeka') !!}</h4>
<p class="!mb-2 flex-1 min-h-[4.5rem]">{!! t('Yapay zeka teknolojilerini kullanarak işlerinizi otomatikleştirebiliriz.') !!}</p> <p class="!mb-0 flex-1 min-h-[4rem] md:min-h-[4.5rem] !text-[11px] md:!text-[14px] !leading-snug md:!leading-relaxed">{!! t('Yapay zeka teknolojilerini kullanarak işlerinizi otomatikleştirebiliriz.') !!}</p>
<a href="{{ route('page.show', ['slug' => 'yapay-zeka']) }}" class="more hover !text-[#e668b3] focus:!text-[#e668b3] hover:!text-[#e668b3] !mt-auto">{!! t('Daha fazla bilgi edin') !!}</a>
</div> </div>
<!--/.card-body --> <!--/.card-body -->
</div> </a>
<!--/.card --> <!--/.card -->
</div> </div>
<!--/column --> <!--/column -->
<div class="md:w-6/12 lg:w-6/12 xl:w-3/12 w-full flex-[0_0_auto] max-w-full !px-[15px] xl:!px-[12.5px] lg:!px-[12.5px] md:!px-[12.5px] !mt-[25px]"> <div class="w-full md:w-6/12 lg:w-6/12 xl:w-3/12 flex-[0_0_auto] max-w-full glass-card-col">
<div class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col"> <a href="{{ route('page.show', ['slug' => 'web-uygulamalari']) }}" class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col">
<div class="card-body flex-[1_1_auto] p-[40px] flex flex-col"> <div class="card-body glass-card-body flex-[1_1_auto] flex flex-col">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-inject icon-svg !w-[2.2rem] !h-[2.2rem] solid-mono text-[#a07cc5] text-violet !mb-3"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-inject icon-svg !w-[1.8rem] !h-[1.8rem] md:!w-[2.2rem] md:!h-[2.2rem] solid-mono text-[#a07cc5] text-violet !mb-2 md:!mb-3"><rect x="2" y="3" width="20" height="14" rx="2" ry="2"></rect><line x1="8" y1="21" x2="16" y2="21"></line><line x1="12" y1="17" x2="12" y2="21"></line></svg>
<h4>{!! t('Web Uygulamaları') !!}</h4> <h4 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Web Uygulamaları') !!}</h4>
<p class="!mb-2 flex-1 min-h-[4.5rem]">{!! t('Web uygulamaları geliştirmek için gerekli olan tüm hizmetleri sunuyoruz.') !!}</p> <p class="!mb-0 flex-1 min-h-[4rem] md:min-h-[4.5rem] !text-[11px] md:!text-[14px] !leading-snug md:!leading-relaxed">{!! t('Web uygulamaları geliştirmek için gerekli olan tüm hizmetleri sunuyoruz.') !!}</p>
<a href="{{ route('page.show', ['slug' => 'web-uygulamalari']) }}" class="more hover !text-[#a07cc5] focus:!text-[#a07cc5] hover:!text-[#a07cc5] !mt-auto">{!! t('Daha fazla bilgi edin') !!}</a>
</div> </div>
<!--/.card-body --> <!--/.card-body -->
</div> </a>
<!--/.card --> <!--/.card -->
</div> </div>
<!--/column --> <!--/column -->
<div class="md:w-6/12 lg:w-6/12 xl:w-3/12 w-full flex-[0_0_auto] max-w-full !px-[15px] xl:!px-[12.5px] lg:!px-[12.5px] md:!px-[12.5px] !mt-[25px]"> <div class="w-full md:w-6/12 lg:w-6/12 xl:w-3/12 flex-[0_0_auto] max-w-full glass-card-col">
<div class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col"> <a href="{{ route('music-productions.index') }}" class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col">
<div class="card-body flex-[1_1_auto] p-[40px] flex flex-col"> <div class="card-body glass-card-body flex-[1_1_auto] flex flex-col">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="svg-inject icon-svg !w-[2.2rem] !h-[2.2rem] solid-mono text-[#f78b77] text-orange !mb-3"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="svg-inject icon-svg !w-[1.8rem] !h-[1.8rem] md:!w-[2.2rem] md:!h-[2.2rem] solid-mono text-[#f78b77] text-orange !mb-2 md:!mb-3"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"></path></svg>
<h4>{!! t('Müzik Prodüksiyon') !!}</h4> <h4 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Müzik Prodüksiyon') !!}</h4>
<p class="!mb-2 flex-1 min-h-[4.5rem]">{!! t('Müzik prodüksiyonu, film müzikleri, kurumsal müzik çalışmaları ve benzeri alanlarda hizmet veriyoruz.') !!}</p> <p class="!mb-0 flex-1 min-h-[4rem] md:min-h-[4.5rem] !text-[11px] md:!text-[14px] !leading-snug md:!leading-relaxed">{!! t('Müzik prodüksiyonu, film müzikleri, kurumsal müzik çalışmaları ve benzeri alanlarda hizmet veriyoruz.') !!}</p>
<a href="{{ route('music-productions.index') }}" class="more hover !text-[#f78b77] focus:!text-[#f78b77] hover:!text-[#f78b77] !mt-auto">{!! t('Daha fazla bilgi edin') !!}</a>
</div> </div>
<!--/.card-body --> <!--/.card-body -->
</div> </a>
<!--/.card --> <!--/.card -->
</div> </div>
<!--/column --> <!--/column -->
<div class="md:w-6/12 lg:w-6/12 xl:w-3/12 w-full flex-[0_0_auto] max-w-full !px-[15px] xl:!px-[12.5px] lg:!px-[12.5px] md:!px-[12.5px] !mt-[25px]"> <div class="w-full md:w-6/12 lg:w-6/12 xl:w-3/12 flex-[0_0_auto] max-w-full glass-card-col">
<div class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col"> <a href="{{ route('page.show', ['slug' => 'uygulama-gelistirme']) }}" class="card glass-card !shadow-[0_0.25rem_1.75rem_rgba(30,34,40,0.07)] h-full flex flex-col">
<div class="card-body flex-[1_1_auto] p-[40px] flex flex-col"> <div class="card-body glass-card-body flex-[1_1_auto] flex flex-col">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-inject icon-svg !w-[2.2rem] !h-[2.2rem] solid-mono text-[#45c4a0] text-green !mb-3"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-inject icon-svg !w-[1.8rem] !h-[1.8rem] md:!w-[2.2rem] md:!h-[2.2rem] solid-mono text-[#45c4a0] text-green !mb-2 md:!mb-3"><rect x="2" y="7" width="20" height="14" rx="2" ry="2"></rect><path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16"></path></svg>
<h4>{!! t('Uygulama Geliştirme') !!}</h4> <h4 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Uygulama Geliştirme') !!}</h4>
<p class="!mb-2 flex-1 min-h-[4.5rem]">{!! t('Android, iOS, MacOS, Windows uygulamaları geliştiriyoruz.') !!}</p> <p class="!mb-0 flex-1 min-h-[4rem] md:min-h-[4.5rem] !text-[11px] md:!text-[14px] !leading-snug md:!leading-relaxed">{!! t('Android, iOS, MacOS, Windows uygulamaları geliştiriyoruz.') !!}</p>
<a href="{{ route('page.show', ['slug' => 'uygulama-gelistirme']) }}" class="more hover !text-[#45c4a0] focus:!text-[#45c4a0] hover:!text-[#45c4a0] !mt-auto">{!! t('Daha fazla bilgi edin') !!}</a>
</div> </div>
<!--/.card-body --> <!--/.card-body -->
</div> </a>
<!--/.card --> <!--/.card -->
</div> </div>
<!--/column --> <!--/column -->
@@ -60,35 +56,86 @@
</section> </section>
<style> <style>
.glass-card-row {
margin-left: -8px !important;
margin-right: -8px !important;
}
@media (min-width: 768px) {
.glass-card-row {
margin-left: -12.5px !important;
margin-right: -12.5px !important;
}
}
.glass-card-col {
padding-left: 8px !important;
padding-right: 8px !important;
margin-top: 16px !important;
}
@media (min-width: 768px) {
.glass-card-col {
padding-left: 12.5px !important;
padding-right: 12.5px !important;
margin-top: 25px !important;
}
}
.glass-card-body {
padding: 20px !important;
}
@media (min-width: 768px) {
.glass-card-body {
padding: 40px !important;
}
}
.glass-card { .glass-card {
position: relative !important; position: relative !important;
border-radius: 30px !important; border-radius: 16px !important;
border: none !important; border: none !important;
background: rgba(255, 255, 255, 0) !important; background: rgba(255, 255, 255, 0.6) !important;
overflow: visible !important; overflow: visible !important;
isolation: isolate; isolation: isolate;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
color: inherit !important;
text-decoration: none !important;
}
@media (min-width: 768px) {
.glass-card {
border-radius: 30px !important;
}
} }
.glass-card svg { .glass-card svg {
filter: brightness(1.4);
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
.glass-card h4 { .glass-card h4 {
color: white; color: #343f52;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
text-shadow: 0px 1px 10px #00000080;
} }
.glass-card:hover h4 { .glass-card:hover h4 {
color: black; color: white;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
text-shadow: none; text-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
} }
.glass-card:hover svg { .glass-card:hover svg {
filter: brightness(0.8); filter: brightness(0) invert(1);
}
.glass-card p {
color: #60697b;
transition: all 0.3s ease-in-out;
}
.glass-card:hover p {
color: rgba(255, 255, 255, 0.9);
} }
.glass-card::before { .glass-card::before {
@@ -97,26 +144,39 @@
inset: 0; inset: 0;
z-index: 1; z-index: 1;
overflow: hidden; overflow: hidden;
border-radius: 30px; border-radius: 16px;
pointer-events: none; pointer-events: none;
-webkit-box-shadow: inset 2px 2px 0px -2px rgba(255, 255, 255, 0.7), inset 0 0 3px 1px rgba(255, 255, 255, 0.7); -webkit-box-shadow: inset 2px 2px 0px -2px rgba(255, 255, 255, 0.7), inset 0 0 3px 1px rgba(255, 255, 255, 0.7);
box-shadow: inset 2px 2px 0px -2px rgba(255, 255, 255, 0.7), inset 0 0 3px 1px rgba(255, 255, 255, 0.7); box-shadow: inset 2px 2px 0px -2px rgba(255, 255, 255, 0.7), inset 0 0 3px 1px rgba(255, 255, 255, 0.7);
} }
@media (min-width: 768px) {
.glass-card::before {
border-radius: 30px;
}
}
.glass-card::after { .glass-card::after {
content: ''; content: '';
position: absolute; position: absolute;
z-index: -1; z-index: -1;
inset: 0; inset: 0;
border-radius: 30px; border-radius: 16px;
-webkit-backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px);
backdrop-filter: blur(15px); backdrop-filter: blur(15px);
overflow: hidden; overflow: hidden;
} }
@media (min-width: 768px) {
.glass-card::after {
border-radius: 30px;
}
}
.glass-card:hover { .glass-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
background: rgba(255, 255, 255, 0.6) !important; background: rgba(247, 108, 38, 0.85) !important;
box-shadow: 0 1rem 3rem rgba(30, 34, 40, 0.12) !important; box-shadow: 0 1rem 3rem rgba(247, 108, 38, 0.3) !important;
} }
</style> </style>
+22
View File
@@ -122,9 +122,22 @@ Route::get('/stajyer/panel', [\App\Http\Controllers\CareerController::class, 'in
Route::post('/stajyer/form-yukle', [\App\Http\Controllers\CareerController::class, 'uploadInternshipForm'])->name('intern.upload-form'); Route::post('/stajyer/form-yukle', [\App\Http\Controllers\CareerController::class, 'uploadInternshipForm'])->name('intern.upload-form');
Route::post('/stajyer/github-kaydet', [\App\Http\Controllers\CareerController::class, 'saveGithubRepo'])->name('intern.save-github'); Route::post('/stajyer/github-kaydet', [\App\Http\Controllers\CareerController::class, 'saveGithubRepo'])->name('intern.save-github');
Route::get('/stajyer/gunluk-indir', [\App\Http\Controllers\CareerController::class, 'downloadMarkdown'])->name('intern.download-journal'); Route::get('/stajyer/gunluk-indir', [\App\Http\Controllers\CareerController::class, 'downloadMarkdown'])->name('intern.download-journal');
Route::post('/stajyer/defteri-kaydet', [\App\Http\Controllers\CareerController::class, 'saveJournalEntry'])->name('intern.save-journal');
Route::get('/stajyer/defteri-yazdir', [\App\Http\Controllers\CareerController::class, 'printJournal'])->name('intern.print-journal');
Route::get('/staj-dogrulama/{code}', [\App\Http\Controllers\CareerController::class, 'verifyCertificate'])->name('internship.verify'); Route::get('/staj-dogrulama/{code}', [\App\Http\Controllers\CareerController::class, 'verifyCertificate'])->name('internship.verify');
Route::post('/stajyer/cikis', [\App\Http\Controllers\CareerController::class, 'internLogout'])->name('intern.logout'); Route::post('/stajyer/cikis', [\App\Http\Controllers\CareerController::class, 'internLogout'])->name('intern.logout');
// Intern Admin Portal
Route::get('/stajyer/admin/giris', [\App\Http\Controllers\CareerController::class, 'internAdminLoginForm'])->name('intern.admin.login');
Route::post('/stajyer/admin/giris', [\App\Http\Controllers\CareerController::class, 'internAdminLogin'])->name('intern.admin.login.submit');
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::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');
// Payment Process // Payment Process
Route::post('/payment/process', [\App\Http\Controllers\PaymentController::class, 'process'])->name('payment.process'); Route::post('/payment/process', [\App\Http\Controllers\PaymentController::class, 'process'])->name('payment.process');
Route::any('/payment/success', [\App\Http\Controllers\PaymentController::class, 'success'])->name('payment.success'); Route::any('/payment/success', [\App\Http\Controllers\PaymentController::class, 'success'])->name('payment.success');
@@ -158,8 +171,17 @@ Route::get('/sitemap.xml', [\App\Http\Controllers\SitemapController::class, 'ind
Route::get('/teklif/{slug}', [\App\Http\Controllers\ProposalController::class, 'show'])->name('proposals.show'); Route::get('/teklif/{slug}', [\App\Http\Controllers\ProposalController::class, 'show'])->name('proposals.show');
Route::post('/teklif/{slug}/action', [\App\Http\Controllers\ProposalController::class, 'action'])->name('proposals.action'); Route::post('/teklif/{slug}/action', [\App\Http\Controllers\ProposalController::class, 'action'])->name('proposals.action');
// Privacy Policy Alternatives
Route::get('/privacy-policy', function () {
return app(PageController::class)->show('privacy');
});
Route::get('/gizlilik-politikasi', function () {
return app(PageController::class)->show('privacy');
});
// Nested Pages (Parent/Child) // Nested Pages (Parent/Child)
Route::get('/{parentSlug}/{slug}', [PageController::class, 'showNested'])->name('page.show_nested'); Route::get('/{parentSlug}/{slug}', [PageController::class, 'showNested'])->name('page.show_nested');
// Pages (en sonda olmalı - catch-all) // Pages (en sonda olmalı - catch-all)
Route::get('/{slug}', [PageController::class, 'show'])->name('page.show'); Route::get('/{slug}', [PageController::class, 'show'])->name('page.show');
+383
View File
@@ -0,0 +1,383 @@
// Tab Switch Logic
document.addEventListener('DOMContentLoaded', function() {
const tabButtons = document.querySelectorAll('.tab-btn');
const tabPanels = document.querySelectorAll('.tab-panel');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
// Deactivate active tab
tabButtons.forEach(btn => {
btn.classList.remove('active', 'bg-blue-50', 'border-blue-100', 'text-blue-900');
btn.classList.add('bg-transparent', 'border-transparent', 'text-slate-600');
btn.querySelector('.tab-icon').classList.remove('bg-blue-600', 'text-white');
btn.querySelector('.tab-icon').classList.add('bg-slate-50', 'text-slate-500');
btn.setAttribute('aria-selected', 'false');
});
// Hide active panels
tabPanels.forEach(panel => {
panel.classList.add('hidden');
});
// Activate clicked tab
button.classList.add('active', 'bg-blue-50', 'border-blue-100', 'text-blue-900');
button.classList.remove('bg-transparent', 'border-transparent', 'text-slate-600');
button.querySelector('.tab-icon').classList.add('bg-blue-600', 'text-white');
button.querySelector('.tab-icon').classList.remove('bg-slate-50', 'text-slate-500');
button.setAttribute('aria-selected', 'true');
// Show targets panel
const targetId = button.getAttribute('data-tab-target');
const targetPanel = document.getElementById(targetId + '-panel');
if (targetPanel) {
targetPanel.classList.remove('hidden');
}
});
});
let quill;
// Tab Switch Logic
document.addEventListener('DOMContentLoaded', function() {
// Initialize Quill editor
quill = new Quill('#editor-content-quill', {
theme: 'snow',
modules: {
toolbar: [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
['clean'] // remove formatting button
]
}
});
const tabButtons = document.querySelectorAll('.tab-btn');
const tabPanels = document.querySelectorAll('.tab-panel');
tabButtons.forEach(button => {
button.addEventListener('click', () => {
// Deactivate active tab
tabButtons.forEach(btn => {
btn.classList.remove('active', 'bg-blue-50', 'border-blue-100', 'text-blue-900');
btn.classList.add('bg-transparent', 'border-transparent', 'text-slate-600');
btn.querySelector('.tab-icon').classList.remove('bg-blue-600', 'text-white');
btn.querySelector('.tab-icon').classList.add('bg-slate-50', 'text-slate-500');
btn.setAttribute('aria-selected', 'false');
});
// Hide active panels
tabPanels.forEach(panel => {
panel.classList.add('hidden');
});
// Activate clicked tab
button.classList.add('active', 'bg-blue-50', 'border-blue-100', 'text-blue-900');
button.classList.remove('bg-transparent', 'border-transparent', 'text-slate-600');
button.querySelector('.tab-icon').classList.add('bg-blue-600', 'text-white');
button.querySelector('.tab-icon').classList.remove('bg-slate-50', 'text-slate-500');
button.setAttribute('aria-selected', 'true');
// Show targets panel
const targetId = button.getAttribute('data-tab-target');
const targetPanel = document.getElementById(targetId + '-panel');
if (targetPanel) {
targetPanel.classList.remove('hidden');
}
});
});
// Handle initial state of date logic
calculateEndDateFrontend();
// Fetch Github commits in memory if repo is set
fetchAllCommitsInMemory();
// Select first day of the notebook workspace on load
selectNotebookDay(0);
});
const githubRepoUrl = "dummy";
let allGithubCommits = {};
let activeDayIdx = 0;
function fetchAllCommitsInMemory() {
if (!githubRepoUrl) return;
let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, '');
repoClean = repoClean.replace(/\/$/, '');
const parts = repoClean.split('/');
if (parts.length < 2) return;
const owner = parts[0];
const repo = parts[1].replace(/\.git$/i, '');
fetch(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=100`)
.then(response => {
if (!response.ok) throw new Error('API Error');
return response.json();
})
.then(commits => {
if (!Array.isArray(commits)) return;
commits.forEach(item => {
const dateStr = item.commit.author.date;
if (dateStr) {
const dateOnly = dateStr.substring(0, 10);
if (!allGithubCommits[dateOnly]) {
allGithubCommits[dateOnly] = [];
}
allGithubCommits[dateOnly].push(item);
}
});
})
.catch(err => {
console.warn('Commits could not be pre-loaded in memory.', err);
});
}
function selectNotebookDay(idx) {
const oldBtn = document.getElementById(`btn-day-${activeDayIdx}`);
if (oldBtn) {
oldBtn.classList.remove('border-blue-200', 'bg-blue-50/50');
oldBtn.classList.add('border-slate-100', 'bg-white');
const oldTitle = oldBtn.querySelector('span.text-xs');
if (oldTitle) {
oldTitle.classList.remove('text-blue-700');
oldTitle.classList.add('text-slate-800');
}
}
activeDayIdx = idx;
const newBtn = document.getElementById(`btn-day-${idx}`);
if (newBtn) {
newBtn.classList.add('border-blue-200', 'bg-blue-50/50');
newBtn.classList.remove('border-slate-100', 'bg-white');
const newTitle = newBtn.querySelector('span.text-xs');
if (newTitle) {
newTitle.classList.add('text-blue-700');
newTitle.classList.remove('text-slate-800');
}
const dayNum = newBtn.getAttribute('data-day-num');
const dateVal = newBtn.getAttribute('data-date');
const dateFormatted = newBtn.getAttribute('data-formatted-date');
const savedContent = newBtn.getAttribute('data-saved-content');
document.getElementById('editor-day-title').textContent = `${dayNum}. Gün`;
document.getElementById('editor-day-date').textContent = dateFormatted;
quill.root.innerHTML = savedContent || '';
document.getElementById('save-status').textContent = '';
// Future validation
const dateParts = dateVal.split('-');
const selectedDate = new Date(dateParts[0], dateParts[1] - 1, dateParts[2]);
const today = new Date();
today.setHours(0,0,0,0);
const isFuture = selectedDate > today;
const warningBlock = document.getElementById('future-warning');
const saveBtn = document.getElementById('save-btn');
const gitBtn = document.querySelector('button[onclick="fillFromGithub()"]');
if (isFuture) {
warningBlock.classList.remove('hidden');
quill.enable(false);
saveBtn.disabled = true;
saveBtn.classList.add('opacity-50', 'cursor-not-allowed');
if (gitBtn) {
gitBtn.disabled = true;
gitBtn.classList.add('opacity-50', 'cursor-not-allowed');
}
} else {
warningBlock.classList.add('hidden');
quill.enable(true);
saveBtn.disabled = false;
saveBtn.classList.remove('opacity-50', 'cursor-not-allowed');
if (gitBtn) {
gitBtn.disabled = false;
gitBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
}
}
}
function fillFromGithub() {
const btn = document.getElementById(`btn-day-${activeDayIdx}`);
if (!btn) return;
const dateVal = btn.getAttribute('data-date');
const dayCommits = allGithubCommits[dateVal] || [];
if (dayCommits.length === 0) {
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.");
return;
}
let commitHtml = "<ul>";
const sorted = [...dayCommits].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', { hour: '2-digit', minute: '2-digit' });
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
});
commitHtml += "</ul>";
const currentHtml = quill.root.innerHTML.trim();
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
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;
} else {
quill.root.innerHTML = commitHtml;
}
} else {
quill.root.innerHTML = commitHtml;
}
}
function saveActiveDay() {
const btn = document.getElementById(`btn-day-${activeDayIdx}`);
if (!btn) return;
const dayNum = btn.getAttribute('data-day-num');
const dateVal = btn.getAttribute('data-date');
let contentVal = quill.root.innerHTML;
if (contentVal === '<p><br></p>') {
contentVal = '';
}
const statusText = document.getElementById('save-status');
statusText.textContent = "Kaydediliyor...";
statusText.className = "text-xs font-semibold text-slate-400 animate-pulse";
fetch(""dummy"", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-TOKEN": ""dummy""
},
body: JSON.stringify({
day_number: dayNum,
date: dateVal,
content: contentVal
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
statusText.textContent = "Başarıyla kaydedildi.";
statusText.className = "text-xs font-semibold text-green-600";
btn.setAttribute('data-saved-content', contentVal);
const badge = document.getElementById(`badge-day-${activeDayIdx}`);
if (badge) {
if (contentVal.trim() !== '') {
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";
} else {
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";
}
}
} else {
statusText.textContent = data.message || "Kaydedilemedi.";
statusText.className = "text-xs font-semibold text-red-600";
}
})
.catch(err => {
console.error(err);
statusText.textContent = "Bağlantı hatası oluştu.";
statusText.className = "text-xs font-semibold text-red-600";
});
}
function openPrintModal() {
const modal = document.getElementById('print-modal');
const card = document.getElementById('print-modal-card');
modal.classList.remove('hidden');
setTimeout(() => {
card.classList.remove('scale-95', 'opacity-0');
card.classList.add('scale-100', 'opacity-100');
}, 10);
}
function closePrintModal() {
const modal = document.getElementById('print-modal');
const card = document.getElementById('print-modal-card');
card.classList.remove('scale-100', 'opacity-100');
card.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
}
function handleFileSelected(input) {
const fileNameElement = document.getElementById('selected-file-name');
const submitBtn = document.getElementById('submit-upload-btn');
if (input.files && input.files.length > 0) {
const file = input.files[0];
const fileSizeMB = file.size / (1024 * 1024);
if (fileSizeMB > 50) {
alert('Dosya boyutu 50MB\'ı aşamaz.');
input.value = '';
fileNameElement.textContent = '';
submitBtn.disabled = true;
submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
return;
}
fileNameElement.textContent = 'Seçilen dosya: ' + file.name + ' (' + fileSizeMB.toFixed(2) + ' MB)';
submitBtn.disabled = false;
submitBtn.classList.remove('opacity-50', 'cursor-not-allowed');
} else {
fileNameElement.textContent = '';
submitBtn.disabled = true;
submitBtn.classList.add('opacity-50', 'cursor-not-allowed');
}
}
function calculateEndDateFrontend() {
const startDateVal = document.getElementById('internship_start_date').value;
const totalDaysVal = document.getElementById('internship_total_days').value;
const endDateInput = document.getElementById('internship_end_date');
if (!startDateVal || !totalDaysVal || totalDaysVal <= 0) {
return;
}
let date = new Date(startDateVal);
let daysToAdd = parseInt(totalDaysVal);
let count = 0;
let endDate = new Date(startDateVal);
while (count < daysToAdd) {
const dayOfWeek = date.getDay();
if (dayOfWeek === 6 || dayOfWeek === 0) { // 6 = Saturday, 0 = Sunday
date.setDate(date.getDate() + 1);
continue;
}
endDate = new Date(date);
date.setDate(date.getDate() + 1);
count++;
}
// Format to YYYY-MM-DD
const yyyy = endDate.getFullYear();
const mm = String(endDate.getMonth() + 1).padStart(2, '0');
const dd = String(endDate.getDate()).padStart(2, '0');
endDateInput.value = `${yyyy}-${mm}-${dd}`;
}
function copyToClipboard(elementId, btn) {
const text = document.getElementById(elementId).textContent;
navigator.clipboard.writeText(text).then(() => {
const icon = btn.querySelector('i');
const originalClass = icon.className;
icon.className = 'uil uil-check text-green-500';
setTimeout(() => {
icon.className = originalClass;
}, 2000);
});
}