7 Commits

23 changed files with 760 additions and 32333 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;
}
}
@@ -510,10 +510,4 @@ class InternApplicationResource extends Resource
]; ];
} }
public static function getWidgets(): array
{
return [
Widgets\InternGanttChartWidget::class,
];
}
} }
@@ -8,11 +8,4 @@ use Filament\Resources\Pages\ListRecords;
class ListInternApplications extends ListRecords class ListInternApplications extends ListRecords
{ {
protected static string $resource = InternApplicationResource::class; protected static string $resource = InternApplicationResource::class;
protected function getHeaderWidgets(): array
{
return [
\App\Filament\Admin\Resources\InternApplications\Widgets\InternGanttChartWidget::class,
];
}
} }
@@ -1,175 +1,2 @@
<?php <?php
// Bu bileşen devre dışı bırakılmıştır.
namespace App\Filament\Admin\Resources\InternApplications\Widgets;
use App\Models\CareerApplication;
use Filament\Widgets\Widget;
use Carbon\Carbon;
class InternGanttChartWidget extends Widget
{
protected string $view = 'filament.admin.resources.intern-applications.widgets.intern-gantt-chart-widget';
protected int | string | array $columnSpan = 'full';
public function getViewData(): array
{
$interns = CareerApplication::query()
->where('type', 'internship')
->where('status', 'accepted')
->whereNotNull('internship_start_date')
->whereNotNull('internship_end_date')
->orderBy('internship_start_date', 'asc')
->get();
if ($interns->isEmpty()) {
return [
'interns' => [],
'months' => [],
'totalDays' => 0,
'minDate' => null,
'todayPercent' => null,
];
}
$minDate = Carbon::parse($interns->min('internship_start_date'))->startOfDay();
$maxDate = Carbon::parse($interns->max('internship_end_date'))->endOfDay();
// Pad the range slightly for better visual presentation
$minDate = $minDate->copy()->subDays(5);
$maxDate = $maxDate->copy()->addDays(5);
$totalDays = $minDate->diffInDays($maxDate);
if ($totalDays <= 0) {
$totalDays = 1;
}
// Generate months in range
$months = [];
$tempDate = $minDate->copy()->startOfMonth();
$endLimit = $maxDate->copy()->endOfMonth();
while ($tempDate->lte($endLimit)) {
$monthStart = $tempDate->copy()->startOfMonth();
$monthEnd = $tempDate->copy()->endOfMonth();
$overlapStart = $monthStart->gt($minDate) ? $monthStart : $minDate;
$overlapEnd = $monthEnd->lt($maxDate) ? $monthEnd : $maxDate;
if ($overlapStart->lte($overlapEnd)) {
$daysInOverlap = $overlapStart->diffInDays($overlapEnd) + 1;
$startOffset = $minDate->diffInDays($overlapStart);
$months[] = [
'name' => $tempDate->translatedFormat('F Y'),
'left' => ($startOffset / $totalDays) * 100,
'width' => ($daysInOverlap / $totalDays) * 100,
];
}
$tempDate->addMonth();
}
// Calculate today's line position if it falls within the range
$today = Carbon::today();
$todayPercent = null;
if ($today->between($minDate, $maxDate)) {
$todayPercent = ($minDate->diffInDays($today) / $totalDays) * 100;
}
$formattedInterns = $interns->map(function ($intern) use ($minDate, $totalDays) {
$start = Carbon::parse($intern->internship_start_date)->startOfDay();
$end = Carbon::parse($intern->internship_end_date)->endOfDay();
$startOffset = $minDate->diffInDays($start);
$duration = $start->diffInDays($end) + 1;
$left = ($startOffset / $totalDays) * 100;
$width = ($duration / $totalDays) * 100;
// Ensure width is at least a visible sliver
if ($width < 1.5) {
$width = 1.5;
}
// Assign a stable color palette based on the intern ID
$palettes = [
[
'bg' => 'bg-emerald-500 dark:bg-emerald-600',
'gradient' => 'from-emerald-400 to-teal-500 dark:from-emerald-500 dark:to-teal-600',
'text' => 'text-emerald-950 dark:text-emerald-50',
'border' => 'border-emerald-500/20',
'shadow' => 'shadow-emerald-500/20'
],
[
'bg' => 'bg-blue-500 dark:bg-blue-600',
'gradient' => 'from-blue-400 to-indigo-500 dark:from-blue-500 dark:to-indigo-600',
'text' => 'text-blue-950 dark:text-blue-50',
'border' => 'border-blue-500/20',
'shadow' => 'shadow-blue-500/20'
],
[
'bg' => 'bg-rose-500 dark:bg-rose-600',
'gradient' => 'from-rose-400 to-pink-500 dark:from-rose-500 dark:to-pink-600',
'text' => 'text-rose-950 dark:text-rose-50',
'border' => 'border-rose-500/20',
'shadow' => 'shadow-rose-500/20'
],
[
'bg' => 'bg-amber-500 dark:bg-amber-600',
'gradient' => 'from-amber-400 to-orange-500 dark:from-amber-500 dark:to-orange-600',
'text' => 'text-amber-950 dark:text-amber-50',
'border' => 'border-amber-500/20',
'shadow' => 'shadow-amber-500/20'
],
[
'bg' => 'bg-violet-500 dark:bg-violet-600',
'gradient' => 'from-violet-400 to-purple-500 dark:from-violet-500 dark:to-purple-600',
'text' => 'text-violet-950 dark:text-violet-50',
'border' => 'border-violet-500/20',
'shadow' => 'shadow-violet-500/20'
],
];
$paletteIndex = $intern->id % count($palettes);
$color = $palettes[$paletteIndex];
$startMonth = (int) $start->format('n');
$endMonth = (int) $end->format('n');
$gridStart = $startMonth + 1;
$gridEnd = min(14, $endMonth + 2);
// Background color map based on user's examples
$hexColors = [
'#2ecaac', // Emerald Teal
'#54c6f9', // Sky Blue
'#ff6252', // Red Coral
'#f59e0b', // Amber
'#8b5cf6', // Violet
];
$hexColor = $hexColors[$intern->id % count($hexColors)];
$isStripes = ($intern->id % 2 === 0);
return [
'id' => $intern->id,
'name' => $intern->name,
'start_date' => $start->translatedFormat('d M Y'),
'end_date' => $end->translatedFormat('d M Y'),
'total_days' => $intern->internship_total_days,
'left' => $left,
'width' => $width,
'color' => $color,
'grid_start' => $gridStart,
'grid_end' => $gridEnd,
'hex_color' => $hexColor,
'is_stripes' => $isStripes,
'raw_start' => $intern->internship_start_date,
'raw_end' => $intern->internship_end_date,
];
});
return [
'interns' => $formattedInterns,
'months' => $months,
'todayPercent' => $todayPercent,
];
}
}
+26 -2
View File
@@ -208,22 +208,35 @@ class CareerController extends Controller
$request->validate([ $request->validate([
'github_repo' => 'required|string|url|max:255', 'github_repo' => 'required|string|url|max:255',
'github_username' => 'nullable|string|max:100',
], [ ], [
'github_repo.required' => 'Lütfen Github depo URL\'sini girin.', 'github_repo.required' => 'Lütfen Github depo URL\'sini girin.',
'github_repo.url' => 'Geçerli bir URL girilmelidir.', 'github_repo.url' => 'Geçerli bir URL girilmelidir.',
'github_repo.max' => 'Github depo URL\'si en fazla 255 karakter olabilir.', 'github_repo.max' => 'Github depo URL\'si en fazla 255 karakter olabilir.',
'github_username.max' => 'Github kullanıcı adı en fazla 100 karakter olabilir.',
]); ]);
$intern = CareerApplication::findOrFail(session('intern_id')); $intern = CareerApplication::findOrFail(session('intern_id'));
// Sanitize & format github URL // Sanitize & format github URL
$repoUrl = trim($request->github_repo); $repoUrl = trim($request->github_repo);
$username = trim($request->github_username);
$intern->update([ $intern->update([
'github_repo' => $repoUrl 'github_repo' => $repoUrl,
'github_username' => $username ?: null,
]); ]);
return redirect()->back()->with('success', 'Github deposu başarıyla güncellendi.'); if ($request->expectsJson() || $request->ajax()) {
return response()->json([
'success' => true,
'message' => 'Github bilgileri başarıyla güncellendi.',
'github_repo' => $repoUrl,
'github_username' => $username ?: null,
]);
}
return redirect()->back()->with('success', 'Github bilgileri başarıyla güncellendi.');
} }
public function downloadMarkdown() public function downloadMarkdown()
@@ -494,6 +507,8 @@ class CareerController extends Controller
], [ ], [
'content' => $request->content, 'content' => $request->content,
'is_retroactive' => $isRetroactive, 'is_retroactive' => $isRetroactive,
'supervisor_approved' => false,
'supervisor_name' => null,
]); ]);
return response()->json([ return response()->json([
@@ -521,6 +536,15 @@ class CareerController extends Controller
} }
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days); $days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
$dayFilter = $request->query('day');
if ($dayFilter) {
$days = array_filter($days, function($d) use ($dayFilter) {
return $d['day_number'] == $dayFilter;
});
$days = array_values($days);
}
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number'); $savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
return view('front.career.print', [ return view('front.career.print', [
+38 -6
View File
@@ -12,24 +12,50 @@ class SitemapController extends Controller
public function index(): Response public function index(): Response
{ {
$urls = []; $urls = [];
$activeLanguages = [];
if (class_exists(\App\Models\Language::class)) {
$activeLanguages = \App\Models\Language::where('is_active', true)->get();
}
$getAlternates = function (string $baseUrl) use ($activeLanguages) {
$alternates = [];
foreach ($activeLanguages as $lang) {
$connector = str_contains($baseUrl, '?') ? '&' : '?';
$alternates[] = [
'hreflang' => $lang->code,
'href' => $baseUrl . $connector . 'locale=' . $lang->code,
];
}
$alternates[] = [
'hreflang' => 'x-default',
'href' => $baseUrl,
];
return $alternates;
};
// 1. Static & Main Pages // 1. Static & Main Pages
$homeUrl = url('/');
$urls[] = [ $urls[] = [
'loc' => url('/'), 'loc' => $homeUrl,
'alternates' => $getAlternates($homeUrl),
'lastmod' => now()->startOfDay()->toAtomString(), 'lastmod' => now()->startOfDay()->toAtomString(),
'changefreq' => 'daily', 'changefreq' => 'daily',
'priority' => '1.0', 'priority' => '1.0',
]; ];
$blogIndexUrl = route('blog.index');
$urls[] = [ $urls[] = [
'loc' => route('blog.index'), 'loc' => $blogIndexUrl,
'alternates' => $getAlternates($blogIndexUrl),
'lastmod' => now()->startOfDay()->toAtomString(), 'lastmod' => now()->startOfDay()->toAtomString(),
'changefreq' => 'weekly', 'changefreq' => 'weekly',
'priority' => '0.8', 'priority' => '0.8',
]; ];
$careerIndexUrl = route('career.index');
$urls[] = [ $urls[] = [
'loc' => route('career.index'), 'loc' => $careerIndexUrl,
'alternates' => $getAlternates($careerIndexUrl),
'lastmod' => now()->startOfMonth()->toAtomString(), 'lastmod' => now()->startOfMonth()->toAtomString(),
'changefreq' => 'monthly', 'changefreq' => 'monthly',
'priority' => '0.5', 'priority' => '0.5',
@@ -40,8 +66,10 @@ class SitemapController extends Controller
->where('is_homepage', false) ->where('is_homepage', false)
->get(); ->get();
foreach ($pages as $page) { foreach ($pages as $page) {
$pageUrl = url($page->slug);
$urls[] = [ $urls[] = [
'loc' => url($page->slug), 'loc' => $pageUrl,
'alternates' => $getAlternates($pageUrl),
'lastmod' => $page->updated_at->toAtomString(), 'lastmod' => $page->updated_at->toAtomString(),
'changefreq' => 'weekly', 'changefreq' => 'weekly',
'priority' => '0.7', 'priority' => '0.7',
@@ -52,8 +80,10 @@ class SitemapController extends Controller
if (class_exists(Blog::class)) { if (class_exists(Blog::class)) {
$posts = Blog::published()->get(); $posts = Blog::published()->get();
foreach ($posts as $post) { foreach ($posts as $post) {
$postUrl = route('blog.show', $post->slug);
$urls[] = [ $urls[] = [
'loc' => route('blog.show', $post->slug), 'loc' => $postUrl,
'alternates' => $getAlternates($postUrl),
'lastmod' => $post->updated_at->toAtomString(), 'lastmod' => $post->updated_at->toAtomString(),
'changefreq' => 'weekly', 'changefreq' => 'weekly',
'priority' => '0.6', 'priority' => '0.6',
@@ -65,8 +95,10 @@ class SitemapController extends Controller
if (class_exists(Product::class)) { if (class_exists(Product::class)) {
$products = Product::where('is_active', true)->get(); $products = Product::where('is_active', true)->get();
foreach ($products as $product) { foreach ($products as $product) {
$productUrl = route('products.show', $product->slug);
$urls[] = [ $urls[] = [
'loc' => route('products.show', $product->slug), 'loc' => $productUrl,
'alternates' => $getAlternates($productUrl),
'lastmod' => $product->updated_at->toAtomString(), 'lastmod' => $product->updated_at->toAtomString(),
'changefreq' => 'weekly', 'changefreq' => 'weekly',
'priority' => '0.7', 'priority' => '0.7',
+19 -11
View File
@@ -16,10 +16,25 @@ class SetLocale
*/ */
public function handle(Request $request, Closure $next): Response public function handle(Request $request, Closure $next): Response
{ {
// Session'dan locale'i al, yoksa varsayılan dil kodunu kullan // Aktif dilleri veritabanından kontrol et
$locale = session('locale'); if (function_exists('available_language_codes')) {
$availableLocales = available_language_codes();
} else {
$availableLocales = ['tr', 'en', 'de', 'ar', 'se', 'ru'];
}
// Query parametresinden al, yoksa session'dan al
$queryLocale = $request->query('locale') ?? $request->query('lang');
$locale = null;
if ($queryLocale && in_array($queryLocale, $availableLocales)) {
$locale = $queryLocale;
session(['locale' => $locale]);
} else {
$locale = session('locale');
}
// Eğer session'da locale yoksa, varsayılan dil kodunu kullan // Eğer locale hala atanmadıysa varsayılan dil kodunu kullan
if (!$locale) { if (!$locale) {
if (function_exists('default_language_code')) { if (function_exists('default_language_code')) {
$locale = default_language_code(); $locale = default_language_code();
@@ -28,14 +43,7 @@ class SetLocale
} }
} }
// Aktif dilleri veritabanından kontrol et // Locale'i aktif diller arasında kontrol et ve ata
if (function_exists('available_language_codes')) {
$availableLocales = available_language_codes();
} else {
$availableLocales = ['tr', 'en'];
}
// Locale'i aktif diller arasında kontrol et
if (in_array($locale, $availableLocales)) { if (in_array($locale, $availableLocales)) {
App::setLocale($locale); App::setLocale($locale);
} else { } else {
+1
View File
@@ -30,6 +30,7 @@ class CareerApplication extends Model
'internship_end_date', 'internship_end_date',
'internship_total_days', 'internship_total_days',
'github_repo', 'github_repo',
'github_username',
'certificate_code', 'certificate_code',
'transcript_markdown', 'transcript_markdown',
'notebook_supervisor_signed', 'notebook_supervisor_signed',
@@ -85,6 +85,13 @@ class AdminPanelProvider extends PanelProvider
->sort(100); // En sonda görünsün ->sort(100); // En sonda görünsün
})->toArray() })->toArray()
]) ])
->navigationItems([
\Filament\Navigation\NavigationItem::make('Admin Stajyer Paneli')
->url('https://truncgil.com/stajyer/admin/panel')
->openUrlInNewTab()
->icon('heroicon-o-academic-cap')
->sort(1000),
])
->assets([ ->assets([
\Filament\Support\Assets\Css::make('citrus', resource_path('css/citrus.css')), \Filament\Support\Assets\Css::make('citrus', resource_path('css/citrus.css')),
]); ]);
+37 -1
View File
@@ -63,10 +63,42 @@ abstract class StructuredData
$siteName = static::siteName(); $siteName = static::siteName();
$node = [ $node = [
'@type' => 'Organization', '@type' => 'ProfessionalService',
'@id' => $siteUrl . '#organization', '@id' => $siteUrl . '#organization',
'name' => $siteName, 'name' => $siteName,
'url' => $siteUrl, 'url' => $siteUrl,
'priceRange' => '$$',
'geo' => [
'@type' => 'GeoCoordinates',
'latitude' => 37.025704,
'longitude' => 37.296559,
],
'areaServed' => [
[
'@type' => 'AdministrativeArea',
'name' => 'Gaziantep',
],
[
'@type' => 'Country',
'name' => 'Turkey',
]
],
'knowsAbout' => [
'Yazılım Geliştirme',
'Web Tasarım',
'Mobil Uygulama Geliştirme',
'E-Ticaret Sistemleri',
'SEO Danışmanlığı',
'Gaziantep Yazılım Şirketleri'
],
'openingHoursSpecification' => [
[
'@type' => 'OpeningHoursSpecification',
'dayOfWeek' => ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
'opens' => '09:00',
'closes' => '18:00',
]
]
]; ];
$logo = setting('site_logo'); $logo = setting('site_logo');
@@ -93,6 +125,10 @@ abstract class StructuredData
$node['address'] = [ $node['address'] = [
'@type' => 'PostalAddress', '@type' => 'PostalAddress',
'streetAddress' => $address, 'streetAddress' => $address,
'addressLocality' => 'Şahinbey',
'addressRegion' => 'Gaziantep',
'postalCode' => '27190',
'addressCountry' => 'TR',
]; ];
} }
@@ -0,0 +1,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');
});
}
};
+6 -6
View File
@@ -1,38 +1,38 @@
@import url(https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@1,300;1,400;1,500;1,600;1,700); @import url(https://fonts.googleapis.com/css2?family=IBM+Plex+Serif:ital,wght@1,300;1,400;1,500;1,600;1,700&display=swap);
@font-face { @font-face {
font-family: 'Space Grotesk'; font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-SemiBold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-SemiBold.woff) format('woff'); src: url(../../fonts/space/SpaceGrotesk-SemiBold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-SemiBold.woff) format('woff');
font-weight: 600; font-weight: 600;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: 'Space Grotesk'; font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Light.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Light.woff) format('woff'); src: url(../../fonts/space/SpaceGrotesk-Light.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Light.woff) format('woff');
font-weight: 300; font-weight: 300;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: 'Space Grotesk'; font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Bold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Bold.woff) format('woff'); src: url(../../fonts/space/SpaceGrotesk-Bold.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Bold.woff) format('woff');
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: 'Space Grotesk'; font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Medium.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Medium.woff) format('woff'); src: url(../../fonts/space/SpaceGrotesk-Medium.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Medium.woff) format('woff');
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: 'Space Grotesk'; font-family: 'Space Grotesk';
src: url(../../fonts/space/SpaceGrotesk-Regular.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Regular.woff) format('woff'); src: url(../../fonts/space/SpaceGrotesk-Regular.woff2) format('woff2'), url(../../fonts/space/SpaceGrotesk-Regular.woff) format('woff');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
* { * {
word-spacing: normal !important word-spacing: normal !important
+10 -10
View File
@@ -3,70 +3,70 @@
src: url(../../fonts/urbanist/Urbanist-BoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-BoldItalic.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-BoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-BoldItalic.woff) format('woff');
font-weight: 700; font-weight: 700;
font-style: italic; font-style: italic;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBoldItalic.woff) format('woff');
font-weight: 600; font-weight: 600;
font-style: italic; font-style: italic;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Medium.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Medium.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-Medium.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Medium.woff) format('woff');
font-weight: 500; font-weight: 500;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-MediumItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-MediumItalic.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-MediumItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-MediumItalic.woff) format('woff');
font-weight: 500; font-weight: 500;
font-style: italic; font-style: italic;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-SemiBold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBold.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-SemiBold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-SemiBold.woff) format('woff');
font-weight: 600; font-weight: 600;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Italic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Italic.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-Italic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Italic.woff) format('woff');
font-weight: 400; font-weight: 400;
font-style: italic; font-style: italic;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Regular.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Regular.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-Regular.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Regular.woff) format('woff');
font-weight: 400; font-weight: 400;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-LightItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-LightItalic.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-LightItalic.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-LightItalic.woff) format('woff');
font-weight: 300; font-weight: 300;
font-style: italic; font-style: italic;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Light.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Light.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-Light.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Light.woff) format('woff');
font-weight: 300; font-weight: 300;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
@font-face { @font-face {
font-family: Urbanist; font-family: Urbanist;
src: url(../../fonts/urbanist/Urbanist-Bold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Bold.woff) format('woff'); src: url(../../fonts/urbanist/Urbanist-Bold.woff2) format('woff2'), url(../../fonts/urbanist/Urbanist-Bold.woff) format('woff');
font-weight: 700; font-weight: 700;
font-style: normal; font-style: normal;
font-display: block font-display: swap;
} }
* { * {
word-spacing: normal !important word-spacing: normal !important
+2 -2
View File
@@ -3,12 +3,12 @@
src:url(Unicons.woff2) format("woff2"),url(Unicons.woff) format("woff"); src:url(Unicons.woff2) format("woff2"),url(Unicons.woff) format("woff");
font-weight:400; font-weight:400;
font-style:normal; font-style:normal;
font-display:block font-display:swap;
} }
@font-face { @font-face {
font-family:Custom; font-family:Custom;
src:url(../custom/Custom.woff2) format("woff2"),url(../custom/Custom.woff) format("woff"); src:url(../custom/Custom.woff2) format("woff2"),url(../custom/Custom.woff) format("woff");
font-weight:400; font-weight:400;
font-style:normal; font-style:normal;
font-display:block font-display:swap;
} }
@@ -0,0 +1 @@
google-site-verification: googlef1M6GQLxVA5g7IaVY6kMQCoIrgADR6HjrOZu79CrXYc.html
+1 -31862
View File
File diff suppressed because one or more lines are too long
+5 -1
View File
@@ -1,2 +1,6 @@
User-agent: * User-agent: *
Disallow: Allow: /
Disallow: /admin/
Disallow: /stajyer/admin/
Sitemap: https://truncgil.com/sitemap.xml
@@ -1,157 +1 @@
<x-filament-widgets::widget> {{-- Bu bileşen görünümü devre dışı bırakılmıştır. --}}
<!-- DevExtreme Gantt CSS Dependencies -->
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx.light.css">
<link rel="stylesheet" href="https://cdn3.devexpress.com/jslib/23.2.5/css/dx-gantt.css">
<style>
.fi-wi-intern-gantt-chart .demo-container {
height: 560px;
background-color: #fff;
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(0, 0, 0, 0.05);
margin-top: 12px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.03);
}
.dark .fi-wi-intern-gantt-chart .demo-container {
background-color: #111827;
border-color: rgba(255, 255, 255, 0.05);
}
.fi-wi-intern-gantt-chart #gantt {
height: 100%;
width: 100%;
}
</style>
<x-filament::section class="fi-wi-intern-gantt-chart">
<x-slot name="heading">
<div class="flex items-center justify-between">
<span class="text-lg font-bold tracking-tight text-gray-950 dark:text-white">
Stajyer Gantt Şeması ve Takvimi
</span>
<span class="text-xs px-2.5 py-1 bg-emerald-50 text-emerald-600 dark:bg-emerald-950 dark:text-emerald-400 rounded-full font-semibold">
DevExtreme Görünümü
</span>
</div>
</x-slot>
@if(count($interns) === 0)
<div class="flex flex-col items-center justify-center p-8 text-center bg-gray-50 dark:bg-gray-900/40 rounded-xl border border-dashed border-gray-200 dark:border-gray-800">
<div class="p-3 bg-gray-100 dark:bg-gray-800 rounded-full mb-3 text-gray-400">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5m-9-6h.008v.008H12v-.008ZM12 15h.008v.008H12V15Zm0 2.25h.008v.008H12v-.008ZM9.75 15h.008v.008H9.75V15Zm0 2.25h.008v.008H9.75v-.008ZM7.5 15h.008v.008H7.5V15Zm0 2.25h.008v.008H7.5v-.008Zm6.75-4.5h.008v.008h-.008v-.008Zm0 2.25h.008v.008h-.008V15Zm0 2.25h.008v.008h-.008v-.008Zm2.25-4.5h.008v.008H16.5v-.008Zm0 2.25h.008v.008H16.5V15Z" />
</svg>
</div>
<p class="text-sm font-medium text-gray-500 dark:text-gray-400">
Tarihleri belirlenmiş onaylı stajyer bulunamadı.
</p>
</div>
@else
<div
x-data="{
internsData: @js($interns),
loadScript(src) {
return new Promise((resolve, reject) => {
if (window.jQuery && src.includes('jquery')) { resolve(); return; }
if (window.DevExpress && src.includes('dx.all')) { resolve(); return; }
if (window.DevExpress && window.DevExpress.ui && window.DevExpress.ui.dxGantt && src.includes('dx-gantt')) { resolve(); return; }
const existing = document.querySelector(`script[src='${src}']`);
if (existing) {
if (existing.readyState === 'loaded' || existing.readyState === 'complete') {
resolve();
} else {
existing.addEventListener('load', resolve);
existing.addEventListener('error', reject);
}
return;
}
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = resolve;
script.onerror = () => reject(new Error('Failed to load script: ' + src));
document.head.appendChild(script);
});
},
showError(msg) {
const errDiv = document.createElement('div');
errDiv.style.color = '#ef4444';
errDiv.style.padding = '20px';
errDiv.style.fontFamily = 'monospace';
errDiv.style.fontSize = '13px';
errDiv.innerHTML = '<strong>Gantt Load Error:</strong><br>' + msg;
this.$refs.ganttContainer.innerHTML = '';
this.$refs.ganttContainer.appendChild(errDiv);
},
async init() {
try {
await this.loadScript('https://code.jquery.com/jquery-3.7.1.min.js').catch(() => { throw new Error('jQuery failed to load'); });
await this.loadScript('https://cdn3.devexpress.com/jslib/23.2.5/js/dx.all.js').catch(() => { throw new Error('dx.all.js failed to load'); });
await this.loadScript('https://cdn3.devexpress.com/jslib/23.2.5/js/dx-gantt.min.js').catch(() => { throw new Error('dx-gantt.min.js failed to load'); });
if (window.jQuery && window.jQuery.fn.dxGantt) {
const formattedData = this.internsData.map(intern => ({
id: intern.id,
parentId: 0,
title: intern.name,
start: new Date(intern.raw_start),
end: new Date(intern.raw_end),
progress: 100
}));
jQuery(this.$refs.ganttContainer).dxGantt({
tasks: {
dataSource: formattedData,
},
editing: {
enabled: false,
},
validation: {
autoUpdateParentTasks: true,
},
toolbar: {
items: [
'collapseAll',
'expandAll',
'separator',
'zoomIn',
'zoomOut',
],
},
columns: [{
dataField: 'title',
caption: 'Stajyer Adı',
width: 250,
}, {
dataField: 'start',
caption: 'Başlangıç Tarihi',
dataType: 'date',
format: 'dd.MM.yyyy',
width: 120,
}, {
dataField: 'end',
caption: 'Bitiş Tarihi',
dataType: 'date',
format: 'dd.MM.yyyy',
width: 120,
}],
scaleType: 'days',
taskListWidth: 490,
});
} else {
throw new Error('DevExpress Gantt component jQuery plugin is not defined after loading scripts.');
}
} catch (e) {
console.error(e);
this.showError(e.message);
}
}
}"
class="dx-viewport demo-container"
>
<div id="gantt" x-ref="ganttContainer" style="height: 100%; width: 100%;"></div>
</div>
@endif
</x-filament::section>
</x-filament-widgets::widget>
@@ -655,15 +655,23 @@
<div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 mb-6"> <div class="p-5 rounded-2xl border border-slate-100 bg-slate-50/50 mb-6">
<form action="{{ route('intern.save-github') }}" method="POST" class="space-y-4"> <form action="{{ route('intern.save-github') }}" method="POST" class="space-y-4">
@csrf @csrf
<div> <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<label for="github_repo" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Public Depo URL'si</label> <div>
<div class="flex flex-col sm:flex-row gap-3"> <label for="github_repo" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Public Depo URL'si</label>
<input type="url" name="github_repo" id="github_repo" placeholder="https://github.com/kullaniciadi/depo-adi" value="{{ $intern->github_repo }}" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" /> <input type="url" name="github_repo" id="github_repo" placeholder="https://github.com/kullaniciadi/depo-adi" value="{{ $intern->github_repo }}" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
<button type="submit" class="px-5 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all shadow-md shadow-blue-500/10 flex-shrink-0">
{{ $intern->github_repo ? 'Güncelle' : 'Kaydet' }}
</button>
</div> </div>
<span class="block text-xs text-slate-400 mt-2"><i class="uil uil-info-circle text-blue-600"></i> Deponuzun <strong>public (herkese açık)</strong> olması gerekmektedir.</span> <div>
<label for="github_username" class="block text-xs font-extrabold text-slate-500 uppercase tracking-wider mb-2">GitHub Kullanıcı Adınız (Opsiyonel)</label>
<input type="text" name="github_username" id="github_username" placeholder="GitHub kullanıcı adınız (örn: Emresatil)" value="{{ $intern->github_username }}" class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 focus:outline-none text-sm font-semibold text-slate-700 bg-white transition-all" />
</div>
</div>
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pt-2 border-t border-slate-100">
<span class="text-xs text-slate-400 leading-normal max-w-xl">
<i class="uil uil-info-circle text-blue-600 text-sm"></i> Deponuzun <strong>public</strong> olması gerekir. Birden fazla kişi aynı projede çalışıyorsa, kendi commitlerinizi süzmek için kullanıcı adınızı yazmanız önerilir.
</span>
<button type="submit" class="px-5 py-3 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-sm transition-all shadow-md shadow-blue-500/10 flex-shrink-0 w-full sm:w-auto">
Bilgileri Kaydet
</button>
</div> </div>
</form> </form>
</div> </div>
@@ -684,6 +692,7 @@
$dayDate = $d['date']; $dayDate = $d['date'];
$dayDateFormatted = $d['formatted_date']; $dayDateFormatted = $d['formatted_date'];
$hasSaved = isset($savedEntries[$dayNum]) && trim($savedEntries[$dayNum]->content) !== ''; $hasSaved = isset($savedEntries[$dayNum]) && trim($savedEntries[$dayNum]->content) !== '';
$isApproved = isset($savedEntries[$dayNum]) && $savedEntries[$dayNum]->supervisor_approved;
@endphp @endphp
<button type="button" <button type="button"
onclick="selectNotebookDay({{ $idx }})" onclick="selectNotebookDay({{ $idx }})"
@@ -692,13 +701,19 @@
data-date="{{ $dayDate }}" data-date="{{ $dayDate }}"
data-formatted-date="{{ $dayDateFormatted }}" data-formatted-date="{{ $dayDateFormatted }}"
data-saved-content="{{ $savedEntries[$dayNum]->content ?? '' }}" data-saved-content="{{ $savedEntries[$dayNum]->content ?? '' }}"
data-approved="{{ $isApproved ? '1' : '0' }}"
class="notebook-day-btn w-full flex items-center justify-between p-3 rounded-2xl border text-left transition-all @if($idx === 0) border-blue-200 bg-blue-50/50 @else border-slate-100 bg-white hover:bg-slate-50 @endif"> class="notebook-day-btn w-full flex items-center justify-between p-3 rounded-2xl border text-left transition-all @if($idx === 0) border-blue-200 bg-blue-50/50 @else border-slate-100 bg-white hover:bg-slate-50 @endif">
<div> <div>
<span class="block text-xs font-extrabold @if($idx === 0) text-blue-700 @else text-slate-800 @endif">{{ $dayNum }}. Gün</span> <span class="block text-xs font-extrabold @if($idx === 0) text-blue-700 @else text-slate-800 @endif">{{ $dayNum }}. Gün</span>
<span class="block text-[10px] text-slate-400 mt-0.5">{{ $dayDateFormatted }}</span> <span class="block text-[10px] text-slate-400 mt-0.5">{{ $dayDateFormatted }}</span>
</div> </div>
<span id="badge-day-{{ $idx }}" class="inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase @if($hasSaved) bg-green-50 text-green-700 border border-green-200 @else bg-slate-50 text-slate-400 border border-slate-200 @endif"> <span id="badge-day-{{ $idx }}" class="inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase
{{ $hasSaved ? 'DOLU' : 'BOŞ' }} @if($isApproved) bg-green-500 text-white border border-green-600
@elseif($hasSaved) bg-amber-50 text-amber-700 border border-amber-200
@else bg-slate-50 text-slate-400 border border-slate-200 @endif">
@if($isApproved) ONAYLANDI
@elseif($hasSaved) DOLU
@else BOŞ @endif
</span> </span>
</button> </button>
@endforeach @endforeach
@@ -708,17 +723,25 @@
<div class="lg:col-span-8 space-y-4"> <div class="lg:col-span-8 space-y-4">
<div class="bg-white border border-slate-100 rounded-2xl p-5 shadow-sm"> <div class="bg-white border border-slate-100 rounded-2xl p-5 shadow-sm">
<div class="flex items-center justify-between pb-3 border-b border-slate-100 mb-4"> <div class="flex items-center justify-between pb-3 border-b border-slate-100 mb-4">
<div> <div class="flex items-center">
<span id="editor-day-title" class="text-sm font-extrabold text-blue-600 bg-blue-50 px-3 py-1 rounded-full">1. Gün</span> <span id="editor-day-title" class="text-sm font-extrabold text-blue-600 bg-blue-50 px-3 py-1 rounded-full">1. Gün</span>
<span id="editor-day-date" class="text-xs font-semibold text-slate-400 ml-2"></span> <span id="editor-day-date" class="text-xs font-semibold text-slate-400 ml-2"></span>
<span id="editor-day-status" class="ml-2"></span>
</div> </div>
@if($intern->github_repo) <div class="flex items-center gap-2">
<button type="button" onclick="fillFromGithub()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-900 text-white font-extrabold rounded-lg text-[10px] transition-all flex items-center gap-1.5 shadow-sm"> @if($intern->github_repo)
<i class="uil uil-github text-xs"></i> <button type="button" onclick="fillFromGithub()" class="px-3 py-1.5 bg-slate-800 hover:bg-slate-900 text-white font-extrabold rounded-lg text-[10px] transition-all flex items-center gap-1.5 shadow-sm">
<span>Git Commitlerini Çek</span> <i class="uil uil-github text-xs"></i>
</button> <span>Git Commitlerini Çek</span>
@endif </button>
@endif
<button type="button" onclick="printActiveDay()" class="px-3 py-1.5 bg-green-600 hover:bg-green-700 text-white font-extrabold rounded-lg text-[10px] transition-all flex items-center gap-1.5 shadow-sm">
<i class="uil uil-print text-xs"></i>
<span>Yazdır</span>
</button>
</div>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
@@ -733,6 +756,19 @@
</div> </div>
</div> </div>
<div id="git-commits-fallback-container" class="hidden p-4 bg-slate-50 border border-slate-200/60 rounded-2xl text-xs space-y-3">
<div class="flex justify-between items-center pb-2 border-b border-slate-200">
<span class="font-extrabold text-slate-700 flex items-center gap-1.5">
<i class="uil uil-info-circle text-blue-600 text-sm"></i>
<span>Seçilen güne ait otomatik commit bulunamadı. Son 10 commit'iniz:</span>
</span>
<button type="button" onclick="hideFallbackCommits()" class="text-slate-400 hover:text-slate-600 font-extrabold">Kapat</button>
</div>
<div id="git-commits-fallback-list" class="space-y-2 max-h-[150px] overflow-y-auto pr-1 no-scrollbar">
<!-- items dynamically populated -->
</div>
</div>
<div class="flex justify-between items-center"> <div class="flex justify-between items-center">
<div id="save-status" class="text-xs font-semibold text-slate-400"></div> <div id="save-status" class="text-xs font-semibold text-slate-400"></div>
<button type="button" id="save-btn" onclick="saveActiveDay()" class="px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs transition-all shadow-md shadow-blue-500/10"> <button type="button" id="save-btn" onclick="saveActiveDay()" class="px-5 py-2.5 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-xl text-xs transition-all shadow-md shadow-blue-500/10">
@@ -794,6 +830,39 @@
</div> </div>
</div> </div>
<!-- Author Selection Modal -->
<div id="author-modal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4">
<div class="bg-white rounded-3xl p-6 max-w-md w-full shadow-2xl border border-slate-100/50 transform scale-95 opacity-0 transition-all duration-300" id="author-modal-card">
<div class="flex justify-between items-start mb-4">
<div>
<h3 class="text-lg font-bold text-slate-800 flex items-center gap-2">
<i class="uil uil-users-alt text-blue-600"></i>
<span>GitHub Yazar Seçimi</span>
</h3>
<p class="text-xs text-slate-400 mt-1">Bu günün commitlerinde birden fazla yazar tespit edildi. Hangisi sizsiniz?</p>
</div>
<button onclick="closeAuthorModal()" class="text-slate-400 hover:text-slate-600">
<i class="uil uil-multiply text-xl"></i>
</button>
</div>
<div id="author-list-container" class="space-y-2 mb-4 max-h-[220px] overflow-y-auto pr-1 no-scrollbar">
<!-- Dynamically populated author options -->
</div>
<div class="flex items-center gap-2 mb-5 bg-slate-50 p-3 rounded-xl border border-slate-200/60">
<input type="checkbox" id="save-selected-author-pref" class="rounded text-blue-600 focus:ring-blue-500 w-4 h-4" checked />
<label for="save-selected-author-pref" class="text-xs text-slate-600 font-bold select-none cursor-pointer">
Seçtiğim kullanıcı adını profilime kaydet ve varsayılan yap
</label>
</div>
<div class="flex justify-end gap-2">
<button type="button" onclick="closeAuthorModal()" class="px-4 py-2 bg-slate-100 hover:bg-slate-200 text-slate-600 font-bold rounded-xl text-xs transition-all">İptal</button>
</div>
</div>
</div>
@push('styles') @push('styles')
<link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" /> <link href="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.snow.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script> <script src="https://cdn.jsdelivr.net/npm/quill@2.0.2/dist/quill.js"></script>
@@ -855,56 +924,218 @@
// Handle initial state of date logic // Handle initial state of date logic
calculateEndDateFrontend(); calculateEndDateFrontend();
// Fetch Github commits in memory if repo is set
fetchAllCommitsInMemory();
// Select first day of the notebook workspace on load // Select first day of the notebook workspace on load
selectNotebookDay(0); selectNotebookDay(0);
}); });
const githubRepoUrl = @json($intern->github_repo); const githubRepoUrl = @json($intern->github_repo);
let allGithubCommits = {}; let githubUsername = @json($intern->github_username);
let activeDayIdx = 0; let activeDayIdx = 0;
function fetchAllCommitsInMemory() { let activeCommitsToProcess = [];
let activeCommitsDateVal = '';
function openAuthorModal(uniqueAuthors) {
const modal = document.getElementById('author-modal');
const card = document.getElementById('author-modal-card');
const container = document.getElementById('author-list-container');
if (!modal || !card || !container) return;
container.innerHTML = '';
// 1. Add "Hepsi" (All commits) option
const allBtn = document.createElement('button');
allBtn.type = 'button';
allBtn.className = "w-full p-3 rounded-xl border border-slate-200 hover:border-blue-400 hover:bg-blue-50/20 text-left transition-all font-extrabold text-xs text-slate-700 flex items-center justify-between";
allBtn.innerHTML = `
<span>Tüm Commitler (Filtreleme Yapma)</span>
<i class="uil uil-angle-right text-base text-slate-400"></i>
`;
allBtn.addEventListener('click', () => {
insertCommitsForAuthor(null);
});
container.appendChild(allBtn);
// 2. Add each unique author option
uniqueAuthors.forEach(author => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = "w-full p-3 rounded-xl border border-slate-200 hover:border-blue-400 hover:bg-blue-50/20 text-left transition-all font-extrabold text-xs text-slate-700 flex items-center justify-between mt-2";
btn.innerHTML = `
<span>${author}</span>
<i class="uil uil-angle-right text-base text-slate-400"></i>
`;
btn.addEventListener('click', () => {
insertCommitsForAuthor(author);
});
container.appendChild(btn);
});
modal.classList.remove('hidden');
setTimeout(() => {
card.classList.remove('scale-95', 'opacity-0');
card.classList.add('scale-100', 'opacity-100');
}, 10);
}
function closeAuthorModal() {
const modal = document.getElementById('author-modal');
const card = document.getElementById('author-modal-card');
if (!modal || !card) return;
card.classList.remove('scale-100', 'opacity-100');
card.classList.add('scale-95', 'opacity-0');
setTimeout(() => {
modal.classList.add('hidden');
}, 300);
}
function insertCommitsForAuthor(author) {
closeAuthorModal();
const savePref = document.getElementById('save-selected-author-pref');
if (author && savePref && savePref.checked) {
saveGithubUsernamePreference(author);
}
let dayCommits = activeCommitsToProcess;
if (author) {
dayCommits = activeCommitsToProcess.filter(item => {
const commitAuthorName = item.commit.author.name;
const commitAuthorLogin = item.author ? item.author.login : null;
return (commitAuthorLogin && commitAuthorLogin.toLowerCase() === author.toLowerCase()) ||
(commitAuthorName && commitAuthorName.toLowerCase() === author.toLowerCase());
});
}
renderCommitsToEditor(dayCommits);
}
function saveGithubUsernamePreference(username) {
if (!githubRepoUrl) return; if (!githubRepoUrl) return;
let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, ''); fetch("{{ route('intern.save-github') }}", {
repoClean = repoClean.replace(/\/$/, ''); method: "POST",
const parts = repoClean.split('/'); headers: {
if (parts.length < 2) return; "Content-Type": "application/json",
"X-CSRF-TOKEN": "{{ csrf_token() }}"
const owner = parts[0]; },
const repo = parts[1].replace(/\.git$/i, ''); body: JSON.stringify({
github_repo: githubRepoUrl,
fetch(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=100`) github_username: username
.then(response => {
if (!response.ok) throw new Error('API Error');
return response.json();
}) })
.then(commits => { })
if (!Array.isArray(commits)) return; .then(response => response.json())
commits.forEach(item => { .then(data => {
const dateStr = item.commit.author.date; if (data.success) {
if (dateStr) { githubUsername = username;
const d = new Date(dateStr); const usernameInput = document.getElementById('github_username');
const formatter = new Intl.DateTimeFormat('fr-CA', { if (usernameInput) {
timeZone: 'Europe/Istanbul', usernameInput.value = username;
year: 'numeric', }
month: '2-digit', }
day: '2-digit' })
}); .catch(err => {
const dateOnly = formatter.format(d); console.warn("Kullanıcı adı tercihi kaydedilemedi.", err);
if (!allGithubCommits[dateOnly]) { });
allGithubCommits[dateOnly] = []; }
}
allGithubCommits[dateOnly].push(item); function renderCommitsToEditor(commitsList) {
} if (commitsList.length === 0) {
}); alert("Bu yazar için seçilen güne ait commit bulunamadı.");
}) return;
.catch(err => { }
console.warn('Commits could not be pre-loaded in memory.', err);
let commitHtml = "<ul>";
const sorted = [...commitsList].reverse();
sorted.forEach(c => {
const msg = c.commit.message;
const sha = c.sha.substring(0, 7);
const authorDate = new Date(c.commit.author.date);
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
});
commitHtml += "</ul>";
const currentHtml = quill.root.innerHTML.trim();
const headerHtml = "<p><strong>Git Commit Mesajları:</strong></p>";
let newHtml = "";
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
newHtml = currentHtml + "<br><br>" + headerHtml + commitHtml;
} else {
newHtml = headerHtml + commitHtml;
}
quill.setContents([]);
quill.clipboard.dangerouslyPasteHTML(newHtml);
}
function hideFallbackCommits() {
const container = document.getElementById('git-commits-fallback-container');
if (container) {
container.classList.add('hidden');
}
}
function formatDateToIstanbul(d) {
try {
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: 'Europe/Istanbul',
year: 'numeric',
month: '2-digit',
day: '2-digit'
}); });
const parts = formatter.formatToParts(d);
const year = parts.find(p => p.type === 'year').value;
const month = parts.find(p => p.type === 'month').value;
const day = parts.find(p => p.type === 'day').value;
return `${year}-${month}-${day}`;
} catch (e) {
return d.toISOString().split('T')[0];
}
}
function updateEditorStatusBadge(isApproved, hasSaved) {
const statusSpan = document.getElementById('editor-day-status');
if (!statusSpan) return;
if (isApproved) {
statusSpan.textContent = 'ONAYLANDI';
statusSpan.className = 'inline-flex px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-green-500 text-white border border-green-600 ml-2';
} else if (hasSaved) {
statusSpan.textContent = 'ONAY BEKLİYOR';
statusSpan.className = 'inline-flex px-2 py-0.5 rounded-full text-[10px] font-extrabold uppercase bg-amber-50 text-amber-700 border border-amber-200 ml-2';
} else {
statusSpan.textContent = '';
statusSpan.className = 'hidden';
}
}
function printActiveDay() {
const btn = document.getElementById(`btn-day-${activeDayIdx}`);
if (!btn) return;
const dayNum = btn.getAttribute('data-day-num');
window.open(`{{ route('intern.print-journal') }}?size=a4&day=${dayNum}`, '_blank');
}
function appendCommitToQuill(sha, time, msg) {
const escapedMsg = msg.replace(/</g, "&lt;").replace(/>/g, "&gt;");
const commitHtml = `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${escapedMsg}</li>`;
const currentHtml = quill.root.innerHTML.trim();
const hasHeader = currentHtml.includes("Git Commit Mesajları:");
const headerHtml = hasHeader ? "" : "<p><strong>Git Commit Mesajları:</strong></p>";
let newHtml = "";
if (currentHtml !== "" && currentHtml !== "<p><br></p>") {
newHtml = currentHtml + "<br><br>" + headerHtml + "<ul>" + commitHtml + "</ul>";
} else {
newHtml = headerHtml + "<ul>" + commitHtml + "</ul>";
}
quill.setContents([]);
quill.clipboard.dangerouslyPasteHTML(newHtml);
} }
function selectNotebookDay(idx) { function selectNotebookDay(idx) {
@@ -934,12 +1165,20 @@
const dateVal = newBtn.getAttribute('data-date'); const dateVal = newBtn.getAttribute('data-date');
const dateFormatted = newBtn.getAttribute('data-formatted-date'); const dateFormatted = newBtn.getAttribute('data-formatted-date');
const savedContent = newBtn.getAttribute('data-saved-content'); const savedContent = newBtn.getAttribute('data-saved-content');
const isApproved = newBtn.getAttribute('data-approved') === '1';
document.getElementById('editor-day-title').textContent = `${dayNum}. Gün`; document.getElementById('editor-day-title').textContent = `${dayNum}. Gün`;
document.getElementById('editor-day-date').textContent = dateFormatted; document.getElementById('editor-day-date').textContent = dateFormatted;
quill.root.innerHTML = savedContent || ''; quill.setContents([]);
if (savedContent) {
quill.clipboard.dangerouslyPasteHTML(savedContent);
}
document.getElementById('save-status').textContent = ''; document.getElementById('save-status').textContent = '';
hideFallbackCommits();
const hasSaved = savedContent && savedContent.trim() !== '' && savedContent !== '<p><br></p>';
updateEditorStatusBadge(isApproved, hasSaved);
// Future validation // Future validation
const dateParts = dateVal.split('-'); const dateParts = dateVal.split('-');
@@ -979,34 +1218,166 @@
if (!btn) return; if (!btn) return;
const dateVal = btn.getAttribute('data-date'); const dateVal = btn.getAttribute('data-date');
const dayCommits = allGithubCommits[dateVal] || []; const formattedDate = btn.getAttribute('data-formatted-date');
if (dayCommits.length === 0) { if (!githubRepoUrl) {
alert("Bu staj gününe ait GitHub commit'i bulunamadı. Lütfen commit attığınızdan ve tarihin doğru olduğundan emin olun."); alert("Lütfen önce GitHub depo URL'nizi kaydedin.");
return; return;
} }
let commitHtml = "<ul>"; let repoClean = githubRepoUrl.replace(/https?:\/\/(www\.)?github\.com\//i, '');
const sorted = [...dayCommits].reverse(); repoClean = repoClean.replace(/\/$/, '');
sorted.forEach(c => { const parts = repoClean.split('/');
const msg = c.commit.message; if (parts.length < 2) {
const sha = c.sha.substring(0, 7); alert("Geçersiz GitHub URL'si.");
const authorDate = new Date(c.commit.author.date); return;
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
commitHtml += `<li><strong>[${time}]</strong> (<em>${sha}</em>) ${msg}</li>`;
});
commitHtml += "</ul>";
const currentHtml = quill.root.innerHTML.trim();
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;
} }
const owner = parts[0];
const repo = parts[1].replace(/\.git$/i, '');
let branch = '';
// Parse branch if /tree/{branch} or /commits/{branch} is used
if (parts.length >= 4 && (parts[2] === 'tree' || parts[2] === 'commits')) {
branch = parts.slice(3).join('/');
}
const gitBtn = document.querySelector('button[onclick="fillFromGithub()"]');
const originalContent = gitBtn.innerHTML;
gitBtn.disabled = true;
gitBtn.innerHTML = `<i class="uil uil-spinner text-xs animate-spin"></i> <span>Çekiliyor...</span>`;
hideFallbackCommits();
fetch(`https://api.github.com/repos/${owner}/${repo}/commits?per_page=100${branch ? `&sha=${branch}` : ''}`)
.then(response => {
if (!response.ok) {
if (response.status === 404) {
throw new Error("Depo bulunamadı veya herkese açık (public) değil.");
} else if (response.status === 403) {
throw new Error("GitHub API istek limitine ulaşıldı. Lütfen daha sonra tekrar deneyin.");
} else {
throw new Error(`GitHub API Hatası (Kod: ${response.status})`);
}
}
return response.json();
})
.then(commits => {
if (!Array.isArray(commits)) {
throw new Error("Geçersiz API yanıtı.");
}
// Filter commits by the selected date
const dayCommits = commits.filter(item => {
const dateStr = item.commit.author.date;
if (!dateStr) return false;
const d = new Date(dateStr);
const dateOnly = formatDateToIstanbul(d);
return dateOnly === dateVal;
});
if (dayCommits.length === 0) {
// No commits found for this date. Show fallback list of latest 10 commits.
const fallbackContainer = document.getElementById('git-commits-fallback-container');
const fallbackList = document.getElementById('git-commits-fallback-list');
if (fallbackContainer && fallbackList) {
fallbackList.innerHTML = '';
const latestCommits = commits.slice(0, 10);
if (latestCommits.length === 0) {
fallbackList.innerHTML = `<p class="text-slate-400 italic">Depoda hiç commit bulunamadı.</p>`;
} else {
latestCommits.forEach(c => {
const msg = c.commit.message;
const sha = c.sha.substring(0, 7);
const authorDate = new Date(c.commit.author.date);
const time = authorDate.toLocaleTimeString('tr-TR', { timeZone: 'Europe/Istanbul', hour: '2-digit', minute: '2-digit' });
const commitDateOnly = formatDateToIstanbul(authorDate);
// format date to d.m.Y
const [cy, cm, cd] = commitDateOnly.split('-');
const formattedCommitDate = `${cd}.${cm}.${cy}`;
const row = document.createElement('div');
row.className = "flex items-center justify-between gap-3 p-2 hover:bg-slate-100 rounded-lg border border-slate-100 transition-colors";
const infoDiv = document.createElement('div');
infoDiv.className = "flex-grow min-w-0";
const timeSpan = document.createElement('span');
timeSpan.className = "font-bold text-slate-700 select-all";
timeSpan.textContent = `[${formattedCommitDate} ${time}] `;
const shaSpan = document.createElement('span');
shaSpan.className = "text-slate-500 font-semibold select-all";
shaSpan.textContent = `(${sha}) `;
const msgSpan = document.createElement('span');
msgSpan.className = "text-slate-600 truncate block sm:inline-block max-w-full font-medium ml-1";
msgSpan.textContent = msg;
infoDiv.appendChild(timeSpan);
infoDiv.appendChild(shaSpan);
infoDiv.appendChild(msgSpan);
const addBtn = document.createElement('button');
addBtn.type = 'button';
addBtn.className = "px-2 py-1 bg-blue-50 text-blue-600 hover:bg-blue-600 hover:text-white font-extrabold rounded transition-all flex-shrink-0 text-[10px]";
addBtn.textContent = "Ekle";
addBtn.addEventListener('click', () => {
appendCommitToQuill(sha, `${formattedCommitDate} ${time}`, msg);
});
row.appendChild(infoDiv);
row.appendChild(addBtn);
fallbackList.appendChild(row);
});
}
fallbackContainer.classList.remove('hidden');
alert(`Bu staj gününe (${formattedDate}) ait otomatik GitHub commit'i bulunamadı. Son 10 commit'iniz aşağıda listelenmiştir, rapora eklemek istediklerinizi seçebilirsiniz.`);
} else {
alert(`Bu staj gününe (${formattedDate}) ait GitHub commit'i bulunamadı. Lütfen commit attığınızdan ve tarihin doğru olduğundan emin olun.`);
}
return;
}
// Store these commits in memory for processing
activeCommitsToProcess = dayCommits;
activeCommitsDateVal = dateVal;
// Get unique authors
const authorsSet = new Set();
dayCommits.forEach(item => {
const login = item.author ? item.author.login : null;
const name = item.commit.author.name;
if (login) authorsSet.add(login);
if (name) authorsSet.add(name);
});
const uniqueAuthors = Array.from(authorsSet);
// If there are multiple authors
if (uniqueAuthors.length > 1) {
// If we have a saved githubUsername that matches one of the unique authors
const matchedAuthor = uniqueAuthors.find(a => githubUsername && a.toLowerCase() === githubUsername.toLowerCase());
if (matchedAuthor) {
// Automatically filter and insert without modal
insertCommitsForAuthor(matchedAuthor);
} else {
// Show modal to choose
openAuthorModal(uniqueAuthors);
}
} else {
// Only 1 author, insert directly
renderCommitsToEditor(dayCommits);
}
})
.catch(err => {
alert("Commitler çekilirken hata oluştu: " + err.message);
})
.finally(() => {
gitBtn.disabled = false;
gitBtn.innerHTML = originalContent;
});
} }
function saveActiveDay() { function saveActiveDay() {
@@ -1043,17 +1414,21 @@
statusText.className = "text-xs font-semibold text-green-600"; statusText.className = "text-xs font-semibold text-green-600";
btn.setAttribute('data-saved-content', contentVal); btn.setAttribute('data-saved-content', contentVal);
btn.setAttribute('data-approved', '0');
const badge = document.getElementById(`badge-day-${activeDayIdx}`); const badge = document.getElementById(`badge-day-${activeDayIdx}`);
const hasContent = contentVal.trim() !== '' && contentVal !== '<p><br></p>';
if (badge) { if (badge) {
if (contentVal.trim() !== '') { if (hasContent) {
badge.textContent = 'DOLU'; badge.textContent = 'DOLU';
badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-green-50 text-green-700 border border-green-200"; badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-amber-50 text-amber-700 border border-amber-200";
} else { } else {
badge.textContent = 'BOŞ'; badge.textContent = 'BOŞ';
badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-slate-50 text-slate-400 border border-slate-200"; badge.className = "inline-flex px-2 py-0.5 rounded-lg text-[9px] font-extrabold uppercase bg-slate-50 text-slate-400 border border-slate-200";
} }
} }
updateEditorStatusBadge(false, hasContent);
} else { } else {
statusText.textContent = data.message || "Kaydedilemedi."; statusText.textContent = data.message || "Kaydedilemedi.";
statusText.className = "text-xs font-semibold text-red-600"; statusText.className = "text-xs font-semibold text-red-600";
+2 -2
View File
@@ -331,8 +331,8 @@
<span>Panele Dön</span> <span>Panele Dön</span>
</a> </a>
<div style="border-left: 1px solid #e2e8f0; margin: 0 5px; height: 35px;"></div> <div style="border-left: 1px solid #e2e8f0; margin: 0 5px; height: 35px;"></div>
<a href="?size=a4{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}" class="btn {{ $size === 'a4' ? 'btn-primary' : 'btn-secondary' }}">A4 Boyutu</a> <a href="?size=a4{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}{{ request()->has('day') ? '&day='.request('day') : '' }}" class="btn {{ $size === 'a4' ? 'btn-primary' : 'btn-secondary' }}">A4 Boyutu</a>
<a href="?size=a5{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}" class="btn {{ $size === 'a5' ? 'btn-primary' : 'btn-secondary' }}">A5 Boyutu</a> <a href="?size=a5{{ request()->has('intern_id') ? '&intern_id='.request('intern_id') : '' }}{{ request()->has('day') ? '&day='.request('day') : '' }}" class="btn {{ $size === 'a5' ? 'btn-primary' : 'btn-secondary' }}">A5 Boyutu</a>
<button onclick="window.print()" class="btn btn-primary"> <button onclick="window.print()" class="btn btn-primary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg> <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
<span>Yazdır / PDF Kaydet</span> <span>Yazdır / PDF Kaydet</span>
+2 -1
View File
@@ -14,7 +14,8 @@
<link rel="preload" href="{{ asset('html/assets/css/fonts/urbanist.css') }}" as="style" onload="this.rel='stylesheet'"> <link rel="preload" href="{{ asset('html/assets/css/fonts/urbanist.css') }}" as="style" onload="this.rel='stylesheet'">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate"> <link rel="preload" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" rel="stylesheet"></noscript>
@stack('styles') @stack('styles')
<style> <style>
.navbar.navbar-light.fixed .btn:not(.btn-expand):not(.btn-gradient) { .navbar.navbar-light.fixed .btn:not(.btn-expand):not(.btn-gradient) {
+33 -3
View File
@@ -17,16 +17,35 @@ $favicon_path = setting('site_favicon');
} }
@endphp @endphp
@php @php
$seo_canonical = $meta['canonical'] ?? url()->current(); $seo_canonical = $meta['canonical'] ?? null;
if (!$seo_canonical) {
$seo_canonical = url()->current();
$localeQuery = request()->query('locale') ?? request()->query('lang');
if ($localeQuery && function_exists('available_language_codes') && in_array($localeQuery, available_language_codes())) {
$seo_canonical .= '?locale=' . $localeQuery;
}
}
$seo_robots = $meta['robots'] ?? 'index, follow'; $seo_robots = $meta['robots'] ?? 'index, follow';
$seo_og_type = $meta['og_type'] ?? 'website'; $seo_og_type = $meta['og_type'] ?? 'website';
$seo_og_locale = str_replace('_', '-', $meta['locale'] ?? app()->getLocale()); $seo_og_locale = str_replace('_', '-', $meta['locale'] ?? app()->getLocale());
$activeLanguages = class_exists(\App\Models\Language::class)
? \App\Models\Language::where('is_active', true)->get()
: collect([]);
$currentUrl = url()->current();
@endphp @endphp
<title>{{ $seo_title }}</title> <title>{{ $seo_title }}</title>
<meta name="description" content="{{ $seo_description }}"> <meta name="description" content="{{ $seo_description }}">
<meta name="robots" content="{{ $seo_robots }}"> <meta name="robots" content="{{ $seo_robots }}">
<link rel="canonical" href="{{ $seo_canonical }}"> <link rel="canonical" href="{{ $seo_canonical }}">
@foreach($activeLanguages as $lang)
<link rel="alternate" hreflang="{{ $lang->code }}" href="{{ $currentUrl . '?locale=' . $lang->code }}">
@endforeach
@if($activeLanguages->count() > 0)
<link rel="alternate" hreflang="x-default" href="{{ $currentUrl }}">
@endif
<!-- Open Graph / Facebook --> <!-- Open Graph / Facebook -->
<meta property="og:type" content="{{ $seo_og_type }}"> <meta property="og:type" content="{{ $seo_og_type }}">
<meta property="og:url" content="{{ $seo_canonical }}"> <meta property="og:url" content="{{ $seo_canonical }}">
@@ -58,6 +77,15 @@ $favicon_path = setting('site_favicon');
<link rel="icon" href="{{ $favicon_path ? (str_starts_with($favicon_path, 'http') ? $favicon_path : asset($favicon_path)) : asset('assets/img/favicon.png') }}"> <link rel="icon" href="{{ $favicon_path ? (str_starts_with($favicon_path, 'http') ? $favicon_path : asset($favicon_path)) : asset('assets/img/favicon.png') }}">
@stack('head') @stack('head')
@if(request()->routeIs('homepage'))
@php
$hero_bg_preload = setting('hero_bg') ?: './assets/img/photos/bg16.webp';
if ($hero_bg_preload && !str_starts_with($hero_bg_preload, 'http')) {
$hero_bg_preload = asset($hero_bg_preload);
}
@endphp
<link rel="preload" href="{{ $hero_bg_preload }}" as="image" fetchpriority="high">
@endif
<link rel="stylesheet" href="{{ asset('html/style.css') }}"> <link rel="stylesheet" href="{{ asset('html/style.css') }}">
<link rel="preload" href="{{ asset('assets/css/icon.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'"> <link rel="preload" href="{{ asset('assets/css/icon.css') }}" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ asset('assets/css/icon.css') }}"></noscript> <noscript><link rel="stylesheet" href="{{ asset('assets/css/icon.css') }}"></noscript>
@@ -70,8 +98,10 @@ $favicon_path = setting('site_favicon');
<link rel="preload" href="{{ asset('assets/css/fonts/space.css') }}" as="style" onload="this.rel='stylesheet'"> <link rel="preload" href="{{ asset('assets/css/fonts/space.css') }}" as="style" onload="this.rel='stylesheet'">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Caveat:wght@600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <link rel="preload" href="https://fonts.googleapis.com/css2?family=Caveat:wght@600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate"> <noscript><link href="https://fonts.googleapis.com/css2?family=Caveat:wght@600&family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet"></noscript>
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=translate&display=swap" rel="stylesheet"></noscript>
<!-- Site Verification --> <!-- Site Verification -->
@if(setting('seo_google_search_console')) @if(setting('seo_google_search_console'))
+7 -1
View File
@@ -1,8 +1,14 @@
{!! '<' . '?xml version="1.0" encoding="UTF-8"?' . '>' !!} {!! '<' . '?xml version="1.0" encoding="UTF-8"?' . '>' !!}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
@foreach($urls as $url) @foreach($urls as $url)
<url> <url>
<loc>{{ $url['loc'] }}</loc> <loc>{{ $url['loc'] }}</loc>
@if(!empty($url['alternates']))
@foreach($url['alternates'] as $alt)
<xhtml:link rel="alternate" hreflang="{{ $alt['hreflang'] }}" href="{{ $alt['href'] }}"/>
@endforeach
@endif
<lastmod>{{ $url['lastmod'] }}</lastmod> <lastmod>{{ $url['lastmod'] }}</lastmod>
<changefreq>{{ $url['changefreq'] }}</changefreq> <changefreq>{{ $url['changefreq'] }}</changefreq>
<priority>{{ $url['priority'] }}</priority> <priority>{{ $url['priority'] }}</priority>