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')
->label('Kullanıcı Adı')
->unique(ignoreRecord: true)
->nullable(),
->default(fn ($record) => $record?->email)
->disabled()
->dehydrated()
->autocomplete('new-username'),
TextInput::make('password')
->label('Şifre')
->password()
->revealable()
->autocomplete('new-password')
->formatStateUsing(fn () => null)
->dehydrateStateUsing(fn ($state) => filled($state) ? Hash::make($state) : null)
->dehydrated(fn ($state) => filled($state))
->placeholder('Şifreyi değiştirmek istemiyorsanız boş bırakın')
@@ -136,6 +140,12 @@ class InternApplicationResource extends Resource
->disk('public')
->directory('signed_interns')
->downloadable()
->live()
->afterStateUpdated(function ($state, Set $set) {
if ($state) {
$set('status', 'accepted');
}
})
->nullable(),
DatePicker::make('internship_start_date')
@@ -177,6 +187,150 @@ class InternApplicationResource extends Resource
->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')
->icon('heroicon-o-academic-cap')
->schema([
@@ -355,4 +509,5 @@ class InternApplicationResource extends Resource
'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'));
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
return view('front.career.intern_dashboard', [
'intern' => $intern,
'days' => $days,
'savedEntries' => $savedEntries,
'meta' => [
'title' => 'Stajyer Paneli',
'description' => 'Belgelerinizi buradan indirebilirsiniz.',
@@ -186,6 +191,7 @@ class CareerController extends Controller
'internship_start_date' => $request->internship_start_date,
'internship_end_date' => $endDate->format('Y-m-d'),
'internship_total_days' => $daysToAdd,
'status' => 'waiting_document',
]);
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([
'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.url' => 'Geçerli bir URL girilmelidir.',
'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'));
// Sanitize & format github URL
$repoUrl = trim($request->github_repo);
$username = trim($request->github_username);
$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()
{
if (!session()->has('intern_id')) {
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
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(session('intern_id'));
$intern = CareerApplication::findOrFail($internId);
$repo = $intern->github_repo;
if (!$repo) {
return redirect()->back()->with('error', 'Lütfen önce Github deposunu tanımlayın.');
}
@@ -319,12 +345,400 @@ class CareerController extends Controller
->where('type', 'internship')
->firstOrFail();
$days = self::getInternshipDates($application->internship_start_date, $application->internship_total_days);
$savedEntries = $application->journalEntries()->get()->keyBy('day_number');
return view('front.career.verify', [
'application' => $application,
'days' => $days,
'savedEntries' => $savedEntries,
'meta' => [
'title' => 'Staj Bitirme Sertifikası Doğrulama - ' . $application->name,
'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
{
$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
$homeUrl = url('/');
$urls[] = [
'loc' => url('/'),
'loc' => $homeUrl,
'alternates' => $getAlternates($homeUrl),
'lastmod' => now()->startOfDay()->toAtomString(),
'changefreq' => 'daily',
'priority' => '1.0',
];
$blogIndexUrl = route('blog.index');
$urls[] = [
'loc' => route('blog.index'),
'loc' => $blogIndexUrl,
'alternates' => $getAlternates($blogIndexUrl),
'lastmod' => now()->startOfDay()->toAtomString(),
'changefreq' => 'weekly',
'priority' => '0.8',
];
$careerIndexUrl = route('career.index');
$urls[] = [
'loc' => route('career.index'),
'loc' => $careerIndexUrl,
'alternates' => $getAlternates($careerIndexUrl),
'lastmod' => now()->startOfMonth()->toAtomString(),
'changefreq' => 'monthly',
'priority' => '0.5',
@@ -40,8 +66,10 @@ class SitemapController extends Controller
->where('is_homepage', false)
->get();
foreach ($pages as $page) {
$pageUrl = url($page->slug);
$urls[] = [
'loc' => url($page->slug),
'loc' => $pageUrl,
'alternates' => $getAlternates($pageUrl),
'lastmod' => $page->updated_at->toAtomString(),
'changefreq' => 'weekly',
'priority' => '0.7',
@@ -52,8 +80,10 @@ class SitemapController extends Controller
if (class_exists(Blog::class)) {
$posts = Blog::published()->get();
foreach ($posts as $post) {
$postUrl = route('blog.show', $post->slug);
$urls[] = [
'loc' => route('blog.show', $post->slug),
'loc' => $postUrl,
'alternates' => $getAlternates($postUrl),
'lastmod' => $post->updated_at->toAtomString(),
'changefreq' => 'weekly',
'priority' => '0.6',
@@ -65,8 +95,10 @@ class SitemapController extends Controller
if (class_exists(Product::class)) {
$products = Product::where('is_active', true)->get();
foreach ($products as $product) {
$productUrl = route('products.show', $product->slug);
$urls[] = [
'loc' => route('products.show', $product->slug),
'loc' => $productUrl,
'alternates' => $getAlternates($productUrl),
'lastmod' => $product->updated_at->toAtomString(),
'changefreq' => 'weekly',
'priority' => '0.7',
+19 -11
View File
@@ -16,10 +16,25 @@ class SetLocale
*/
public function handle(Request $request, Closure $next): Response
{
// Session'dan locale'i al, yoksa varsayılan dil kodunu kullan
$locale = session('locale');
// Aktif dilleri veritabanından kontrol et
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 (function_exists('default_language_code')) {
$locale = default_language_code();
@@ -28,14 +43,7 @@ class SetLocale
}
}
// Aktif dilleri veritabanından kontrol et
if (function_exists('available_language_codes')) {
$availableLocales = available_language_codes();
} else {
$availableLocales = ['tr', 'en'];
}
// Locale'i aktif diller arasında kontrol et
// Locale'i aktif diller arasında kontrol et ve ata
if (in_array($locale, $availableLocales)) {
App::setLocale($locale);
} else {
+25 -5
View File
@@ -30,18 +30,35 @@ class CareerApplication extends Model
'internship_end_date',
'internship_total_days',
'github_repo',
'github_username',
'certificate_code',
'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()
{
static::saving(function ($model) {
if ($model->type === 'internship' && !$model->certificate_code) {
do {
$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;
if ($model->type === 'internship') {
$model->username = $model->email;
if (!$model->certificate_code) {
do {
$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 [
'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
})->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([
\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();
$node = [
'@type' => 'Organization',
'@type' => 'ProfessionalService',
'@id' => $siteUrl . '#organization',
'name' => $siteName,
'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');
@@ -93,6 +125,10 @@ abstract class StructuredData
$node['address'] = [
'@type' => 'PostalAddress',
'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',
'rejected' => 'Reddedildi',
'accepted' => 'Kabul Edildi',
'waiting_document' => 'Belge Bekleniyor',
'waiting_document' => 'İmzalı Staj Formu Bekleniyor',
// Wizard and Mermaid
'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-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-SemiBold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-SemiBold.woff) format('woff');
font-weight: 600;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Light.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Light.woff) format('woff');
font-weight: 300;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Bold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Bold.woff) format('woff');
font-weight: 700;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Medium.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Medium.woff) format('woff');
font-weight: 500;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Regular.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Regular.woff) format('woff');
font-weight: 400;
font-style: normal;
font-display: block
font-display: swap;
}
* {
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');
font-weight: 700;
font-style: italic;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff) format('woff');
font-weight: 600;
font-style: italic;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Medium.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Medium.woff) format('woff');
font-weight: 500;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-MediumItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-MediumItalic.woff) format('woff');
font-weight: 500;
font-style: italic;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-SemiBold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBold.woff) format('woff');
font-weight: 600;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Italic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Italic.woff) format('woff');
font-weight: 400;
font-style: italic;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Regular.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Regular.woff) format('woff');
font-weight: 400;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-LightItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-LightItalic.woff) format('woff');
font-weight: 300;
font-style: italic;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Light.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Light.woff) format('woff');
font-weight: 300;
font-style: normal;
font-display: block
font-display: swap;
}
@font-face {
font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Bold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Bold.woff) format('woff');
font-weight: 700;
font-style: normal;
font-display: block
font-display: swap;
}
* {
word-spacing: normal !important
+2 -2
View File
@@ -3,12 +3,12 @@
src:url(Unicons.woff2) format("woff2"),url(Unicons.woff) format("woff");
font-weight:400;
font-style:normal;
font-display:block
font-display:swap;
}
@font-face {
font-family:Custom;
src:url(../custom/Custom.woff2) format("woff2"),url(../custom/Custom.woff) format("woff");
font-weight:400;
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: *
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')
<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="container max-w-6xl mx-auto px-4">
<!-- 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="flex items-center gap-4">
<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">
<i class="uil uil-shield-check text-2xl"></i>
<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-3 flex-1 min-w-0">
<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-xl"></i>
</div>
<div>
<h3 class="text-lg font-bold text-slate-800">Güvenli Belge Doğrulama</h3>
<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>
<div class="text-sm leading-normal">
<span class="font-bold text-slate-800">Güvenli Belge Doğrulama:</span>
<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 class="flex items-center gap-3 flex-wrap">
<div class="flex-shrink-0">
<!-- 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">
<i class="uil uil-file-download text-lg" 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 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>
<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-base" id="pdf-icon"></i>
<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>
</div>
</div>
</div>
</div>
@@ -47,8 +42,6 @@
</div>
<!-- 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]">
@@ -59,24 +52,23 @@
<img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Teknoloji" class="h-16 md:h-20 object-contain">
</div>
<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>
</div>
</div>
<!-- Main Content -->
<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>
<div class="w-24 h-[3px] bg-orange-500 mx-auto mb-8"></div>
<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>
<p class="text-slate-400 text-xs md:text-sm uppercase tracking-wider mb-2">Sayın</p>
<h2 class="font-serif text-2xl md:text-4xl font-extrabold text-slate-900 tracking-wide mb-6">{{ $application->name }}</h2>
<p class="text-slate-400 text-xs md:text-sm tracking-wider mb-0">SAYIN</p>
<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">
<p>
<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>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 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.
@@ -90,28 +82,18 @@
<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">
<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-[9px] text-slate-500 block">Karekod okutularak belgenin güncelliği e-Devlet stilinde doğrulanabilir.</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 doğrulanabilir.</span>
</div>
</div>
<!-- Middle: Date -->
<div class="text-center">
<span class="text-[9px] text-slate-400 block uppercase tracking-wider">Düzenlenme Tarihi</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-[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-800 text-glow-white">{{ \Carbon\Carbon::parse($application->internship_end_date)->format('d.m.Y') }}</span>
</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>
@@ -129,15 +111,15 @@
<!-- 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 uppercase transcript-header-title">Staj Akademik Transkripti</h2>
<p class="text-slate-400 tracking-wider uppercase mt-0.5 transcript-header-subtitle">Ve Performans Değerlendirme Raporu</p>
<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 mt-0.5 transcript-header-subtitle">VE PERFORMANS DEĞERLENDİRME RAPORU</p>
</div>
<img src="{{ asset('logos/truncgil-yatay.svg') }}" alt="Trunçgil Logo" class="h-6 object-contain">
</div>
<!-- Student Info block -->
<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="flex justify-between border-b border-slate-200/50 pb-1">
<span class="text-slate-500">Adı Soyadı:</span>
@@ -169,7 +151,7 @@
<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 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>
</div>
</div>
@@ -182,8 +164,97 @@
</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>
@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')
<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) */
--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 {
font-family: 'Inter', sans-serif;
@@ -201,6 +287,13 @@
.font-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-header-title {
@@ -375,6 +468,7 @@
padding: 15mm !important;
box-shadow: none !important;
border: none !important;
border-radius: 0 !important;
box-sizing: border-box !important;
page-break-after: always !important;
break-after: page !important;
@@ -397,6 +491,7 @@
padding: 10mm !important;
box-shadow: none !important;
border: none !important;
border-radius: 0 !important;
box-sizing: border-box !important;
overflow: hidden !important;
}
@@ -435,6 +530,7 @@
function scaleAllDocuments() {
scaleDocument('.certificate-container-wrapper', '#certificate-node', 1122, 794);
scaleDocument('.transcript-container-wrapper', '#transcript-node', 794, 1122);
scaleDocument('.notebook-container-wrapper', '#notebook-node', 794, 1122);
}
window.addEventListener('resize', scaleAllDocuments);
@@ -464,22 +560,40 @@
const certEl = document.getElementById('certificate-node');
const transEl = document.getElementById('transcript-node');
const notebookEl = document.getElementById('notebook-node');
// Force elements to normal scale prior to capturing for PDF
const origCertTransform = certEl.style.transform;
const origCertWidth = certEl.style.width;
const origCertHeight = certEl.style.height;
const origCertRadius = certEl.style.borderRadius;
const origTransTransform = transEl.style.transform;
const origTransWidth = transEl.style.width;
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.width = '1122px';
certEl.style.height = '794px';
certEl.style.borderRadius = '0';
transEl.style.transform = 'none';
transEl.style.width = '794px';
transEl.style.height = '1122px';
transEl.style.borderRadius = '0';
// Render Page 1 (Landscape)
const certCanvas = await html2canvas(certEl, {
scale: 2,
@@ -488,7 +602,7 @@
logging: false
});
const certImgData = certCanvas.toDataURL('image/jpeg', 1.0);
// Initialize landscape A4 document
const pdf = new jsPDF({
orientation: 'landscape',
@@ -496,7 +610,7 @@
format: 'a4'
});
pdf.addImage(certImgData, 'JPEG', 0, 0, 297, 210);
// Render Page 2 (Portrait)
const transCanvas = await html2canvas(transEl, {
scale: 2,
@@ -505,23 +619,47 @@
logging: false
});
const transImgData = transCanvas.toDataURL('image/jpeg', 1.0);
// Calculate exact height using aspect ratio to prevent vertical squishing
const transWidth = 210;
const transHeight = (transCanvas.height * transWidth) / transCanvas.width;
// Add portrait page to PDF
pdf.addPage('a4', 'portrait');
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
certEl.style.transform = origCertTransform;
certEl.style.width = origCertWidth;
certEl.style.height = origCertHeight;
certEl.style.borderRadius = origCertRadius;
transEl.style.transform = origTransTransform;
transEl.style.width = origTransWidth;
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
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="preconnect" href="https://fonts.googleapis.com">
<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')
<style>
.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
@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_og_type = $meta['og_type'] ?? 'website';
$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
<title>{{ $seo_title }}</title>
<meta name="description" content="{{ $seo_description }}">
<meta name="robots" content="{{ $seo_robots }}">
<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 -->
<meta property="og:type" content="{{ $seo_og_type }}">
<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') }}">
@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="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>
@@ -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="preconnect" href="https://fonts.googleapis.com">
<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="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=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'">
<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 -->
@if(setting('seo_google_search_console'))
+7 -1
View File
@@ -1,8 +1,14 @@
{!! '<' . '?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)
<url>
<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>
<changefreq>{{ $url['changefreq'] }}</changefreq>
<priority>{{ $url['priority'] }}</priority>
@@ -78,4 +78,27 @@
left: 50%;
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>
@@ -1,55 +1,51 @@
<section class="wrapper !bg-[#ffffff]">
<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="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="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">
<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>
<h4>{!! t('Yapay Zeka') !!}</h4>
<p class="!mb-2 flex-1 min-h-[4.5rem]">{!! 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 class="flex flex-wrap glass-card-row !mb-[4.5rem] !mt-[-9rem] xl:!mb-[7rem] lg:!mb-[7rem] md:!mb-[7rem]">
<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">
<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 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-[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 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Yapay Zeka') !!}</h4>
<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>
</div>
<!--/.card-body -->
</div>
</a>
<!--/.card -->
</div>
<!--/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="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">
<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>
<h4>{!! 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>
<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 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">
<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 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-[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 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Web Uygulamaları') !!}</h4>
<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>
</div>
<!--/.card-body -->
</div>
</a>
<!--/.card -->
</div>
<!--/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="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">
<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>
<h4>{!! 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>
<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 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">
<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 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-[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 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Müzik Prodüksiyon') !!}</h4>
<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>
</div>
<!--/.card-body -->
</div>
</a>
<!--/.card -->
</div>
<!--/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="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">
<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>
<h4>{!! t('Uygulama Geliştirme') !!}</h4>
<p class="!mb-2 flex-1 min-h-[4.5rem]">{!! 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 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">
<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 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-[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 class="!text-[14px] md:!text-[1.1rem] !mb-1 md:!mb-2">{!! t('Uygulama Geliştirme') !!}</h4>
<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>
</div>
<!--/.card-body -->
</div>
</a>
<!--/.card -->
</div>
<!--/column -->
@@ -60,35 +56,86 @@
</section>
<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 {
position: relative !important;
border-radius: 30px !important;
border-radius: 16px !important;
border: none !important;
background: rgba(255, 255, 255, 0) !important;
background: rgba(255, 255, 255, 0.6) !important;
overflow: visible !important;
isolation: isolate;
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 {
filter: brightness(1.4);
transition: all 0.3s ease-in-out;
}
.glass-card h4 {
color: white;
color: #343f52;
transition: all 0.3s ease-in-out;
text-shadow: 0px 1px 10px #00000080;
}
.glass-card:hover h4 {
color: black;
color: white;
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 {
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 {
@@ -97,26 +144,39 @@
inset: 0;
z-index: 1;
overflow: hidden;
border-radius: 30px;
border-radius: 16px;
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);
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 {
content: '';
position: absolute;
z-index: -1;
inset: 0;
border-radius: 30px;
border-radius: 16px;
-webkit-backdrop-filter: blur(15px);
backdrop-filter: blur(15px);
overflow: hidden;
}
@media (min-width: 768px) {
.glass-card::after {
border-radius: 30px;
}
}
.glass-card:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.6) !important;
box-shadow: 0 1rem 3rem rgba(30, 34, 40, 0.12) !important;
background: rgba(247, 108, 38, 0.85) !important;
box-shadow: 0 1rem 3rem rgba(247, 108, 38, 0.3) !important;
}
</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/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::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::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
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');
@@ -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::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)
Route::get('/{parentSlug}/{slug}', [PageController::class, 'showNested'])->name('page.show_nested');
// Pages (en sonda olmalı - catch-all)
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);
});
}