feat: implement intern blog management system with approval workflows and administrative feedback
This commit is contained in:
@@ -2,11 +2,14 @@
|
|||||||
|
|
||||||
namespace App\Filament\Admin\Resources\Blogs\Tables;
|
namespace App\Filament\Admin\Resources\Blogs\Tables;
|
||||||
|
|
||||||
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Actions\ForceDeleteBulkAction;
|
use Filament\Actions\ForceDeleteBulkAction;
|
||||||
use Filament\Actions\RestoreBulkAction;
|
use Filament\Actions\RestoreBulkAction;
|
||||||
|
use Filament\Forms\Components\Textarea;
|
||||||
|
use Filament\Notifications\Notification;
|
||||||
use Filament\Tables\Columns\ImageColumn;
|
use Filament\Tables\Columns\ImageColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Columns\ToggleColumn;
|
use Filament\Tables\Columns\ToggleColumn;
|
||||||
@@ -32,26 +35,45 @@ class BlogsTable
|
|||||||
->sortable()
|
->sortable()
|
||||||
->limit(50),
|
->limit(50),
|
||||||
|
|
||||||
TextColumn::make('slug')
|
|
||||||
->label(__('blog.table_slug'))
|
|
||||||
->searchable()
|
|
||||||
->sortable()
|
|
||||||
->limit(30),
|
|
||||||
|
|
||||||
TextColumn::make('status')
|
TextColumn::make('status')
|
||||||
->label(__('blog.table_status'))
|
->label(__('blog.table_status'))
|
||||||
->badge()
|
->badge()
|
||||||
->color(fn (string $state): string => match ($state) {
|
->color(fn (?string $state): string => match ($state) {
|
||||||
'draft' => 'gray',
|
'draft' => 'gray',
|
||||||
|
'pending' => 'info',
|
||||||
'published' => 'success',
|
'published' => 'success',
|
||||||
|
'rejected' => 'danger',
|
||||||
'archived' => 'warning',
|
'archived' => 'warning',
|
||||||
|
default => 'gray',
|
||||||
})
|
})
|
||||||
->formatStateUsing(fn (string $state): string => match ($state) {
|
->formatStateUsing(fn (?string $state): string => match ($state) {
|
||||||
'draft' => __('blog.status_draft'),
|
'draft' => 'Taslak',
|
||||||
'published' => __('blog.status_published'),
|
'pending' => 'Onay Bekliyor',
|
||||||
'archived' => __('blog.status_archived'),
|
'published' => 'Yayınlandı',
|
||||||
|
'rejected' => 'Revize İstendi',
|
||||||
|
'archived' => 'Arşivlendi',
|
||||||
|
default => $state ?? '-',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
TextColumn::make('careerApplication.name')
|
||||||
|
->label('Stajyer')
|
||||||
|
->searchable()
|
||||||
|
->sortable()
|
||||||
|
->badge()
|
||||||
|
->color('purple')
|
||||||
|
->toggleable(),
|
||||||
|
|
||||||
|
TextColumn::make('intern_category')
|
||||||
|
->label('Staj Konusu')
|
||||||
|
->formatStateUsing(fn (?string $state): string => match ($state) {
|
||||||
|
'experience' => '1. Staj Tecrübesi',
|
||||||
|
'technical_challenge' => '2. Teknik Zorluklar',
|
||||||
|
'product_showcase' => '3. Ürün Tanıtımı',
|
||||||
|
default => $state ?? '-',
|
||||||
|
})
|
||||||
|
->badge()
|
||||||
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('author.name')
|
TextColumn::make('author.name')
|
||||||
->label(__('blog.table_author'))
|
->label(__('blog.table_author'))
|
||||||
->searchable()
|
->searchable()
|
||||||
@@ -75,39 +97,79 @@ class BlogsTable
|
|||||||
ToggleColumn::make('is_featured')
|
ToggleColumn::make('is_featured')
|
||||||
->label(__('blog.is_featured_field'))
|
->label(__('blog.is_featured_field'))
|
||||||
->alignCenter(),
|
->alignCenter(),
|
||||||
|
|
||||||
TextColumn::make('created_at')
|
|
||||||
->label(__('blog.table_created_at'))
|
|
||||||
->dateTime('d.m.Y H:i')
|
|
||||||
->sortable()
|
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
|
||||||
|
|
||||||
TextColumn::make('updated_at')
|
|
||||||
->label(__('blog.table_updated_at'))
|
|
||||||
->dateTime('d.m.Y H:i')
|
|
||||||
->sortable()
|
|
||||||
->toggleable(isToggledHiddenByDefault: true),
|
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
SelectFilter::make('status')
|
SelectFilter::make('status')
|
||||||
->label(__('blog.status_field'))
|
->label(__('blog.status_field'))
|
||||||
->options([
|
->options([
|
||||||
'draft' => __('blog.status_draft'),
|
'draft' => 'Taslak',
|
||||||
'published' => __('blog.status_published'),
|
'pending' => 'Onay Bekliyor',
|
||||||
'archived' => __('blog.status_archived'),
|
'published' => 'Yayınlandı',
|
||||||
|
'rejected' => 'Revize İstendi',
|
||||||
|
'archived' => 'Arşivlendi',
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
SelectFilter::make('intern_category')
|
||||||
|
->label('Staj Blog Konusu')
|
||||||
|
->options([
|
||||||
|
'experience' => '1. Staj Tecrübesi',
|
||||||
|
'technical_challenge' => '2. Teknik Zorluklar',
|
||||||
|
'product_showcase' => '3. Ürün Tanıtımı',
|
||||||
|
]),
|
||||||
|
|
||||||
|
TernaryFilter::make('is_intern_blog')
|
||||||
|
->label('Sadece Stajyer Yazıları')
|
||||||
|
->queries(
|
||||||
|
true: fn ($query) => $query->whereNotNull('career_application_id'),
|
||||||
|
false: fn ($query) => $query->whereNull('career_application_id'),
|
||||||
|
),
|
||||||
|
|
||||||
SelectFilter::make('category_id')
|
SelectFilter::make('category_id')
|
||||||
->label(__('blog.category_field'))
|
->label(__('blog.category_field'))
|
||||||
->relationship('category', 'name'),
|
->relationship('category', 'name'),
|
||||||
|
|
||||||
TernaryFilter::make('is_featured')
|
|
||||||
->label(__('blog.is_featured_field')),
|
|
||||||
|
|
||||||
TernaryFilter::make('allow_comments')
|
|
||||||
->label(__('blog.allow_comments_field')),
|
|
||||||
])
|
])
|
||||||
->recordActions([
|
->recordActions([
|
||||||
|
Action::make('approve')
|
||||||
|
->label('Onayla & Yayınla')
|
||||||
|
->icon('heroicon-o-check-circle')
|
||||||
|
->color('success')
|
||||||
|
->visible(fn ($record) => in_array($record->status, ['pending', 'draft', 'rejected']))
|
||||||
|
->action(function ($record) {
|
||||||
|
$record->status = 'published';
|
||||||
|
$record->published_at = now();
|
||||||
|
$record->admin_feedback = null;
|
||||||
|
$record->save();
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('Yazı Onaylandı')
|
||||||
|
->body('Blog yazısı başarıyla yayınlandı.')
|
||||||
|
->success()
|
||||||
|
->send();
|
||||||
|
}),
|
||||||
|
|
||||||
|
Action::make('reject')
|
||||||
|
->label('Revize İstə')
|
||||||
|
->icon('heroicon-o-x-circle')
|
||||||
|
->color('danger')
|
||||||
|
->visible(fn ($record) => in_array($record->status, ['pending', 'published']))
|
||||||
|
->form([
|
||||||
|
Textarea::make('admin_feedback')
|
||||||
|
->label('Revizyon Gerekçesi / Stajyere Not')
|
||||||
|
->required()
|
||||||
|
->placeholder('Örn: Başlığı ve içerikteki kod bloklarını düzenleyiniz.'),
|
||||||
|
])
|
||||||
|
->action(function ($record, array $data) {
|
||||||
|
$record->status = 'rejected';
|
||||||
|
$record->admin_feedback = $data['admin_feedback'];
|
||||||
|
$record->save();
|
||||||
|
|
||||||
|
Notification::make()
|
||||||
|
->title('Revizyon Talebi Gönderildi')
|
||||||
|
->body('Stajyere revizyon bildirimi iletildi.')
|
||||||
|
->warning()
|
||||||
|
->send();
|
||||||
|
}),
|
||||||
|
|
||||||
EditAction::make()
|
EditAction::make()
|
||||||
->label(__('blog.edit')),
|
->label(__('blog.edit')),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -363,7 +363,71 @@ class InternApplicationResource extends Resource
|
|||||||
"#### 📝 Danışman Görüşü ve Değerlendirme Notu\n" .
|
"#### 📝 Danışman Görüşü ve Değerlendirme Notu\n" .
|
||||||
"\"Stajyerimiz, staj süresi boyunca kendisine verilen görevleri büyük bir titizlikle yerine getirmiştir. Özellikle karşılaştığı teknik problemlere getirdiği pratik çözümler ve yeni teknolojileri öğrenme isteği takdir edilmeye değerdir. Kurumumuz bünyesinde yürüttüğümüz projelere sağladığı katkılardan ötürü teşekkür eder, profesyonel kariyerinde başarılar dileriz.\"";
|
"\"Stajyerimiz, staj süresi boyunca kendisine verilen görevleri büyük bir titizlikle yerine getirmiştir. Özellikle karşılaştığı teknik problemlere getirdiği pratik çözümler ve yeni teknolojileri öğrenme isteği takdir edilmeye değerdir. Kurumumuz bünyesinde yürüttüğümüz projelere sağladığı katkılardan ötürü teşekkür eder, profesyonel kariyerinde başarılar dileriz.\"";
|
||||||
}),
|
}),
|
||||||
])->columns(1)
|
])->columns(1),
|
||||||
|
|
||||||
|
Tab::make('Stajyer Blog Yazıları')
|
||||||
|
->icon('heroicon-o-newspaper')
|
||||||
|
->schema([
|
||||||
|
\Filament\Forms\Components\Placeholder::make('intern_blogs_view')
|
||||||
|
->label('Yazılan Blog Yazıları')
|
||||||
|
->content(function ($record) {
|
||||||
|
if (!$record) return 'Henüz kayıt bulunmuyor.';
|
||||||
|
$blogs = $record->blogs()->orderBy('created_at', 'desc')->get();
|
||||||
|
if ($blogs->isEmpty()) {
|
||||||
|
return new \Illuminate\Support\HtmlString('<div style="padding: 15px; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; color: #64748b; font-size: 13px;">Bu stajyer henüz blog yazısı oluşturmadı.</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
$html = '<div style="space-y: 12px;">';
|
||||||
|
foreach ($blogs as $b) {
|
||||||
|
$catLabel = match ($b->intern_category) {
|
||||||
|
'experience' => '1. Staj Tecrübesi',
|
||||||
|
'technical_challenge' => '2. Teknik Zorluklar',
|
||||||
|
'product_showcase' => '3. Ürün Tanıtımı',
|
||||||
|
default => $b->intern_category ?? '-',
|
||||||
|
};
|
||||||
|
$statusBg = match ($b->status) {
|
||||||
|
'published' => '#d1fae5',
|
||||||
|
'pending' => '#fef3c7',
|
||||||
|
'rejected' => '#fee2e2',
|
||||||
|
default => '#f1f5f9',
|
||||||
|
};
|
||||||
|
$statusColor = match ($b->status) {
|
||||||
|
'published' => '#065f46',
|
||||||
|
'pending' => '#92400e',
|
||||||
|
'rejected' => '#991b1b',
|
||||||
|
default => '#475569',
|
||||||
|
};
|
||||||
|
$statusText = match ($b->status) {
|
||||||
|
'published' => 'Yayınlandı',
|
||||||
|
'pending' => 'Onay Bekliyor',
|
||||||
|
'rejected' => 'Revize İstendi',
|
||||||
|
default => 'Taslak',
|
||||||
|
};
|
||||||
|
|
||||||
|
$html .= '<div style="padding: 14px; border: 1px solid #e2e8f0; border-radius: 10px; background: #ffffff; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;">';
|
||||||
|
$html .= ' <div>';
|
||||||
|
$html .= ' <div style="display: flex; gap: 8px; align-items: center; margin-bottom: 4px;">';
|
||||||
|
$html .= ' <span style="font-size: 11px; font-weight: 800; background: #eff6ff; color: #1d4ed8; padding: 2px 8px; border-radius: 6px;">' . e($catLabel) . '</span>';
|
||||||
|
$html .= ' <span style="font-size: 11px; font-weight: 800; background: ' . $statusBg . '; color: ' . $statusColor . '; padding: 2px 8px; border-radius: 6px;">' . $statusText . '</span>';
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <h4 style="margin: 0; font-size: 14px; font-weight: bold; color: #1e293b;">' . e($b->title) . '</h4>';
|
||||||
|
if ($b->admin_feedback) {
|
||||||
|
$html .= ' <p style="margin: 4px 0 0 0; font-size: 11px; color: #dc2626;"><strong>Revizyon Notu:</strong> ' . e($b->admin_feedback) . '</p>';
|
||||||
|
}
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= ' <div>';
|
||||||
|
if ($b->status === 'published') {
|
||||||
|
$html .= ' <a href="/blog/' . $b->slug . '" target="_blank" style="padding: 6px 12px; background: #059669; color: white; border-radius: 6px; font-size: 11px; font-weight: bold; text-decoration: none;">Sitede Gör</a>';
|
||||||
|
}
|
||||||
|
$html .= ' </div>';
|
||||||
|
$html .= '</div>';
|
||||||
|
}
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return new \Illuminate\Support\HtmlString($html);
|
||||||
|
})
|
||||||
|
->columnSpanFull(),
|
||||||
|
])
|
||||||
])->columnSpanFull()
|
])->columnSpanFull()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\CareerApplication;
|
use App\Models\CareerApplication;
|
||||||
|
use App\Models\Blog;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class CareerController extends Controller
|
class CareerController extends Controller
|
||||||
{
|
{
|
||||||
@@ -98,8 +100,11 @@ class CareerController extends Controller
|
|||||||
'password' => 'required|string',
|
'password' => 'required|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$intern = CareerApplication::where('username', $request->username)
|
$intern = CareerApplication::where('type', 'internship')
|
||||||
->where('type', 'internship')
|
->where(function ($q) use ($request) {
|
||||||
|
$q->where('username', $request->username)
|
||||||
|
->orWhere('email', $request->username);
|
||||||
|
})
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if (!$intern || !\Illuminate\Support\Facades\Hash::check($request->password, $intern->password)) {
|
if (!$intern || !\Illuminate\Support\Facades\Hash::check($request->password, $intern->password)) {
|
||||||
@@ -124,18 +129,109 @@ class CareerController extends Controller
|
|||||||
$intern = CareerApplication::findOrFail(session('intern_id'));
|
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||||
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
$days = self::getInternshipDates($intern->internship_start_date, $intern->internship_total_days);
|
||||||
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
$savedEntries = $intern->journalEntries()->get()->keyBy('day_number');
|
||||||
|
$blogs = $intern->blogs()->orderBy('created_at', 'desc')->get();
|
||||||
|
|
||||||
return view('front.career.intern_dashboard', [
|
return view('front.career.intern_dashboard', [
|
||||||
'intern' => $intern,
|
'intern' => $intern,
|
||||||
'days' => $days,
|
'days' => $days,
|
||||||
'savedEntries' => $savedEntries,
|
'savedEntries' => $savedEntries,
|
||||||
|
'blogs' => $blogs,
|
||||||
'meta' => [
|
'meta' => [
|
||||||
'title' => 'Stajyer Paneli',
|
'title' => 'Stajyer Paneli',
|
||||||
'description' => 'Belgelerinizi buradan indirebilirsiniz.',
|
'description' => 'Staj belgelerinizi ve blog yazılarınızı yönetin.',
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function saveInternBlog(Request $request)
|
||||||
|
{
|
||||||
|
if (!session()->has('intern_id')) {
|
||||||
|
return redirect()->route('intern.login')->with('error', 'Lütfen önce giriş yapın.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$intern = CareerApplication::findOrFail(session('intern_id'));
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'blog_id' => 'nullable|integer|exists:blogs,id',
|
||||||
|
'title' => 'required|string|max:255',
|
||||||
|
'intern_category' => 'required|string|in:experience,technical_challenge,product_showcase',
|
||||||
|
'excerpt' => 'nullable|string|max:500',
|
||||||
|
'content' => 'required|string|min:50',
|
||||||
|
'featured_image' => 'nullable|image|mimes:jpeg,png,jpg,webp|max:5120',
|
||||||
|
'action_type' => 'required|string|in:draft,submit',
|
||||||
|
], [
|
||||||
|
'title.required' => 'Lütfen blog yazısı başlığını girin.',
|
||||||
|
'intern_category.required' => 'Lütfen bir kategori seçin.',
|
||||||
|
'content.required' => 'Lütfen blog yazısı içeriğini girin.',
|
||||||
|
'content.min' => 'Blog içeriği en az 50 karakter olmalıdır.',
|
||||||
|
'featured_image.image' => 'Görsel geçerli bir resim dosyası olmalıdır.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$status = $request->action_type === 'draft' ? 'draft' : 'pending';
|
||||||
|
|
||||||
|
if ($request->filled('blog_id')) {
|
||||||
|
$blog = Blog::where('id', $request->blog_id)
|
||||||
|
->where('career_application_id', $intern->id)
|
||||||
|
->firstOrFail();
|
||||||
|
} else {
|
||||||
|
$blog = new Blog();
|
||||||
|
$blog->career_application_id = $intern->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle slug
|
||||||
|
if (!$blog->exists || $blog->title !== $request->title) {
|
||||||
|
$baseSlug = Str::slug($request->title);
|
||||||
|
$slug = $baseSlug;
|
||||||
|
$count = 1;
|
||||||
|
while (Blog::where('slug', $slug)->where('id', '!=', $blog->id ?? 0)->exists()) {
|
||||||
|
$slug = $baseSlug . '-' . $count;
|
||||||
|
$count++;
|
||||||
|
}
|
||||||
|
$blog->slug = $slug;
|
||||||
|
}
|
||||||
|
|
||||||
|
$blog->title = $request->title;
|
||||||
|
$blog->intern_category = $request->intern_category;
|
||||||
|
$blog->excerpt = $request->excerpt;
|
||||||
|
$blog->content = $request->content;
|
||||||
|
$blog->status = $status;
|
||||||
|
$blog->meta_title = $request->title;
|
||||||
|
$blog->meta_description = Str::limit(strip_tags($request->excerpt ?: $request->content), 160);
|
||||||
|
|
||||||
|
if ($request->hasFile('featured_image')) {
|
||||||
|
if ($blog->featured_image && Storage::disk('public')->exists($blog->featured_image)) {
|
||||||
|
Storage::disk('public')->delete($blog->featured_image);
|
||||||
|
}
|
||||||
|
$blog->featured_image = $request->file('featured_image')->store('blogs', 'public');
|
||||||
|
}
|
||||||
|
|
||||||
|
$blog->save();
|
||||||
|
|
||||||
|
$msg = $status === 'draft' ? 'Blog yazısı taslak olarak kaydedildi.' : 'Blog yazısı incelemeye gönderildi.';
|
||||||
|
return redirect()->back()->with('success', $msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function deleteInternBlog($id)
|
||||||
|
{
|
||||||
|
if (!session()->has('intern_id')) {
|
||||||
|
return redirect()->route('intern.login')->with('error', 'Lütfen giriş yapın.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$blog = Blog::where('id', $id)
|
||||||
|
->where('career_application_id', session('intern_id'))
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
if (in_array($blog->status, ['draft', 'pending', 'rejected'])) {
|
||||||
|
if ($blog->featured_image && Storage::disk('public')->exists($blog->featured_image)) {
|
||||||
|
Storage::disk('public')->delete($blog->featured_image);
|
||||||
|
}
|
||||||
|
$blog->forceDelete();
|
||||||
|
return redirect()->back()->with('success', 'Blog yazısı silindi.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->with('error', 'Yayınlanmış blog yazıları silinemez.');
|
||||||
|
}
|
||||||
|
|
||||||
public function uploadInternshipForm(Request $request)
|
public function uploadInternshipForm(Request $request)
|
||||||
{
|
{
|
||||||
if (!session()->has('intern_id')) {
|
if (!session()->has('intern_id')) {
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ class Blog extends Model
|
|||||||
'published_at',
|
'published_at',
|
||||||
'author_id',
|
'author_id',
|
||||||
'category_id',
|
'category_id',
|
||||||
|
'career_application_id',
|
||||||
|
'intern_category',
|
||||||
|
'admin_feedback',
|
||||||
'tags',
|
'tags',
|
||||||
'view_count',
|
'view_count',
|
||||||
'is_featured',
|
'is_featured',
|
||||||
@@ -53,6 +56,11 @@ class Blog extends Model
|
|||||||
return $this->belongsTo(User::class, 'author_id');
|
return $this->belongsTo(User::class, 'author_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function careerApplication()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(CareerApplication::class, 'career_application_id');
|
||||||
|
}
|
||||||
|
|
||||||
public function category()
|
public function category()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(BlogCategory::class, 'category_id');
|
return $this->belongsTo(BlogCategory::class, 'category_id');
|
||||||
|
|||||||
@@ -48,6 +48,14 @@ class CareerApplication extends Model
|
|||||||
return $this->hasMany(InternshipJournalEntry::class);
|
return $this->hasMany(InternshipJournalEntry::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the blog posts written by this intern.
|
||||||
|
*/
|
||||||
|
public function blogs()
|
||||||
|
{
|
||||||
|
return $this->hasMany(Blog::class, 'career_application_id');
|
||||||
|
}
|
||||||
|
|
||||||
protected static function booted()
|
protected static function booted()
|
||||||
{
|
{
|
||||||
static::saving(function ($model) {
|
static::saving(function ($model) {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?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('blogs', function (Blueprint $table) {
|
||||||
|
$table->foreignId('career_application_id')->nullable()->after('author_id')->constrained('career_applications')->onDelete('cascade');
|
||||||
|
$table->string('intern_category')->nullable()->after('career_application_id');
|
||||||
|
$table->text('admin_feedback')->nullable()->after('intern_category');
|
||||||
|
$table->foreignId('author_id')->nullable()->change();
|
||||||
|
$table->string('status')->default('draft')->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('blogs', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['career_application_id']);
|
||||||
|
$table->dropColumn(['career_application_id', 'intern_category', 'admin_feedback']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -377,6 +377,23 @@
|
|||||||
<span class="block text-xs text-slate-400 font-semibold mt-0.5">Günlük çalışma defteri</span>
|
<span class="block text-xs text-slate-400 font-semibold mt-0.5">Günlük çalışma defteri</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Menu Item 4: Blog Yazıları -->
|
||||||
|
@php
|
||||||
|
$approvedBlogsCount = isset($blogs) ? $blogs->where('status', 'published')->count() : 0;
|
||||||
|
@endphp
|
||||||
|
<button type="button" role="tab" aria-selected="false" data-tab-target="blogs" class="tab-btn w-full flex items-center gap-4 p-3 rounded-2xl text-left transition-all border border-transparent">
|
||||||
|
<div class="w-10 h-10 rounded-xl bg-slate-50 text-slate-500 flex items-center justify-center text-xl flex-shrink-0 tab-icon">
|
||||||
|
<i class="uil uil-newspaper"></i>
|
||||||
|
</div>
|
||||||
|
<div class="flex-grow">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<span class="block text-sm font-extrabold text-slate-800">Blog Yazıları</span>
|
||||||
|
<span class="text-[10px] font-extrabold px-2 py-0.5 rounded-full @if($approvedBlogsCount >= 3) bg-green-100 text-green-700 @else bg-blue-100 text-blue-700 @endif">{{ $approvedBlogsCount }}/3</span>
|
||||||
|
</div>
|
||||||
|
<span class="block text-xs text-slate-400 font-semibold mt-0.5">Sitede yayınlanacak bloglar</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -785,6 +802,222 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Panel 4: Blog Yazıları -->
|
||||||
|
<div id="blogs-panel" class="tab-panel hidden space-y-6">
|
||||||
|
<div class="bg-white rounded-3xl p-6 md:p-8 shadow-xl border border-slate-100/50">
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 pb-6 border-b border-slate-100">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold text-slate-800 text-lg flex items-center gap-2">
|
||||||
|
<i class="uil uil-newspaper text-blue-600"></i>
|
||||||
|
<span>Staj Blog Yazıları (Min. 3 Adet)</span>
|
||||||
|
</h3>
|
||||||
|
<p class="text-xs text-slate-400 mt-1">
|
||||||
|
Staj süresince en az 3 blog yazısı kaleme almalısınız. Onaylanan yazılar web sitemizin blog bölümünde yayınlanacaktır.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="openBlogModal()" class="px-4 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 flex items-center gap-2">
|
||||||
|
<i class="uil uil-plus text-base"></i>
|
||||||
|
<span>Yeni Blog Yazısı Ekle</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress Bar -->
|
||||||
|
@php
|
||||||
|
$userBlogs = $blogs ?? collect();
|
||||||
|
$publishedBlogs = $userBlogs->where('status', 'published');
|
||||||
|
$publishedCount = $publishedBlogs->count();
|
||||||
|
$progressPercent = min(100, round(($publishedCount / 3) * 100));
|
||||||
|
@endphp
|
||||||
|
<div class="mt-6 p-5 bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl border border-blue-100">
|
||||||
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<span class="text-xs font-extrabold text-blue-900">Blog Görev Tamamlama Oranı</span>
|
||||||
|
<span class="text-xs font-black text-blue-700">{{ $publishedCount }} / 3 Yayınlandı (%{{ $progressPercent }})</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-blue-200/60 rounded-full h-2.5 overflow-hidden">
|
||||||
|
<div class="bg-blue-600 h-2.5 rounded-full transition-all duration-500" style="width: {{ $progressPercent }}%"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 3 Mandatory Category Cards -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
|
||||||
|
@php
|
||||||
|
$expBlog = $userBlogs->where('intern_category', 'experience')->first();
|
||||||
|
$techBlog = $userBlogs->where('intern_category', 'technical_challenge')->first();
|
||||||
|
$prodBlog = $userBlogs->where('intern_category', 'product_showcase')->first();
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<!-- Category 1 -->
|
||||||
|
<div class="p-4 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<span class="text-[10px] uppercase font-extrabold tracking-wider text-slate-400">1. Konu</span>
|
||||||
|
@if($expBlog && $expBlog->status === 'published')
|
||||||
|
<span class="px-2 py-0.5 bg-green-50 text-green-700 border border-green-200 text-[10px] font-bold rounded-lg">Yayınlandı ✓</span>
|
||||||
|
@elseif($expBlog && $expBlog->status === 'pending')
|
||||||
|
<span class="px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 text-[10px] font-bold rounded-lg">Onay Bekliyor</span>
|
||||||
|
@elseif($expBlog && $expBlog->status === 'rejected')
|
||||||
|
<span class="px-2 py-0.5 bg-red-50 text-red-700 border border-red-200 text-[10px] font-bold rounded-lg">Revize İstendi</span>
|
||||||
|
@elseif($expBlog && $expBlog->status === 'draft')
|
||||||
|
<span class="px-2 py-0.5 bg-slate-100 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-lg">Taslak</span>
|
||||||
|
@else
|
||||||
|
<span class="px-2 py-0.5 bg-slate-100 text-slate-400 border border-slate-200 text-[10px] font-bold rounded-lg">Bekliyor</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<h4 class="text-xs font-extrabold text-slate-800">Staj Tecrübesi & Adaptasyon</h4>
|
||||||
|
<p class="text-[11px] text-slate-400 mt-1">Staj süreci, şirket kültürü ve ilk izlenimlerinizi anlatan yazı.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category 2 -->
|
||||||
|
<div class="p-4 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<span class="text-[10px] uppercase font-extrabold tracking-wider text-slate-400">2. Konu</span>
|
||||||
|
@if($techBlog && $techBlog->status === 'published')
|
||||||
|
<span class="px-2 py-0.5 bg-green-50 text-green-700 border border-green-200 text-[10px] font-bold rounded-lg">Yayınlandı ✓</span>
|
||||||
|
@elseif($techBlog && $techBlog->status === 'pending')
|
||||||
|
<span class="px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 text-[10px] font-bold rounded-lg">Onay Bekliyor</span>
|
||||||
|
@elseif($techBlog && $techBlog->status === 'rejected')
|
||||||
|
<span class="px-2 py-0.5 bg-red-50 text-red-700 border border-red-200 text-[10px] font-bold rounded-lg">Revize İstendi</span>
|
||||||
|
@elseif($techBlog && $techBlog->status === 'draft')
|
||||||
|
<span class="px-2 py-0.5 bg-slate-100 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-lg">Taslak</span>
|
||||||
|
@else
|
||||||
|
<span class="px-2 py-0.5 bg-slate-100 text-slate-400 border border-slate-200 text-[10px] font-bold rounded-lg">Bekliyor</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<h4 class="text-xs font-extrabold text-slate-800">Teknik Zorluklar & Çözümler</h4>
|
||||||
|
<p class="text-[11px] text-slate-400 mt-1">Gelişim sürecinde karşılaştığınız teknik engeller ve çözüm yöntemleri.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Category 3 -->
|
||||||
|
<div class="p-4 rounded-2xl border border-slate-100 bg-slate-50/50 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<span class="text-[10px] uppercase font-extrabold tracking-wider text-slate-400">3. Konu</span>
|
||||||
|
@if($prodBlog && $prodBlog->status === 'published')
|
||||||
|
<span class="px-2 py-0.5 bg-green-50 text-green-700 border border-green-200 text-[10px] font-bold rounded-lg">Yayınlandı ✓</span>
|
||||||
|
@elseif($prodBlog && $prodBlog->status === 'pending')
|
||||||
|
<span class="px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 text-[10px] font-bold rounded-lg">Onay Bekliyor</span>
|
||||||
|
@elseif($prodBlog && $prodBlog->status === 'rejected')
|
||||||
|
<span class="px-2 py-0.5 bg-red-50 text-red-700 border border-red-200 text-[10px] font-bold rounded-lg">Revize İstendi</span>
|
||||||
|
@elseif($prodBlog && $prodBlog->status === 'draft')
|
||||||
|
<span class="px-2 py-0.5 bg-slate-100 text-slate-600 border border-slate-200 text-[10px] font-bold rounded-lg">Taslak</span>
|
||||||
|
@else
|
||||||
|
<span class="px-2 py-0.5 bg-slate-100 text-slate-400 border border-slate-200 text-[10px] font-bold rounded-lg">Bekliyor</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<h4 class="text-xs font-extrabold text-slate-800">Ürün / Proje Tanıtımı (Showcase)</h4>
|
||||||
|
<p class="text-[11px] text-slate-400 mt-1">Geliştirdiğiniz nihai ürünün amacı, mimarisi ve canlı demo anlatımı.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Blog List Table -->
|
||||||
|
<div class="mt-8">
|
||||||
|
<h4 class="text-sm font-extrabold text-slate-800 mb-4">Gönderilen Blog Yazılarınız</h4>
|
||||||
|
|
||||||
|
@if($userBlogs->isEmpty())
|
||||||
|
<div class="text-center py-10 border-2 border-dashed border-slate-100 rounded-2xl">
|
||||||
|
<i class="uil uil-file-edit-alt text-4xl text-slate-300"></i>
|
||||||
|
<p class="text-xs text-slate-500 font-bold mt-2">Henüz bir blog yazısı eklemediniz.</p>
|
||||||
|
<button type="button" onclick="openBlogModal()" class="mt-3 text-xs font-extrabold text-blue-600 hover:text-blue-800">
|
||||||
|
+ İlk Blog Yazısını Ekle
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr class="border-b border-slate-100 text-[11px] font-extrabold text-slate-400 uppercase tracking-wider">
|
||||||
|
<th class="py-3 px-3">Görsel</th>
|
||||||
|
<th class="py-3 px-3">Başlık & Konu</th>
|
||||||
|
<th class="py-3 px-3">Durum</th>
|
||||||
|
<th class="py-3 px-3">Yönetici Notu</th>
|
||||||
|
<th class="py-3 px-3 text-right">İşlemler</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-100 text-xs">
|
||||||
|
@foreach($userBlogs as $blogItem)
|
||||||
|
<tr>
|
||||||
|
<td class="py-3 px-3">
|
||||||
|
@if($blogItem->featured_image)
|
||||||
|
<img src="{{ asset('storage/' . $blogItem->featured_image) }}" class="w-12 h-12 object-cover rounded-xl border border-slate-100" />
|
||||||
|
@else
|
||||||
|
<div class="w-12 h-12 rounded-xl bg-slate-100 flex items-center justify-center text-slate-400">
|
||||||
|
<i class="uil uil-image text-xl"></i>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3">
|
||||||
|
<span class="font-bold text-slate-800 block text-sm">{{ $blogItem->title }}</span>
|
||||||
|
<span class="text-[10px] font-bold text-slate-400 block mt-0.5">
|
||||||
|
@if($blogItem->intern_category === 'experience') 1. Staj Tecrübesi
|
||||||
|
@elseif($blogItem->intern_category === 'technical_challenge') 2. Teknik Zorluklar
|
||||||
|
@elseif($blogItem->intern_category === 'product_showcase') 3. Ürün Tanıtımı
|
||||||
|
@else {{ $blogItem->intern_category }} @endif
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3">
|
||||||
|
@if($blogItem->status === 'published')
|
||||||
|
<span class="px-2.5 py-1 bg-green-50 text-green-700 border border-green-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
|
||||||
|
<i class="uil uil-check-circle text-sm"></i> Sitede Yayınlandı
|
||||||
|
</span>
|
||||||
|
@elseif($blogItem->status === 'pending')
|
||||||
|
<span class="px-2.5 py-1 bg-amber-50 text-amber-700 border border-amber-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
|
||||||
|
<i class="uil uil-clock text-sm"></i> Onay Bekliyor
|
||||||
|
</span>
|
||||||
|
@elseif($blogItem->status === 'rejected')
|
||||||
|
<span class="px-2.5 py-1 bg-red-50 text-red-700 border border-red-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
|
||||||
|
<i class="uil uil-exclamation-triangle text-sm"></i> Revize İstendi
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<span class="px-2.5 py-1 bg-slate-100 text-slate-600 border border-slate-200 font-extrabold rounded-full text-[11px] inline-flex items-center gap-1">
|
||||||
|
<i class="uil uil-edit text-sm"></i> Taslak
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3">
|
||||||
|
@if($blogItem->admin_feedback)
|
||||||
|
<div class="p-2 bg-red-50 border border-red-100 rounded-xl text-red-700 text-[11px] font-semibold max-w-xs">
|
||||||
|
<strong>Geribildirim:</strong> {{ $blogItem->admin_feedback }}
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<span class="text-slate-300">-</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td class="py-3 px-3 text-right">
|
||||||
|
<div class="flex items-center justify-end gap-2">
|
||||||
|
@if($blogItem->status === 'published')
|
||||||
|
<a href="{{ route('blog.show', $blogItem->slug) }}" target="_blank" class="px-3 py-1.5 bg-green-50 text-green-700 hover:bg-green-100 font-bold rounded-lg text-xs transition-all flex items-center gap-1">
|
||||||
|
<i class="uil uil-external-link-alt"></i> Sitede Gör
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<button type="button" onclick='editBlogItem({{ json_encode($blogItem) }})' class="px-3 py-1.5 bg-blue-50 text-blue-600 hover:bg-blue-100 font-bold rounded-lg text-xs transition-all flex items-center gap-1">
|
||||||
|
<i class="uil uil-pen"></i> Düzenle
|
||||||
|
</button>
|
||||||
|
<form action="{{ route('intern.blog.delete', $blogItem->id) }}" method="POST" onsubmit="return confirm('Bu blog yazısını silmek istediğinize emin misiniz?');">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button type="submit" class="px-2.5 py-1.5 bg-slate-100 text-red-600 hover:bg-red-50 font-bold rounded-lg text-xs transition-all">
|
||||||
|
<i class="uil uil-trash-alt"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1563,6 +1796,89 @@
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
let blogQuill = null;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
if (document.getElementById('blog-quill-editor')) {
|
||||||
|
blogQuill = new Quill('#blog-quill-editor', {
|
||||||
|
theme: 'snow',
|
||||||
|
placeholder: 'Blog yazınızı buraya detaylı bir şekilde yazın...',
|
||||||
|
modules: {
|
||||||
|
toolbar: [
|
||||||
|
[{ 'header': [1, 2, 3, false] }],
|
||||||
|
['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'],
|
||||||
|
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
|
||||||
|
['link', 'clean']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function openBlogModal() {
|
||||||
|
document.getElementById('modal_blog_id').value = '';
|
||||||
|
document.getElementById('modal_blog_title').value = '';
|
||||||
|
document.getElementById('modal_intern_category').value = 'experience';
|
||||||
|
document.getElementById('modal_blog_excerpt').value = '';
|
||||||
|
if (blogQuill) blogQuill.setContents([]);
|
||||||
|
document.getElementById('blog-modal-title').querySelector('span').textContent = 'Yeni Blog Yazısı Ekle';
|
||||||
|
|
||||||
|
const modal = document.getElementById('blog-modal');
|
||||||
|
const card = document.getElementById('blog-modal-card');
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
setTimeout(() => {
|
||||||
|
card.classList.remove('scale-95', 'opacity-0');
|
||||||
|
card.classList.add('scale-100', 'opacity-100');
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function editBlogItem(blogItem) {
|
||||||
|
document.getElementById('modal_blog_id').value = blogItem.id;
|
||||||
|
document.getElementById('modal_blog_title').value = blogItem.title;
|
||||||
|
document.getElementById('modal_intern_category').value = blogItem.intern_category || 'experience';
|
||||||
|
document.getElementById('modal_blog_excerpt').value = blogItem.excerpt || '';
|
||||||
|
if (blogQuill && blogItem.content) {
|
||||||
|
blogQuill.setContents([]);
|
||||||
|
blogQuill.clipboard.dangerouslyPasteHTML(blogItem.content);
|
||||||
|
}
|
||||||
|
document.getElementById('blog-modal-title').querySelector('span').textContent = 'Blog Yazısını Düzenle';
|
||||||
|
|
||||||
|
const modal = document.getElementById('blog-modal');
|
||||||
|
const card = document.getElementById('blog-modal-card');
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
setTimeout(() => {
|
||||||
|
card.classList.remove('scale-95', 'opacity-0');
|
||||||
|
card.classList.add('scale-100', 'opacity-100');
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBlogModal() {
|
||||||
|
const modal = document.getElementById('blog-modal');
|
||||||
|
const card = document.getElementById('blog-modal-card');
|
||||||
|
card.classList.remove('scale-100', 'opacity-100');
|
||||||
|
card.classList.add('scale-95', 'opacity-0');
|
||||||
|
setTimeout(() => {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitBlogForm(actionType) {
|
||||||
|
const title = document.getElementById('modal_blog_title').value.trim();
|
||||||
|
if (!title) {
|
||||||
|
alert('Lütfen blog başlığını giriniz.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlContent = blogQuill ? blogQuill.root.innerHTML.trim() : '';
|
||||||
|
if (!htmlContent || htmlContent === '<p><br></p>') {
|
||||||
|
alert('Lütfen blog içeriğini giriniz.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('modal_action_type').value = actionType;
|
||||||
|
document.getElementById('modal_blog_content').value = htmlContent;
|
||||||
|
document.getElementById('blog-form').submit();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.shadow-xl {
|
.shadow-xl {
|
||||||
@@ -1592,5 +1908,75 @@
|
|||||||
</style>
|
</style>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
|
<!-- Blog Creation/Edit Modal -->
|
||||||
|
<div id="blog-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 md:p-8 max-w-3xl w-full shadow-2xl border border-slate-100/50 transform scale-95 opacity-0 transition-all duration-300 max-h-[90vh] overflow-y-auto" id="blog-modal-card">
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-xl font-extrabold text-slate-800 flex items-center gap-2" id="blog-modal-title">
|
||||||
|
<i class="uil uil-pen text-blue-600"></i>
|
||||||
|
<span>Yeni Blog Yazısı Ekle</span>
|
||||||
|
</h3>
|
||||||
|
<p class="text-xs text-slate-400 mt-1">Yazınız yöneticiniz tarafından onaylandıktan sonra web sitesinde yayınlanacaktır.</p>
|
||||||
|
</div>
|
||||||
|
<button type="button" onclick="closeBlogModal()" class="text-slate-400 hover:text-slate-600">
|
||||||
|
<i class="uil uil-multiply text-2xl"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="blog-form" action="{{ route('intern.blog.save') }}" method="POST" enctype="multipart/form-data">
|
||||||
|
@csrf
|
||||||
|
<input type="hidden" name="blog_id" id="modal_blog_id" value="" />
|
||||||
|
<input type="hidden" name="action_type" id="modal_action_type" value="submit" />
|
||||||
|
<input type="hidden" name="content" id="modal_blog_content" value="" />
|
||||||
|
|
||||||
|
<div class="space-y-5">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Blog Başlığı *</label>
|
||||||
|
<input type="text" name="title" id="modal_blog_title" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 text-sm font-bold text-slate-800" placeholder="Örn: Uzaktan Staj Deneyimim ve Karşılaştığım Zorluklar" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Staj Konu Kategorisi *</label>
|
||||||
|
<select name="intern_category" id="modal_intern_category" required class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 text-sm font-bold text-slate-800">
|
||||||
|
<option value="experience">1. Staj Tecrübesi & Adaptasyon</option>
|
||||||
|
<option value="technical_challenge">2. Teknik Zorluklar & Çözümler</option>
|
||||||
|
<option value="product_showcase">3. Ürün / Proje Tanıtımı (Showcase)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Kapak Görseli</label>
|
||||||
|
<input type="file" name="featured_image" id="modal_featured_image" accept="image/*" class="w-full text-xs text-slate-500 file:mr-4 file:py-2.5 file:px-4 file:rounded-xl file:border-0 file:text-xs file:font-bold file:bg-blue-50 file:text-blue-700 hover:file:bg-blue-100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Kısa Özet (Excerpt)</label>
|
||||||
|
<textarea name="excerpt" id="modal_blog_excerpt" rows="2" class="w-full px-4 py-3 rounded-xl border border-slate-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-100 text-xs font-medium text-slate-700" placeholder="Yazınızın liste sayfalarında görünecek kısa özeti..."></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-extrabold text-slate-600 uppercase tracking-wider mb-2">Blog İçeriği *</label>
|
||||||
|
<div id="blog-quill-editor-container" class="rounded-xl border border-slate-200 overflow-hidden">
|
||||||
|
<div id="blog-quill-editor" style="height: 250px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-col sm:flex-row justify-end gap-3 mt-8 pt-4 border-t border-slate-100">
|
||||||
|
<button type="button" onclick="submitBlogForm('draft')" class="px-5 py-3 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 font-extrabold text-xs transition-all">
|
||||||
|
Taslak Olarak Kaydet
|
||||||
|
</button>
|
||||||
|
<button type="button" onclick="submitBlogForm('submit')" class="px-6 py-3 rounded-xl bg-blue-600 hover:bg-blue-700 text-white font-extrabold text-xs transition-all shadow-md shadow-blue-500/10 flex items-center justify-center gap-2">
|
||||||
|
<i class="uil uil-arrow-right text-base"></i>
|
||||||
|
<span>Onaya Gönder</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@include('front.career.partials.guide_modal')
|
@include('front.career.partials.guide_modal')
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -124,6 +124,8 @@ Route::post('/stajyer/github-kaydet', [\App\Http\Controllers\CareerController::c
|
|||||||
Route::get('/stajyer/gunluk-indir', [\App\Http\Controllers\CareerController::class, 'downloadMarkdown'])->name('intern.download-journal');
|
Route::get('/stajyer/gunluk-indir', [\App\Http\Controllers\CareerController::class, 'downloadMarkdown'])->name('intern.download-journal');
|
||||||
Route::post('/stajyer/defteri-kaydet', [\App\Http\Controllers\CareerController::class, 'saveJournalEntry'])->name('intern.save-journal');
|
Route::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('/stajyer/defteri-yazdir', [\App\Http\Controllers\CareerController::class, 'printJournal'])->name('intern.print-journal');
|
||||||
|
Route::post('/stajyer/blog-kaydet', [\App\Http\Controllers\CareerController::class, 'saveInternBlog'])->name('intern.blog.save');
|
||||||
|
Route::delete('/stajyer/blog/{id}/sil', [\App\Http\Controllers\CareerController::class, 'deleteInternBlog'])->name('intern.blog.delete');
|
||||||
Route::get('/staj-dogrulama/{code}', [\App\Http\Controllers\CareerController::class, 'verifyCertificate'])->name('internship.verify');
|
Route::get('/staj-dogrulama/{code}', [\App\Http\Controllers\CareerController::class, 'verifyCertificate'])->name('internship.verify');
|
||||||
Route::post('/stajyer/cikis', [\App\Http\Controllers\CareerController::class, 'internLogout'])->name('intern.logout');
|
Route::post('/stajyer/cikis', [\App\Http\Controllers\CareerController::class, 'internLogout'])->name('intern.logout');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user