Compare commits
2 Commits
d0292af20c
...
4af8900a78
| Author | SHA1 | Date | |
|---|---|---|---|
| 4af8900a78 | |||
| abcf105d07 |
@@ -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
|
||||
{
|
||||
protected static string $resource = InternApplicationResource::class;
|
||||
|
||||
protected function getHeaderWidgets(): array
|
||||
{
|
||||
return [
|
||||
\App\Filament\Admin\Resources\InternApplications\Widgets\InternGanttChartWidget::class,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
+1
-174
@@ -1,175 +1,2 @@
|
||||
<?php
|
||||
|
||||
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,
|
||||
];
|
||||
}
|
||||
}
|
||||
// Bu bileşen devre dışı bırakılmıştır.
|
||||
|
||||
@@ -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')),
|
||||
]);
|
||||
|
||||
+1
-157
@@ -1,157 +1 @@
|
||||
<x-filament-widgets::widget>
|
||||
<!-- 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>
|
||||
{{-- Bu bileşen görünümü devre dışı bırakılmıştır. --}}
|
||||
|
||||
Reference in New Issue
Block a user