diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php new file mode 100644 index 0000000..001e772 --- /dev/null +++ b/app/Http/Controllers/DashboardController.php @@ -0,0 +1,176 @@ +tasks()->count(); + $routinesCount = $user->routines()->count(); + $notesCount = $user->notes()->count(); + $remindersCount = $user->reminders()->count(); + $filesCount = $user->files()->count(); + $projectsCount = $user->projects()->count(); + + // Recent items + $recentTasks = $user->tasks() + ->with('project') + ->latest() + ->take(5) + ->get(); + + $recentNotes = $user->notes() + ->latest() + ->take(5) + ->get(); + + // Today's routines (based on current day and time) + $today = now()->format('l'); // Full day name (Monday, Tuesday, etc.) + $todayRoutines = $user->routines() + ->where('frequency', 'daily') + ->whereJsonContains('days', strtolower($today)) + ->get(); + + // Upcoming reminders + $upcomingReminders = $user->reminders() + ->where('date', '>=', now()) + ->orderBy('date') + ->take(5) + ->get(); + + // Additional statistics for analytics + $completedTasksThisWeek = $user->tasks() + ->where('status', 'completed') + ->whereDate('updated_at', '>=', now()->startOfWeek()) + ->count(); + + $totalTasks = max($user->tasks()->count(), 1); + $completedTasks = $user->tasks()->where('status', 'completed')->count(); + $completionRate = round(($completedTasks / $totalTasks) * 100); + + $activeProjects = $user->projects() + ->where('status', 'in_progress') + ->count(); + + $overdueTasks = $user->tasks() + ->where('due_date', '<', now()) + ->where('status', '!=', 'completed') + ->count(); + + // Task status distribution + $taskStatusDistribution = [ + 'to_do' => $user->tasks()->where('status', 'to_do')->count(), + 'in_progress' => $user->tasks()->where('status', 'in_progress')->count(), + 'completed' => $user->tasks()->where('status', 'completed')->count(), + ]; + + // Priority distribution (only non-completed tasks) + $priorityDistribution = [ + 'high' => $user->tasks()->where('priority', 'high')->where('status', '!=', 'completed')->count(), + 'medium' => $user->tasks()->where('priority', 'medium')->where('status', '!=', 'completed')->count(), + 'low' => $user->tasks()->where('priority', 'low')->where('status', '!=', 'completed')->count(), + ]; + + // Calculate priority percentages for progress bars + $totalNonCompletedTasks = max($user->tasks()->where('status', '!=', 'completed')->count(), 1); + $priorityPercentages = [ + 'high' => round(($priorityDistribution['high'] / $totalNonCompletedTasks) * 100), + 'medium' => round(($priorityDistribution['medium'] / $totalNonCompletedTasks) * 100), + 'low' => round(($priorityDistribution['low'] / $totalNonCompletedTasks) * 100), + ]; + + return view('dashboard', compact( + 'tasksCount', + 'routinesCount', + 'notesCount', + 'remindersCount', + 'filesCount', + 'projectsCount', + 'recentTasks', + 'todayRoutines', + 'recentNotes', + 'upcomingReminders', + 'completedTasksThisWeek', + 'completionRate', + 'activeProjects', + 'overdueTasks', + 'taskStatusDistribution', + 'priorityDistribution', + 'priorityPercentages' + )); + } + + /** + * Get productivity data for charts (AJAX endpoint). + */ + public function getProductivityData(Request $request) + { + $user = Auth::user(); + $period = $request->get('period', 'week'); + + $data = []; + $labels = []; + + switch ($period) { + case 'week': + $startDate = now()->startOfWeek(); + for ($i = 0; $i < 7; $i++) { + $date = $startDate->copy()->addDays($i); + $labels[] = $date->format('M j'); + $data[] = $user->tasks() + ->where('status', 'completed') + ->whereDate('updated_at', $date) + ->count(); + } + break; + + case 'month': + $startDate = now()->startOfMonth(); + $daysInMonth = now()->daysInMonth; + for ($i = 0; $i < $daysInMonth; $i++) { + $date = $startDate->copy()->addDays($i); + $labels[] = $date->format('j'); + $data[] = $user->tasks() + ->where('status', 'completed') + ->whereDate('updated_at', $date) + ->count(); + } + break; + + case 'year': + for ($i = 0; $i < 12; $i++) { + $date = now()->startOfYear()->addMonths($i); + $labels[] = $date->format('M'); + $data[] = $user->tasks() + ->where('status', 'completed') + ->whereYear('updated_at', now()->year) + ->whereMonth('updated_at', $date->month) + ->count(); + } + break; + } + + return response()->json([ + 'labels' => $labels, + 'data' => $data + ]); + } +} diff --git a/app/Http/Controllers/NoteController.php b/app/Http/Controllers/NoteController.php index fb5d28f..d27fd10 100644 --- a/app/Http/Controllers/NoteController.php +++ b/app/Http/Controllers/NoteController.php @@ -5,18 +5,59 @@ use App\Models\Note; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Http\JsonResponse; class NoteController extends Controller { - public function index() + public function index(Request $request) { - $notes = Auth::user()->notes()->latest()->get(); - return view('notes.index', compact('notes')); + $query = Auth::user()->notes()->latest(); + + // Handle search + if ($request->filled('search')) { + $query->search($request->search); + } + + // Handle category filter + if ($request->filled('category') && $request->category !== 'all') { + $query->byCategory($request->category); + } + + // Handle favorites filter + if ($request->filled('favorites') && $request->favorites == '1') { + $query->favorites(); + } + + $notes = $query->get(); + + // Get categories for filter + $categories = Auth::user()->notes() + ->whereNotNull('category') + ->where('category', '!=', '') + ->distinct() + ->pluck('category') + ->sort(); + + if ($request->ajax()) { + return response()->json([ + 'html' => view('notes.partials.notes-grid', compact('notes'))->render(), + 'count' => $notes->count() + ]); + } + + return view('notes.index', compact('notes', 'categories')); } public function create() { - return view('notes.create'); + $categories = Auth::user()->notes() + ->whereNotNull('category') + ->where('category', '!=', '') + ->distinct() + ->pluck('category') + ->sort(); + + return view('notes.create', compact('categories')); } public function store(Request $request) @@ -24,37 +65,111 @@ public function store(Request $request) $request->validate([ 'title' => 'required|string|max:255', 'content' => 'required|string', + 'category' => 'nullable|string|max:100', + 'tags' => 'nullable|string', + 'is_favorite' => 'boolean', 'date' => 'nullable|date', 'time' => 'nullable|date_format:H:i', ]); - Auth::user()->notes()->create($request->all()); + $data = $request->all(); + + // Process tags + if ($request->filled('tags')) { + $data['tags'] = array_map('trim', explode(',', $request->tags)); + } + + Auth::user()->notes()->create($data); return redirect()->route('notes.index')->with('success', 'Note created successfully.'); } + public function show(Note $note) + { + if ($note->user_id !== Auth::id()) { + abort(403); + } + return view('notes.show', compact('note')); + } + public function edit(Note $note) { - return view('notes.edit', compact('note')); + if ($note->user_id !== Auth::id()) { + abort(403); + } + + $categories = Auth::user()->notes() + ->whereNotNull('category') + ->where('category', '!=', '') + ->distinct() + ->pluck('category') + ->sort(); + + return view('notes.edit', compact('note', 'categories')); } public function update(Request $request, Note $note) { + if ($note->user_id !== Auth::id()) { + abort(403); + } + $request->validate([ 'title' => 'required|string|max:255', 'content' => 'required|string', + 'category' => 'nullable|string|max:100', + 'tags' => 'nullable|string', + 'is_favorite' => 'boolean', 'date' => 'nullable|date', 'time' => 'nullable|date_format:H:i', ]); - $note->update($request->all()); + $data = $request->all(); + + // Process tags + if ($request->filled('tags')) { + $data['tags'] = array_map('trim', explode(',', $request->tags)); + } + + $note->update($data); return redirect()->route('notes.index')->with('success', 'Note updated successfully.'); } public function destroy(Note $note) { + if ($note->user_id !== Auth::id()) { + abort(403); + } + $note->delete(); return redirect()->route('notes.index')->with('success', 'Note deleted successfully.'); } + + public function toggleFavorite(Note $note): JsonResponse + { + if ($note->user_id !== Auth::id()) { + abort(403); + } + + $note->update(['is_favorite' => !$note->is_favorite]); + + return response()->json([ + 'success' => true, + 'is_favorite' => $note->is_favorite + ]); + } + + public function duplicate(Note $note) + { + if ($note->user_id !== Auth::id()) { + abort(403); + } + + $newNote = $note->replicate(); + $newNote->title = $note->title . ' (Copy)'; + $newNote->save(); + + return redirect()->route('notes.index')->with('success', 'Note duplicated successfully.'); + } } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php new file mode 100644 index 0000000..b318860 --- /dev/null +++ b/app/Http/Controllers/ProfileController.php @@ -0,0 +1,114 @@ +validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,' . $user->id], + 'avatar' => ['nullable', 'image', 'mimes:jpeg,png,jpg,gif', 'max:2048'], + 'bio' => ['nullable', 'string', 'max:500'], + 'phone' => ['nullable', 'string', 'max:20'], + 'location' => ['nullable', 'string', 'max:255'], + 'website' => ['nullable', 'url', 'max:255'], + ]); + + $updateData = [ + 'name' => $request->name, + 'email' => $request->email, + 'bio' => $request->bio, + 'phone' => $request->phone, + 'location' => $request->location, + 'website' => $request->website, + ]; + + // Handle avatar upload + if ($request->hasFile('avatar')) { + // Delete old avatar if exists + if ($user->avatar && Storage::disk('public')->exists($user->avatar)) { + Storage::disk('public')->delete($user->avatar); + } + + $avatarPath = $request->file('avatar')->store('avatars', 'public'); + $updateData['avatar'] = $avatarPath; + } + + // Update user information + $user->update($updateData); + + return redirect()->route('profile.show')->with('success', 'Profile updated successfully!'); + } + + /** + * Show the password change form. + */ + public function showPasswordForm() + { + return view('profile.password'); + } + + /** + * Update the user's password. + */ + public function updatePassword(Request $request) + { + $request->validate([ + 'current_password' => ['required', 'current_password'], + 'password' => ['required', 'confirmed', Password::min(8)], + ]); + + Auth::user()->update([ + 'password' => Hash::make($request->password), + ]); + + return redirect()->route('profile.show')->with('success', 'Password updated successfully!'); + } + + /** + * Delete the user's avatar. + */ + public function deleteAvatar() + { + $user = Auth::user(); + + if ($user->avatar && Storage::disk('public')->exists($user->avatar)) { + Storage::disk('public')->delete($user->avatar); + } + + $user->update(['avatar' => null]); + + return response()->json(['success' => true]); + } +} diff --git a/app/Http/Controllers/ReminderController.php b/app/Http/Controllers/ReminderController.php index 6af4728..c88726e 100644 --- a/app/Http/Controllers/ReminderController.php +++ b/app/Http/Controllers/ReminderController.php @@ -5,18 +5,133 @@ use App\Models\Reminder; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Http\JsonResponse; +use Carbon\Carbon; class ReminderController extends Controller { - public function index() + public function index(Request $request) { - $reminders = Auth::user()->reminders()->latest()->get(); - return view('reminders.index', compact('reminders')); + $query = Auth::user()->reminders()->latest(); + + // Handle search + if ($request->filled('search')) { + $query->search($request->search); + } + + // Handle category filter + if ($request->filled('category') && $request->category !== 'all') { + $query->byCategory($request->category); + } + + // Handle priority filter + if ($request->filled('priority') && $request->priority !== 'all') { + $query->byPriority($request->priority); + } + + // Handle status filter + if ($request->filled('status')) { + switch ($request->status) { + case 'active': + $query->active(); + break; + case 'completed': + $query->completed(); + break; + case 'overdue': + $query->overdue(); + break; + case 'due_today': + $query->dueToday(); + break; + case 'due_soon': + $query->dueSoon(); + break; + } + } else { + // Default to active reminders + $query->active(); + } + + // Handle view type (calendar or list) + $view = $request->get('view', 'list'); + + $reminders = $query->get(); + + // Get categories and priorities for filters + $categories = Auth::user()->reminders() + ->whereNotNull('category') + ->where('category', '!=', '') + ->distinct() + ->pluck('category') + ->sort(); + + $priorities = [ + Reminder::PRIORITY_LOW => 'Low', + Reminder::PRIORITY_MEDIUM => 'Medium', + Reminder::PRIORITY_HIGH => 'High', + Reminder::PRIORITY_URGENT => 'Urgent' + ]; + + // Statistics + $stats = [ + 'total' => Auth::user()->reminders()->count(), + 'active' => Auth::user()->reminders()->active()->count(), + 'completed' => Auth::user()->reminders()->completed()->count(), + 'overdue' => Auth::user()->reminders()->overdue()->count(), + 'due_today' => Auth::user()->reminders()->dueToday()->count(), + ]; + + if ($request->ajax()) { + if ($view === 'calendar') { + // Return calendar events format + $events = $reminders->map(function ($reminder) { + return [ + 'id' => $reminder->id, + 'title' => $reminder->title, + 'start' => $reminder->formatted_date_time?->toISOString(), + 'backgroundColor' => $reminder->priority_color, + 'borderColor' => $reminder->priority_color, + 'textColor' => '#fff', + 'extendedProps' => [ + 'priority' => $reminder->priority, + 'category' => $reminder->category, + 'description' => $reminder->description, + 'is_completed' => $reminder->is_completed, + 'is_overdue' => $reminder->is_overdue, + ] + ]; + }); + + return response()->json($events); + } else { + return response()->json([ + 'html' => view('reminders.partials.reminders-grid', compact('reminders'))->render(), + 'count' => $reminders->count() + ]); + } + } + + return view('reminders.index', compact('reminders', 'categories', 'priorities', 'stats', 'view')); } public function create() { - return view('reminders.create'); + $categories = Auth::user()->reminders() + ->whereNotNull('category') + ->where('category', '!=', '') + ->distinct() + ->pluck('category') + ->sort(); + + $priorities = [ + Reminder::PRIORITY_LOW => 'Low', + Reminder::PRIORITY_MEDIUM => 'Medium', + Reminder::PRIORITY_HIGH => 'High', + Reminder::PRIORITY_URGENT => 'Urgent' + ]; + + return view('reminders.create', compact('categories', 'priorities')); } public function store(Request $request) @@ -26,35 +141,196 @@ public function store(Request $request) 'description' => 'nullable|string', 'date' => 'nullable|date', 'time' => 'nullable|date_format:H:i', + 'priority' => 'required|in:low,medium,high,urgent', + 'category' => 'nullable|string|max:100', + 'location' => 'nullable|string|max:255', + 'tags' => 'nullable|string', + 'is_recurring' => 'boolean', + 'recurrence_type' => 'required_if:is_recurring,1|in:none,daily,weekly,monthly,yearly', + 'recurrence_interval' => 'nullable|integer|min:1', ]); - Auth::user()->reminders()->create($request->all()); + $data = $request->all(); + + // Process tags + if ($request->filled('tags')) { + $data['tags'] = array_map('trim', explode(',', $request->tags)); + } + + // Set default recurrence values + if (!$request->is_recurring) { + $data['recurrence_type'] = Reminder::RECURRENCE_NONE; + $data['recurrence_interval'] = 1; + } + + Auth::user()->reminders()->create($data); return redirect()->route('reminders.index')->with('success', 'Reminder created successfully.'); } + public function show(Reminder $reminder) + { + if ($reminder->user_id !== Auth::id()) { + abort(403); + } + + return view('reminders.show', compact('reminder')); + } + public function edit(Reminder $reminder) { - return view('reminders.edit', compact('reminder')); + if ($reminder->user_id !== Auth::id()) { + abort(403); + } + + $categories = Auth::user()->reminders() + ->whereNotNull('category') + ->where('category', '!=', '') + ->distinct() + ->pluck('category') + ->sort(); + + $priorities = [ + Reminder::PRIORITY_LOW => 'Low', + Reminder::PRIORITY_MEDIUM => 'Medium', + Reminder::PRIORITY_HIGH => 'High', + Reminder::PRIORITY_URGENT => 'Urgent' + ]; + + return view('reminders.edit', compact('reminder', 'categories', 'priorities')); } public function update(Request $request, Reminder $reminder) { + if ($reminder->user_id !== Auth::id()) { + abort(403); + } + $request->validate([ 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'date' => 'nullable|date', 'time' => 'nullable|date_format:H:i', + 'priority' => 'required|in:low,medium,high,urgent', + 'category' => 'nullable|string|max:100', + 'location' => 'nullable|string|max:255', + 'tags' => 'nullable|string', + 'is_recurring' => 'boolean', + 'recurrence_type' => 'required_if:is_recurring,1|in:none,daily,weekly,monthly,yearly', + 'recurrence_interval' => 'nullable|integer|min:1', ]); - $reminder->update($request->all()); + $data = $request->all(); + + // Process tags + if ($request->filled('tags')) { + $data['tags'] = array_map('trim', explode(',', $request->tags)); + } + + // Set default recurrence values + if (!$request->is_recurring) { + $data['recurrence_type'] = Reminder::RECURRENCE_NONE; + $data['recurrence_interval'] = 1; + } + + $reminder->update($data); return redirect()->route('reminders.index')->with('success', 'Reminder updated successfully.'); } public function destroy(Reminder $reminder) { + if ($reminder->user_id !== Auth::id()) { + abort(403); + } + $reminder->delete(); return redirect()->route('reminders.index')->with('success', 'Reminder deleted successfully.'); } + + public function toggleComplete(Reminder $reminder): \Illuminate\Http\JsonResponse + { + if ($reminder->user_id !== Auth::id()) { + abort(403); + } + + if ($reminder->is_completed) { + $reminder->update([ + 'is_completed' => false, + 'completed_at' => null + ]); + } else { + $reminder->markAsCompleted(); + } + + return response()->json([ + 'success' => true, + 'is_completed' => $reminder->fresh()->is_completed, + 'completed_at' => $reminder->fresh()->completed_at?->format('M j, Y g:i A') + ]); + } + + public function snooze(Request $request, Reminder $reminder): \Illuminate\Http\JsonResponse + { + if ($reminder->user_id !== Auth::id()) { + abort(403); + } + + $request->validate([ + 'minutes' => 'required|integer|min:1|max:1440' // Max 24 hours + ]); + + $reminder->snooze($request->minutes); + + return response()->json([ + 'success' => true, + 'snooze_until' => $reminder->fresh()->snooze_until?->format('M j, Y g:i A') + ]); + } + + public function duplicate(Reminder $reminder) + { + if ($reminder->user_id !== Auth::id()) { + abort(403); + } + + $newReminder = $reminder->replicate([ + 'is_completed', + 'completed_at', + 'notification_sent', + 'snooze_until' + ]); + $newReminder->title = $reminder->title . ' (Copy)'; + $newReminder->is_completed = false; + $newReminder->completed_at = null; + $newReminder->notification_sent = false; + $newReminder->snooze_until = null; + $newReminder->save(); + + return redirect()->route('reminders.index')->with('success', 'Reminder duplicated successfully.'); + } + + public function calendar() + { + $reminders = Auth::user()->reminders()->active()->get(); + + $events = $reminders->map(function ($reminder) { + return [ + 'id' => $reminder->id, + 'title' => $reminder->title, + 'start' => $reminder->formatted_date_time?->toISOString(), + 'backgroundColor' => $reminder->priority_color, + 'borderColor' => $reminder->priority_color, + 'textColor' => '#fff', + 'extendedProps' => [ + 'priority' => $reminder->priority, + 'category' => $reminder->category, + 'description' => $reminder->description, + 'is_overdue' => $reminder->is_overdue, + ] + ]; + }); + + return view('reminders.calendar', compact('events')); + } } diff --git a/app/Http/Controllers/TaskController.php b/app/Http/Controllers/TaskController.php index 82d7c79..6837570 100644 --- a/app/Http/Controllers/TaskController.php +++ b/app/Http/Controllers/TaskController.php @@ -3,38 +3,85 @@ use App\Models\Project; use App\Models\Task; +use App\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; class TaskController extends Controller { - public function index(Project $project) + public function index(Project $project = null) { - $tasks = $project->tasks()->get()->groupBy('status'); - $users = $project->users()->get(); - return view('tasks.index', compact('project', 'tasks', 'users')); + $user = Auth::user(); + + if ($project) { + // Show tasks for a specific project + $tasks = Task::where('user_id', $user->id) + ->where('project_id', $project->id) + ->with('project') + ->get() + ->groupBy('status'); + } else { + // Show all tasks excluding those from completed projects + $tasks = Task::where('user_id', $user->id) + ->whereHas('project', function ($query) { + $query->where('status', '!=', 'completed'); + }) + ->with('project') + ->get() + ->groupBy('status'); + } + + $projects = Project::all(); + $users = User::all(); + + return view('tasks.index', compact('tasks', 'projects', 'users', 'project')); + } + + public function create() + { + $projects = Project::all(); + $users = User::all(); + + return view('tasks.create', compact('projects', 'users')); } - public function store(Request $request, Project $project) + public function store(Request $request, Project $project = null) { $request->validate([ + 'project_id' => 'required|exists:projects,id', 'user_id' => 'required|exists:users,id', 'title' => 'required|string|max:255', 'description' => 'nullable|string', 'due_date' => 'nullable|date', 'priority' => 'required|in:low,medium,high', + 'status' => 'required|in:to_do,in_progress,completed', + 'estimated_hours' => 'nullable|numeric|min:0.5', ]); - $project->tasks()->create($request->all()); + Task::create($request->all()); - return redirect()->route('projects.tasks.index', $project)->with('success', 'Task created successfully.'); + // Redirect based on context + if ($project) { + return redirect()->route('projects.tasks.index', $project)->with('success', 'Task created successfully.'); + } else { + return redirect()->route('tasks.index')->with('success', 'Task created successfully.'); + } } public function show(Task $task) { + $task->load('user', 'project', 'checklistItems'); return view('tasks.show', compact('task')); } + public function edit(Task $task) + { + $projects = Project::all(); + $users = User::all(); + + return view('tasks.edit', compact('task', 'projects', 'users')); + } + public function update(Request $request, Task $task) { $request->validate([ @@ -43,11 +90,19 @@ public function update(Request $request, Task $task) 'due_date' => 'nullable|date', 'priority' => 'required|in:low,medium,high', 'status' => 'required|in:to_do,in_progress,completed', + 'estimated_hours' => 'nullable|numeric|min:0.5', ]); $task->update($request->all()); - return redirect()->route('projects.tasks.index', $task->project_id)->with('success', 'Task updated successfully.'); + return redirect()->route('tasks.show', $task->id)->with('success', 'Task updated successfully.'); + } + + public function destroy(Task $task) + { + $task->delete(); + + return redirect()->route('tasks.index')->with('success', 'Task deleted successfully.'); } public function updateStatus(Request $request, Task $task) diff --git a/app/Models/Note.php b/app/Models/Note.php index 84f650f..642a552 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -3,6 +3,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Carbon\Carbon; class Note extends Model { @@ -12,12 +13,60 @@ class Note extends Model 'user_id', 'title', 'content', + 'category', + 'tags', + 'is_favorite', 'date', 'time', ]; + protected $casts = [ + 'is_favorite' => 'boolean', + 'tags' => 'array', + 'date' => 'datetime', + ]; + public function user() { return $this->belongsTo(User::class); } + + public function getExcerptAttribute() + { + return strip_tags(substr($this->content, 0, 150)) . (strlen(strip_tags($this->content)) > 150 ? '...' : ''); + } + + public function getWordCountAttribute() + { + return str_word_count(strip_tags($this->content)); + } + + public function getFormattedDateAttribute() + { + return $this->date ? $this->date->format('M j, Y') : null; + } + + public function getFormattedTimeAttribute() + { + return $this->time ? Carbon::parse($this->time)->format('g:i A') : null; + } + + public function scopeFavorites($query) + { + return $query->where('is_favorite', true); + } + + public function scopeByCategory($query, $category) + { + return $query->where('category', $category); + } + + public function scopeSearch($query, $search) + { + return $query->where(function ($q) use ($search) { + $q->where('title', 'like', "%{$search}%") + ->orWhere('content', 'like', "%{$search}%") + ->orWhere('category', 'like', "%{$search}%"); + }); + } } diff --git a/app/Models/Reminder.php b/app/Models/Reminder.php index c9a7d34..7d3851c 100644 --- a/app/Models/Reminder.php +++ b/app/Models/Reminder.php @@ -4,21 +4,225 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Carbon\Carbon; class Reminder extends Model { use HasFactory; protected $fillable = [ + 'user_id', 'title', 'description', 'date', 'time', + 'priority', + 'category', + 'is_recurring', + 'recurrence_type', + 'recurrence_interval', + 'is_completed', + 'completed_at', + 'snooze_until', + 'notification_sent', + 'location', + 'tags', ]; + protected $casts = [ + 'date' => 'datetime', + 'completed_at' => 'datetime', + 'snooze_until' => 'datetime', + 'is_recurring' => 'boolean', + 'is_completed' => 'boolean', + 'notification_sent' => 'boolean', + 'tags' => 'array', + ]; + + const PRIORITY_LOW = 'low'; + const PRIORITY_MEDIUM = 'medium'; + const PRIORITY_HIGH = 'high'; + const PRIORITY_URGENT = 'urgent'; + + const RECURRENCE_NONE = 'none'; + const RECURRENCE_DAILY = 'daily'; + const RECURRENCE_WEEKLY = 'weekly'; + const RECURRENCE_MONTHLY = 'monthly'; + const RECURRENCE_YEARLY = 'yearly'; + public function user() { return $this->belongsTo(User::class); } + public function getFormattedDateAttribute() + { + return $this->date ? $this->date->format('M j, Y') : null; + } + + public function getFormattedTimeAttribute() + { + return $this->time ? Carbon::parse($this->time)->format('g:i A') : null; + } + + public function getFormattedDateTimeAttribute() + { + if (!$this->date) return null; + + $dateTime = $this->date; + if ($this->time) { + $time = Carbon::parse($this->time); + $dateTime = $dateTime->setTime($time->hour, $time->minute); + } + + return $dateTime; + } + + public function getPriorityColorAttribute() + { + return match($this->priority) { + self::PRIORITY_LOW => '#10b981', + self::PRIORITY_MEDIUM => '#f59e0b', + self::PRIORITY_HIGH => '#ef4444', + self::PRIORITY_URGENT => '#dc2626', + default => '#64748b' + }; + } + + public function getPriorityLabelAttribute() + { + return ucfirst($this->priority ?? 'medium'); + } + + public function getIsOverdueAttribute() + { + if ($this->is_completed || !$this->formatted_date_time) { + return false; + } + + return $this->formatted_date_time->isPast(); + } + + public function getIsDueSoonAttribute() + { + if ($this->is_completed || !$this->formatted_date_time) { + return false; + } + + return $this->formatted_date_time->isBetween(now(), now()->addHours(24)); + } + + public function getTimeUntilAttribute() + { + if (!$this->formatted_date_time) return null; + + return $this->formatted_date_time->diffForHumans(); + } + + // Scopes + public function scopeActive($query) + { + return $query->where('is_completed', false); + } + + public function scopeCompleted($query) + { + return $query->where('is_completed', true); + } + + public function scopeOverdue($query) + { + return $query->active() + ->whereNotNull('date') + ->whereRaw('CONCAT(date, " ", COALESCE(time, "00:00:00")) < NOW()'); + } + + public function scopeDueToday($query) + { + return $query->active() + ->whereDate('date', today()); + } + + public function scopeDueSoon($query) + { + return $query->active() + ->whereBetween('date', [now(), now()->addHours(24)]); + } + + public function scopeByPriority($query, $priority) + { + return $query->where('priority', $priority); + } + + public function scopeByCategory($query, $category) + { + return $query->where('category', $category); + } + + public function scopeSearch($query, $search) + { + return $query->where(function ($q) use ($search) { + $q->where('title', 'like', "%{$search}%") + ->orWhere('description', 'like', "%{$search}%") + ->orWhere('category', 'like', "%{$search}%") + ->orWhere('location', 'like', "%{$search}%"); + }); + } + + // Methods + public function markAsCompleted() + { + $this->update([ + 'is_completed' => true, + 'completed_at' => now() + ]); + + // Create next occurrence if recurring + if ($this->is_recurring && $this->recurrence_type !== self::RECURRENCE_NONE) { + $this->createNextOccurrence(); + } + } + + public function snooze($minutes = 15) + { + $this->update([ + 'snooze_until' => now()->addMinutes($minutes), + 'notification_sent' => false + ]); + } + + protected function createNextOccurrence() + { + if (!$this->date) return; + + $nextDate = $this->calculateNextDate(); + if ($nextDate) { + $this->replicate([ + 'is_completed', + 'completed_at', + 'notification_sent', + 'snooze_until' + ])->fill([ + 'date' => $nextDate, + 'is_completed' => false, + 'completed_at' => null, + 'notification_sent' => false, + 'snooze_until' => null, + ])->save(); + } + } + + protected function calculateNextDate() + { + $currentDate = $this->date; + $interval = $this->recurrence_interval ?: 1; + + return match($this->recurrence_type) { + self::RECURRENCE_DAILY => $currentDate->addDays($interval), + self::RECURRENCE_WEEKLY => $currentDate->addWeeks($interval), + self::RECURRENCE_MONTHLY => $currentDate->addMonths($interval), + self::RECURRENCE_YEARLY => $currentDate->addYears($interval), + default => null + }; + } } diff --git a/app/Models/Task.php b/app/Models/Task.php index de24c3a..fb3332e 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -16,6 +16,7 @@ class Task extends Model 'due_date', 'priority', 'status', + 'estimated_hours', ]; public function user() diff --git a/app/Models/User.php b/app/Models/User.php index 514e382..7cd18fa 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -20,6 +20,11 @@ class User extends Authenticatable 'name', 'email', 'password', + 'avatar', + 'bio', + 'phone', + 'location', + 'website', ]; /** diff --git a/database/migrations/2024_05_21_195305_create_tasks_table.php b/database/migrations/2024_05_21_195305_create_tasks_table.php index 21a4590..4fb46b5 100644 --- a/database/migrations/2024_05_21_195305_create_tasks_table.php +++ b/database/migrations/2024_05_21_195305_create_tasks_table.php @@ -20,6 +20,7 @@ public function up(): void $table->date('due_date')->nullable(); $table->enum('priority', ['low', 'medium', 'high']); $table->enum('status', ['to_do', 'in_progress', 'completed'])->default('to_do'); + $table->decimal('estimated_hours', 5, 2)->nullable(); $table->timestamps(); }); } diff --git a/database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php b/database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php new file mode 100644 index 0000000..a30e698 --- /dev/null +++ b/database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php @@ -0,0 +1,30 @@ +string('category')->nullable()->after('content'); + $table->json('tags')->nullable()->after('category'); + $table->boolean('is_favorite')->default(false)->after('tags'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('notes', function (Blueprint $table) { + $table->dropColumn(['category', 'tags', 'is_favorite']); + }); + } +}; diff --git a/database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php b/database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php new file mode 100644 index 0000000..02fdfdd --- /dev/null +++ b/database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php @@ -0,0 +1,42 @@ +enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium')->after('description'); + $table->string('category')->nullable()->after('priority'); + $table->boolean('is_recurring')->default(false)->after('category'); + $table->enum('recurrence_type', ['none', 'daily', 'weekly', 'monthly', 'yearly'])->default('none')->after('is_recurring'); + $table->integer('recurrence_interval')->default(1)->after('recurrence_type'); + $table->boolean('is_completed')->default(false)->after('recurrence_interval'); + $table->timestamp('completed_at')->nullable()->after('is_completed'); + $table->timestamp('snooze_until')->nullable()->after('completed_at'); + $table->boolean('notification_sent')->default(false)->after('snooze_until'); + $table->string('location')->nullable()->after('notification_sent'); + $table->json('tags')->nullable()->after('location'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('reminders', function (Blueprint $table) { + $table->dropColumn([ + 'priority', 'category', 'is_recurring', 'recurrence_type', + 'recurrence_interval', 'is_completed', 'completed_at', + 'snooze_until', 'notification_sent', 'location', 'tags' + ]); + }); + } +}; diff --git a/database/migrations/2025_09_11_044754_add_profile_fields_to_users_table.php b/database/migrations/2025_09_11_044754_add_profile_fields_to_users_table.php new file mode 100644 index 0000000..67085c9 --- /dev/null +++ b/database/migrations/2025_09_11_044754_add_profile_fields_to_users_table.php @@ -0,0 +1,32 @@ +string('avatar')->nullable()->after('email'); + $table->text('bio')->nullable()->after('avatar'); + $table->string('phone', 20)->nullable()->after('bio'); + $table->string('location')->nullable()->after('phone'); + $table->string('website')->nullable()->after('location'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['avatar', 'bio', 'phone', 'location', 'website']); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 32718ea..2f237f3 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -18,5 +18,6 @@ public function run(): void 'email' => 'admin@example.com', 'password' => bcrypt('secret'), ]); + } } diff --git a/database/seeders/TestDataSeeder.php b/database/seeders/TestDataSeeder.php new file mode 100644 index 0000000..23709e9 --- /dev/null +++ b/database/seeders/TestDataSeeder.php @@ -0,0 +1,179 @@ + 'Test User', + 'email' => 'test@example.com', + 'password' => bcrypt('password'), + ]); + } + + // Create test projects + $project1 = Project::firstOrCreate( + ['name' => 'Website Development', 'user_id' => $user->id], + [ + 'description' => 'Building a new company website', + 'status' => 'in_progress', + 'start_date' => now()->subDays(10), + 'end_date' => now()->addDays(20), + 'budget' => 5000.00, + ] + ); + + $project2 = Project::firstOrCreate( + ['name' => 'Mobile App', 'user_id' => $user->id], + [ + 'description' => 'Developing mobile application', + 'status' => 'not_started', + 'start_date' => now()->subDays(5), + 'end_date' => now()->addDays(30), + 'budget' => 8000.00, + ] + ); + + // Create test tasks with different statuses and dates for productivity chart + $taskData = [ + ['title' => 'Design homepage', 'status' => 'completed', 'priority' => 'high', 'days_ago' => 6], + ['title' => 'Setup database', 'status' => 'completed', 'priority' => 'high', 'days_ago' => 5], + ['title' => 'Create API endpoints', 'status' => 'completed', 'priority' => 'medium', 'days_ago' => 4], + ['title' => 'Build user authentication', 'status' => 'completed', 'priority' => 'high', 'days_ago' => 3], + ['title' => 'Implement dashboard', 'status' => 'completed', 'priority' => 'medium', 'days_ago' => 2], + ['title' => 'Add charts functionality', 'status' => 'completed', 'priority' => 'medium', 'days_ago' => 1], + ['title' => 'Write documentation', 'status' => 'in_progress', 'priority' => 'low', 'days_ago' => 0], + ['title' => 'Testing phase', 'status' => 'to_do', 'priority' => 'high', 'days_ago' => 0], + ['title' => 'Deploy to production', 'status' => 'to_do', 'priority' => 'high', 'days_ago' => 0], + ['title' => 'Mobile app wireframes', 'status' => 'in_progress', 'priority' => 'medium', 'days_ago' => 0], + ['title' => 'UI/UX design', 'status' => 'to_do', 'priority' => 'medium', 'days_ago' => 0], + ]; + + foreach ($taskData as $data) { + $task = Task::firstOrCreate( + [ + 'title' => $data['title'], + 'project_id' => $project1->id, + 'user_id' => $user->id, + ], + [ + 'description' => 'Task description for ' . $data['title'], + 'status' => $data['status'], + 'priority' => $data['priority'], + 'due_date' => now()->addDays(rand(1, 30)), + 'created_at' => now()->subDays($data['days_ago']), + 'updated_at' => $data['status'] === 'completed' ? now()->subDays($data['days_ago']) : now(), + ] + ); + } + + // Add checklist items for some tasks + $sampleTask = Task::where('title', 'Write documentation')->first(); + if ($sampleTask) { + ChecklistItem::firstOrCreate([ + 'task_id' => $sampleTask->id, + 'name' => 'Create user guide' + ], ['completed' => true]); + + ChecklistItem::firstOrCreate([ + 'task_id' => $sampleTask->id, + 'name' => 'Write API documentation' + ], ['completed' => true]); + + ChecklistItem::firstOrCreate([ + 'task_id' => $sampleTask->id, + 'name' => 'Add code examples' + ], ['completed' => false]); + + ChecklistItem::firstOrCreate([ + 'task_id' => $sampleTask->id, + 'name' => 'Review and proofread' + ], ['completed' => false]); + } + + // Create some notes + Note::firstOrCreate( + ['title' => 'Project Ideas', 'user_id' => $user->id], + [ + 'content' => 'Ideas for future projects and improvements', + 'category' => 'ideas', + 'is_favorite' => true, + ] + ); + + Note::firstOrCreate( + ['title' => 'Meeting Notes', 'user_id' => $user->id], + [ + 'content' => 'Notes from the team meeting about project progress', + 'category' => 'meetings', + 'is_favorite' => false, + ] + ); + + // Create some reminders + Reminder::firstOrCreate( + ['title' => 'Team standup', 'user_id' => $user->id], + [ + 'description' => 'Daily team standup meeting', + 'date' => now()->addHours(2)->format('Y-m-d'), + 'time' => now()->addHours(2)->format('H:i'), + 'priority' => 'medium', + 'is_completed' => false, + ] + ); + + Reminder::firstOrCreate( + ['title' => 'Project deadline', 'user_id' => $user->id], + [ + 'description' => 'Website project deadline reminder', + 'date' => now()->addDays(20)->format('Y-m-d'), + 'time' => '09:00', + 'priority' => 'high', + 'is_completed' => false, + ] + ); + + // Create some routines + Routine::firstOrCreate( + ['title' => 'Morning Workout', 'user_id' => $user->id], + [ + 'description' => 'Daily morning exercise routine', + 'frequency' => 'daily', + 'days' => json_encode(['monday', 'tuesday', 'wednesday', 'thursday', 'friday']), + 'start_time' => '07:00', + 'end_time' => '08:00', + ] + ); + + // Create some files + File::firstOrCreate( + ['name' => 'Project Documentation.pdf', 'user_id' => $user->id], + [ + 'path' => 'files/project-docs.pdf', + 'type' => 'pdf', + ] + ); + + $this->command->info('Test data created successfully!'); + } +} diff --git a/public/assets/script.js b/public/assets/dashboard/script.js similarity index 100% rename from public/assets/script.js rename to public/assets/dashboard/script.js diff --git a/public/assets/dashboard/style.css b/public/assets/dashboard/style.css new file mode 100644 index 0000000..099d33c --- /dev/null +++ b/public/assets/dashboard/style.css @@ -0,0 +1,773 @@ +.welcome-section { + padding: 2rem 0; + margin-bottom: 1rem; +} + +.welcome-title { + font-size: 2rem; + font-weight: 600; + color: var(--gray-900); + margin-bottom: 0.5rem; +} + +.welcome-subtitle { + font-size: 1.125rem; + color: var(--gray-500); + margin: 0; +} + +.stat-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + padding: 1.5rem; + position: relative; + transition: all 0.15s ease; + height: 100%; + display: flex; + flex-direction: column; +} + +.stat-card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-2px); +} + +.stat-card-icon { + width: 48px; + height: 48px; + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 1rem; + font-size: 1.5rem; + color: white; +} + +.stat-card-primary .stat-card-icon { + background-color: var(--primary-500); +} + +.stat-card-success .stat-card-icon { + background-color: var(--success-500); +} + +.stat-card-warning .stat-card-icon { + background-color: var(--warning-500); +} + +.stat-card-info .stat-card-icon { + background-color: #3b82f6; +} + +.stat-card-number { + font-size: 2rem; + font-weight: 700; + color: var(--gray-900); + line-height: 1; + margin-bottom: 0.5rem; +} + +.stat-card-label { + color: var(--gray-500); + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 1rem; +} + +.stat-card-action { + display: flex; + align-items: center; + justify-content: space-between; + color: var(--primary-600); + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; + margin-top: auto; + padding-top: 1rem; + border-top: 1px solid var(--gray-100); + transition: color 0.15s ease; +} + +.stat-card-action:hover { + color: var(--primary-700); +} + +.activity-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + height: 100%; + display: flex; + flex-direction: column; +} + +.activity-card-header { + padding: 1.5rem 1.5rem 0; + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1rem; +} + +.activity-card-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 600; + color: var(--gray-900); + font-size: 1.125rem; +} + +.activity-card-content { + flex: 1; + padding: 0 1.5rem 1.5rem; +} + +.activity-item { + display: flex; + align-items: flex-start; + gap: 0.75rem; + padding: 0.75rem 0; + border-bottom: 1px solid var(--gray-100); +} + +.activity-item:last-child { + border-bottom: none; +} + +.activity-item-icon { + width: 32px; + height: 32px; + border-radius: var(--radius-md); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + font-size: 1rem; +} + +.task-status-to_do .activity-item-icon { + background-color: var(--gray-100); + color: var(--gray-500); +} + +.task-status-in_progress .activity-item-icon { + background-color: var(--primary-100); + color: var(--primary-600); +} + +.task-status-completed .activity-item-icon { + background-color: var(--success-100); + color: var(--success-600); +} + +.routine-frequency .activity-item-icon { + background-color: var(--warning-100); + color: var(--warning-600); +} + +.note-icon .activity-item-icon { + background-color: #ddd6fe; + color: #7c3aed; +} + +.reminder-today .activity-item-icon { + background-color: var(--warning-100); + color: var(--warning-600); +} + +.reminder-overdue .activity-item-icon { + background-color: var(--error-100); + color: var(--error-600); +} + +.reminder-upcoming .activity-item-icon { + background-color: var(--primary-100); + color: var(--primary-600); +} + +.activity-item-content { + flex: 1; + min-width: 0; +} + +.activity-item-title { + font-weight: 500; + color: var(--gray-900); + margin-bottom: 0.25rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.activity-item-meta { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.75rem; +} + +.task-status-badge, +.routine-frequency-badge, +.reminder-status-badge { + padding: 0.125rem 0.5rem; + border-radius: 12px; + font-weight: 500; + font-size: 0.75rem; +} + +.status-to_do { + background-color: var(--gray-100); + color: var(--gray-600); +} + +.status-in_progress { + background-color: var(--primary-100); + color: var(--primary-600); +} + +.status-completed { + background-color: var(--success-100); + color: var(--success-600); +} + +.status-today { + background-color: var(--warning-100); + color: var(--warning-600); +} + +.status-overdue { + background-color: var(--error-100); + color: var(--error-600); +} + +.status-upcoming { + background-color: var(--primary-100); + color: var(--primary-600); +} + +.routine-frequency-badge { + background-color: var(--warning-100); + color: var(--warning-600); +} + +.activity-item-date { + color: var(--gray-400); +} + +.empty-state { + text-align: center; + padding: 3rem 1rem; + color: var(--gray-500); +} + +.empty-state i { + font-size: 2.5rem; + margin-bottom: 1rem; + opacity: 0.5; +} + +.empty-state p { + margin-bottom: 1rem; + font-size: 0.875rem; +} + +.mb-6 { + margin-bottom: 3rem !important; +} + +/* Analytics Card Styles */ +.analytics-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + height: 100%; +} + +.analytics-card-header { + padding: 1.5rem 1.5rem 1rem; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--gray-200); +} + +.analytics-card-title { + display: flex; + align-items: center; + gap: 0.5rem; + font-weight: 600; + color: var(--gray-900); + font-size: 1.125rem; +} + +.analytics-card-actions .form-select { + border: 1px solid var(--gray-300); + font-size: 0.875rem; + padding: 0.25rem 0.75rem; +} + +.analytics-card-content { + padding: 1.5rem; +} + +.metric-widget { + text-align: center; + padding: 1rem; + border-radius: var(--radius-md); + background: var(--gray-50); +} + +.metric-value { + font-size: 2rem; + font-weight: 700; + color: var(--gray-900); + margin-bottom: 0.25rem; +} + +.metric-label { + font-size: 0.875rem; + color: var(--gray-500); + font-weight: 500; + margin-bottom: 0.5rem; +} + +.metric-trend { + font-size: 0.75rem; + font-weight: 500; + padding: 0.125rem 0.5rem; + border-radius: 12px; +} + +.metric-trend.positive { + background-color: #f0fdf4; + color: var(--success-600); +} + +.metric-trend.negative { + background-color: #fef2f2; + color: var(--error-600); +} + +.metric-trend.neutral { + background-color: var(--gray-100); + color: var(--gray-600); +} + +.chart-container { + position: relative; + height: 300px; + margin-top: 1rem; +} + +/* Quick Actions Card */ +.quick-actions-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + padding: 1.5rem; +} + +.quick-actions-header h3 { + font-size: 1.125rem; + font-weight: 600; + color: var(--gray-900); + margin-bottom: 1rem; +} + +.quick-actions-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; +} + +.quick-action-item { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 1rem; + border-radius: var(--radius-md); + text-decoration: none; + transition: all 0.15s ease; + border: 1px solid var(--gray-200); +} + +.quick-action-item:hover { + transform: translateY(-2px); + box-shadow: var(--shadow-md); + text-decoration: none; +} + +.quick-action-icon { + width: 48px; + height: 48px; + border-radius: var(--radius-lg); + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0.75rem; + font-size: 1.25rem; + color: white; +} + +.quick-action-icon.primary { + background-color: var(--primary-500); +} + +.quick-action-icon.success { + background-color: var(--success-500); +} + +.quick-action-icon.warning { + background-color: var(--warning-500); +} + +.quick-action-icon.info { + background-color: #3b82f6; +} + +.quick-action-item span { + font-size: 0.875rem; + font-weight: 500; + color: var(--gray-700); +} + +/* Mini Calendar */ +.mini-calendar-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + overflow: hidden; +} + +.mini-calendar-header { + padding: 1rem 1.5rem; + background: var(--gray-50); + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid var(--gray-200); +} + +.mini-calendar-header h3 { + font-size: 1rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.calendar-nav { + display: flex; + gap: 0.25rem; +} + +.mini-calendar { + padding: 1rem; +} + +.calendar-grid { + display: grid; + grid-template-columns: repeat(7, 1fr); + gap: 0.25rem; +} + +.calendar-day { + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.75rem; + font-weight: 500; + border-radius: var(--radius-sm); + cursor: pointer; + transition: background-color 0.15s ease; +} + +.calendar-day:hover { + background-color: var(--gray-100); +} + +.calendar-day.today { + background-color: var(--primary-500); + color: white; +} + +.calendar-day.other-month { + color: var(--gray-300); +} + +.calendar-header { + font-weight: 600; + color: var(--gray-500); + font-size: 0.75rem; + padding: 0.5rem 0; + text-align: center; +} + +/* Status Distribution Card */ +.status-distribution-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + padding: 1.5rem; + height: 100%; +} + +.status-distribution-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.5rem; +} + +.status-distribution-header h3 { + font-size: 1.125rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.total-tasks { + font-size: 0.875rem; + color: var(--gray-500); + font-weight: 500; +} + +.status-distribution-chart { + display: flex; + justify-content: center; + margin-bottom: 1.5rem; +} + +.status-distribution-legend { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.legend-color { + width: 16px; + height: 16px; + border-radius: 50%; + flex-shrink: 0; +} + +.legend-label { + font-size: 0.875rem; + color: var(--gray-700); + font-weight: 500; +} + +/* Priority Card */ +.priority-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + padding: 1.5rem; + height: 100%; +} + +.priority-header { + margin-bottom: 1.5rem; +} + +.priority-header h3 { + font-size: 1.125rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.priority-items { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.priority-item { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + border-radius: var(--radius-md); + background: var(--gray-50); +} + +.priority-indicator { + width: 12px; + height: 12px; + border-radius: 50%; + flex-shrink: 0; +} + +.priority-item.high .priority-indicator { + background-color: var(--error-500); +} + +.priority-item.medium .priority-indicator { + background-color: var(--warning-500); +} + +.priority-item.low .priority-indicator { + background-color: var(--success-500); +} + +.priority-content { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; +} + +.priority-label { + font-size: 0.875rem; + font-weight: 500; + color: var(--gray-700); +} + +.priority-count { + font-size: 1.125rem; + font-weight: 600; + color: var(--gray-900); +} + +.priority-progress { + width: 60px; + height: 8px; + background-color: var(--gray-200); + border-radius: 4px; + overflow: hidden; +} + +.progress-bar { + height: 100%; + transition: width 0.3s ease; +} + +.progress-bar.high { + background-color: var(--error-500); +} + +.progress-bar.medium { + background-color: var(--warning-500); +} + +.progress-bar.low { + background-color: var(--success-500); +} + +/* Timeline Card */ +.timeline-card { + background: white; + border: 1px solid var(--gray-200); + border-radius: var(--radius-lg); + padding: 1.5rem; + height: 100%; +} + +.timeline-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.5rem; +} + +.timeline-header h3 { + font-size: 1.125rem; + font-weight: 600; + color: var(--gray-900); + margin: 0; +} + +.view-all-link { + font-size: 0.875rem; + color: var(--primary-600); + text-decoration: none; + font-weight: 500; +} + +.view-all-link:hover { + color: var(--primary-700); +} + +.timeline { + position: relative; +} + +.timeline::before { + content: ''; + position: absolute; + left: 8px; + top: 0; + bottom: 0; + width: 2px; + background-color: var(--gray-200); +} + +.timeline-item { + display: flex; + align-items: flex-start; + gap: 1rem; + margin-bottom: 1.5rem; + position: relative; +} + +.timeline-item:last-child { + margin-bottom: 0; +} + +.timeline-marker { + width: 16px; + height: 16px; + border-radius: 50%; + border: 2px solid white; + flex-shrink: 0; + z-index: 1; + position: relative; +} + +.timeline-marker.to_do { + background-color: var(--gray-400); +} + +.timeline-marker.in_progress { + background-color: var(--warning-500); +} + +.timeline-marker.completed { + background-color: var(--success-500); +} + +.timeline-content { + flex: 1; + min-width: 0; +} + +.timeline-title { + font-weight: 500; + color: var(--gray-900); + margin-bottom: 0.25rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.timeline-subtitle { + font-size: 0.75rem; + color: var(--gray-500); + margin-bottom: 0.25rem; +} + +.timeline-time { + font-size: 0.75rem; + color: var(--gray-400); +} diff --git a/public/assets/style.css b/public/assets/tasks/script.js similarity index 100% rename from public/assets/style.css rename to public/assets/tasks/script.js diff --git a/public/assets/tasks/style.css b/public/assets/tasks/style.css new file mode 100644 index 0000000..139f790 --- /dev/null +++ b/public/assets/tasks/style.css @@ -0,0 +1,1672 @@ +.tasks-header { + background: linear-gradient(135deg, + var(--primary-600) 0%, + var(--primary-700) 100%); + border-radius: 18px; + padding: 28px 36px; + color: white; + margin-bottom: 32px; + position: relative; + overflow: hidden; + border: 2px solid var(--primary-500); + box-shadow: var(--shadow-lg); +} + +.tasks-header::before { + content: ""; + position: absolute; + top: 0; + right: 0; + width: 100px; + height: 100px; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + transform: translate(25px, -25px); +} + +.tasks-header h1 { + margin: 0; + font-size: 32px; + font-weight: 700; + position: relative; + z-index: 1; +} + +.tasks-header p { + margin: 8px 0 0; + opacity: 0.9; + font-size: 16px; + position: relative; + z-index: 1; +} + +.view-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 28px; + background: white; + padding: 20px 28px; + border-radius: 16px; + box-shadow: var(--shadow-sm); + border: 2px solid var(--gray-200); +} + +.view-toggle { + display: flex; + background: var(--gray-100); + border-radius: 10px; + padding: 4px; + gap: 2px; +} + +.view-toggle button { + padding: 10px 18px; + border: none; + background: transparent; + border-radius: 8px; + font-weight: 600; + font-size: 14px; + color: var(--gray-600); + transition: all 0.2s ease; + cursor: pointer; +} + +.view-toggle button.active { + background: var(--primary-600); + color: white; + box-shadow: var(--shadow-sm); +} + +.task-filters { + display: flex; + gap: 16px; + align-items: center; +} + +.filter-select { + padding: 8px 14px; + border: 2px solid var(--gray-200); + border-radius: 8px; + background: white; + font-size: 14px; + color: var(--gray-700); + min-width: 120px; +} + +.filter-select:focus { + outline: none; + border-color: var(--primary-500); +} + +.kanban-container { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + min-height: 600px; +} + +.kanban-column { + background: var(--gray-50); + border-radius: 16px; + padding: 0; + border: 2px solid var(--gray-200); + overflow: hidden; + box-shadow: var(--shadow-sm); +} + +.column-header { + padding: 20px 24px; + display: flex; + justify-content: space-between; + align-items: center; + font-weight: 700; + font-size: 16px; + color: white; + position: relative; +} + +.column-header.todo { + background: linear-gradient(135deg, + var(--primary-500) 0%, + var(--primary-600) 100%); +} + +.column-header.in-progress { + background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); +} + +.column-header.completed { + background: linear-gradient(135deg, + var(--success-500) 0%, + var(--success-600) 100%); +} + +.column-header .task-count { + background: rgba(255, 255, 255, 0.2); + padding: 4px 12px; + border-radius: 12px; + font-size: 12px; + font-weight: 600; +} + +.add-task-btn { + background: rgba(255, 255, 255, 0.2); + border: 2px solid rgba(255, 255, 255, 0.3); + color: white; + border-radius: 8px; + padding: 6px 12px; + font-size: 18px; + cursor: pointer; + transition: all 0.2s ease; +} + +.add-task-btn:hover { + background: rgba(255, 255, 255, 0.3); + transform: scale(1.05); +} + +.kanban-list { + padding: 24px; + min-height: 500px; + background: var(--gray-50); +} + +.task-card { + background: white; + border: 2px solid var(--gray-200); + border-radius: 12px; + padding: 18px; + margin-bottom: 16px; + cursor: move; + transition: all 0.2s ease; + position: relative; + box-shadow: var(--shadow-sm); +} + +.task-card:hover { + border-color: var(--primary-500); + box-shadow: var(--shadow-lg); + transform: translateY(-2px); +} + +.task-card.dragging { + opacity: 0.5; + transform: rotate(5deg); + z-index: 1000; +} + +.task-title { + font-size: 16px; + font-weight: 600; + color: var(--gray-900); + margin-bottom: 8px; + line-height: 1.4; +} + +.task-description { + font-size: 14px; + color: var(--gray-600); + margin-bottom: 14px; + line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.task-meta { + display: flex; + justify-content: between; + align-items: center; + gap: 10px; + margin-bottom: 12px; +} + +.priority-badge { + padding: 4px 10px; + border-radius: 12px; + font-size: 11px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.priority-low { + background: rgba(16, 185, 129, 0.1); + color: var(--success-600); +} + +.priority-medium { + background: rgba(245, 158, 11, 0.1); + color: #d97706; +} + +.priority-high { + background: rgba(239, 68, 68, 0.1); + color: var(--error-600); +} + +.due-date { + font-size: 12px; + color: var(--gray-500); + display: flex; + align-items: center; + gap: 4px; +} + +.due-date.overdue { + color: var(--error-600); + font-weight: 600; +} + +.task-assignee { + width: 28px; + height: 28px; + border-radius: 50%; + background: var(--primary-500); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 600; + margin-left: auto; +} + +.task-actions { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 14px; + padding-top: 14px; + border-top: 1px solid var(--gray-200); +} + +.task-btn { + padding: 6px 12px; + border: none; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 4px; +} + +.btn-view { + background: var(--primary-50); + color: var(--primary-600); + border: 1px solid var(--primary-200); +} + +.btn-view:hover { + background: var(--primary-100); + color: var(--primary-700); +} + +.btn-edit { + background: var(--gray-100); + color: var(--gray-700); + border: 1px solid var(--gray-200); +} + +.btn-edit:hover { + background: var(--gray-200); + color: var(--gray-800); +} + +.list-view { + display: none; +} + +.list-view.active { + display: block; +} + +.kanban-view.active { + display: grid; +} + +.tasks-table { + background: white; + border-radius: 16px; + overflow: hidden; + box-shadow: var(--shadow-sm); + border: 2px solid var(--gray-200); +} + +.table-header { + background: var(--gray-100); + padding: 16px 24px; + border-bottom: 2px solid var(--gray-200); + font-weight: 600; + color: var(--gray-700); +} + +.main-content { + padding: 32px; + background: var(--gray-25); +} + +.back-link { + display: inline-flex; + align-items: center; + gap: 8px; + color: var(--gray-600); + text-decoration: none; + margin-bottom: 24px; + font-weight: 500; + transition: color 0.2s ease; +} + +.back-link:hover { + color: var(--primary-600); +} + +@media (max-width: 768px) { + .kanban-container { + grid-template-columns: 1fr; + gap: 16px; + } + + .view-controls { + flex-direction: column; + gap: 16px; + align-items: stretch; + } + + .task-filters { + justify-content: center; + } + + .main-content { + padding: 20px; + } +} + +.empty-state { + text-align: center; + color: var(--gray-400); + font-style: italic; + padding: 40px 20px; + background: var(--gray-50); + border: 2px dashed var(--gray-200); + border-radius: 12px; + margin-top: 16px; +} + +.empty-state i { + font-size: 48px; + margin-bottom: 12px; + color: var(--gray-300); +} + +.back-navigation { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 28px; + background: white; + padding: 16px 24px; + border-radius: 12px; + border: 2px solid var(--gray-200); + box-shadow: var(--shadow-sm); +} + +.nav-left { + display: flex; + align-items: center; + gap: 16px; +} + +.breadcrumb { + display: flex; + align-items: center; + gap: 8px; + color: var(--gray-500); + font-size: 14px; +} + +.breadcrumb a { + color: var(--primary-600); + text-decoration: none; + font-weight: 500; + transition: color 0.2s ease; +} + +.breadcrumb a:hover { + color: var(--primary-700); + text-decoration: underline; +} + +.breadcrumb-separator { + color: var(--gray-400); + margin: 0 4px; +} + +.breadcrumb-current { + color: var(--gray-700); + font-weight: 600; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.nav-actions { + display: flex; + gap: 12px; + align-items: center; +} + +.quick-action-btn { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 12px; + border: 1px solid var(--gray-200); + background: white; + color: var(--gray-600); + text-decoration: none; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + transition: all 0.2s ease; +} + +.quick-action-btn:hover { + background: var(--gray-50); + border-color: var(--gray-300); + color: var(--gray-700); +} + +.quick-action-btn.primary { + background: var(--primary-600); + color: white; + border-color: var(--primary-600); +} + +.quick-action-btn.primary:hover { + background: var(--primary-700); + border-color: var(--primary-700); + color: white; +} + +.task-container { + /* max-width: 1000px; */ + margin: 0 auto; +} + +.task-header { + background: white; + border: 2px solid var(--gray-200); + border-radius: 18px; + padding: 32px; + box-shadow: var(--shadow-lg); + margin-bottom: 28px; + position: relative; + overflow: hidden; +} + +.task-header::before { + content: ''; + position: absolute; + top: 0; + right: 0; + width: 120px; + height: 120px; + background: linear-gradient(135deg, var(--primary-100) 0%, var(--primary-200) 100%); + border-radius: 50%; + transform: translate(40px, -40px); + opacity: 0.7; +} + +.task-header-top { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 20px; +} + +.task-title-section { + position: relative; + z-index: 1; + flex: 1; +} + +.task-id { + background: var(--gray-100); + color: var(--gray-600); + padding: 4px 10px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; + font-family: 'Courier New', monospace; + margin-bottom: 12px; + display: inline-block; +} + +.task-title { + font-size: 32px; + font-weight: 800; + color: var(--gray-900); + margin-bottom: 16px; + line-height: 1.2; +} + +.task-header-actions { + display: flex; + gap: 8px; + position: relative; + z-index: 1; +} + +.header-action-btn { + width: 40px; + height: 40px; + border: 2px solid var(--gray-200); + background: white; + color: var(--gray-600); + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + font-size: 16px; +} + +.header-action-btn:hover { + background: var(--primary-50); + border-color: var(--primary-200); + color: var(--primary-600); + transform: translateY(-1px); +} + +.header-action-btn.danger:hover { + background: var(--error-50); + border-color: var(--error-200); + color: var(--error-600); +} + +.task-meta { + display: flex; + align-items: center; + gap: 24px; + flex-wrap: wrap; + position: relative; + z-index: 1; +} + +.status-badge, +.priority-badge { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 16px; + border-radius: 12px; + font-weight: 600; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.5px; + box-shadow: var(--shadow-sm); +} + +.status-badge { + border: 2px solid; + cursor: pointer; + transition: all 0.2s ease; +} + +.status-badge:hover { + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.status-to-do { + background: var(--primary-50); + color: var(--primary-700); + border-color: var(--primary-200); +} + +.status-in-progress { + background: #fef3c7; + color: #92400e; + border-color: #fcd34d; +} + +.status-completed { + background: var(--success-50); + color: var(--success-700); + border-color: var(--success-200); +} + +.priority-badge { + border: 2px solid; +} + +.priority-low { + background: var(--success-50); + color: var(--success-700); + border-color: var(--success-200); +} + +.priority-medium { + background: #fef3c7; + color: #92400e; + border-color: #fcd34d; +} + +.priority-high { + background: rgba(239, 68, 68, 0.1); + color: var(--error-700); + border-color: var(--error-200); +} + +.status-dot, +.priority-dot { + width: 8px; + height: 8px; + border-radius: 50%; +} + +.status-to-do .status-dot { + background: var(--primary-500); +} + +.status-in-progress .status-dot { + background: #f59e0b; +} + +.status-completed .status-dot { + background: var(--success-500); +} + +.priority-low .priority-dot { + background: var(--success-500); +} + +.priority-medium .priority-dot { + background: #f59e0b; +} + +.priority-high .priority-dot { + background: var(--error-500); +} + +.due-date-info { + display: flex; + align-items: center; + gap: 8px; + color: var(--gray-600); + font-weight: 500; +} + +.task-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 28px; + margin-bottom: 28px; +} + +.task-main { + background: white; + border: 2px solid var(--gray-200); + border-radius: 16px; + overflow: hidden; + box-shadow: var(--shadow-lg); +} + +.task-sidebar { + display: flex; + flex-direction: column; + gap: 24px; +} + +.section-header { + background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%); + padding: 20px 28px; + border-bottom: 2px solid var(--gray-100); +} + +.section-header h3 { + margin: 0; + font-size: 18px; + font-weight: 700; + color: var(--gray-800); + display: flex; + align-items: center; + gap: 12px; +} + +.section-content { + padding: 32px; +} + +.description-content { + color: var(--gray-700); + line-height: 1.8; + font-size: 15px; + background: var(--gray-25); + border-radius: 12px; + padding: 24px; + border: 1px solid var(--gray-200); +} + +.description-content p { + margin-bottom: 16px; +} + +.description-content ul, +.description-content ol { + margin-bottom: 16px; + padding-left: 24px; +} + +.description-content li { + margin-bottom: 8px; +} + +.description-content blockquote { + border-left: 4px solid var(--primary-500); + padding-left: 20px; + margin: 20px 0; + color: var(--gray-600); + font-style: italic; + background: var(--primary-25); + padding: 16px 20px; + border-radius: 0 8px 8px 0; +} + +.description-content code { + background: var(--gray-100); + padding: 4px 8px; + border-radius: 6px; + font-family: 'Courier New', monospace; + font-size: 13px; + color: var(--gray-800); +} + +.empty-description { + text-align: center; + color: var(--gray-400); + font-style: italic; + padding: 40px 20px; +} + +.info-card { + background: white; + border: 2px solid var(--gray-200); + border-radius: 16px; + overflow: hidden; + box-shadow: var(--shadow-lg); +} + +.info-card-header { + background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%); + padding: 18px 24px; + border-bottom: 2px solid var(--gray-100); +} + +.info-card-header h4 { + margin: 0; + font-size: 16px; + font-weight: 700; + color: var(--gray-800); + display: flex; + align-items: center; + gap: 10px; +} + +.info-card-content { + padding: 24px; +} + +.info-item { + display: flex; + align-items: flex-start; + gap: 14px; + margin-bottom: 20px; +} + +.info-item:last-child { + margin-bottom: 0; +} + +.info-icon { + width: 36px; + height: 36px; + border-radius: 10px; + background: var(--primary-100); + color: var(--primary-600); + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + flex-shrink: 0; +} + +.info-content h5 { + margin: 0 0 6px; + font-size: 14px; + font-weight: 600; + color: var(--gray-700); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.info-content p { + margin: 0; + color: var(--gray-600); + font-size: 15px; + font-weight: 500; +} + +.time-tracker-card { + background: linear-gradient(135deg, var(--primary-50) 0%, var(--primary-100) 100%); + border: 2px solid var(--primary-200); +} + +.time-tracker-card .info-card-header { + background: linear-gradient(135deg, var(--primary-100) 0%, var(--primary-200) 100%); + color: var(--primary-800); +} + +.time-display { + font-family: 'Courier New', monospace; + font-size: 28px; + font-weight: 700; + color: var(--primary-700); + text-align: center; + margin-bottom: 20px; + padding: 16px; + background: white; + border-radius: 12px; + border: 2px solid var(--primary-200); +} + +.time-controls { + display: flex; + gap: 12px; + justify-content: center; +} + +.time-btn { + padding: 12px 20px; + border: 2px solid; + border-radius: 10px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + font-size: 14px; + display: flex; + align-items: center; + gap: 8px; +} + +.btn-start { + background: var(--success-500); + color: white; + border-color: var(--success-500); +} + +.btn-start:hover { + background: var(--success-600); + border-color: var(--success-600); + transform: translateY(-1px); +} + +.btn-pause { + background: #f59e0b; + color: white; + border-color: #f59e0b; +} + +.btn-pause:hover { + background: #d97706; + border-color: #d97706; + transform: translateY(-1px); +} + +.btn-stop { + background: var(--error-500); + color: white; + border-color: var(--error-500); +} + +.btn-stop:hover { + background: var(--error-600); + border-color: var(--error-600); + transform: translateY(-1px); +} + +.action-buttons { + background: white; + border: 2px solid var(--gray-200); + border-radius: 16px; + padding: 24px; + box-shadow: var(--shadow-lg); +} + +.action-buttons h4 { + margin-bottom: 20px; + font-size: 16px; + font-weight: 700; + color: var(--gray-800); + display: flex; + align-items: center; + gap: 10px; +} + +.btn-group { + display: flex; + flex-direction: column; + gap: 12px; +} + +.action-btn { + padding: 14px 20px; + border: 2px solid; + border-radius: 10px; + text-decoration: none; + font-weight: 600; + font-size: 14px; + transition: all 0.2s ease; + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + cursor: pointer; +} + +.btn-edit { + background: var(--primary-500); + color: white; + border-color: var(--primary-500); +} + +.btn-edit:hover { + background: var(--primary-600); + border-color: var(--primary-600); + color: white; + transform: translateY(-1px); + box-shadow: var(--shadow-lg); +} + +.btn-delete { + background: white; + color: var(--error-600); + border-color: var(--error-200); +} + +.btn-delete:hover { + background: var(--error-500); + border-color: var(--error-500); + color: white; + transform: translateY(-1px); + box-shadow: var(--shadow-lg); +} + +.progress-section { + margin-top: 28px; +} + +.progress-info { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.progress-label { + font-weight: 600; + color: var(--gray-700); + font-size: 14px; +} + +.progress-percentage { + font-weight: 700; + color: var(--primary-600); + font-size: 16px; +} + +.progress-bar-container { + height: 12px; + background: var(--gray-200); + border-radius: 6px; + overflow: hidden; + position: relative; +} + +.progress-bar { + height: 100%; + background: linear-gradient(90deg, var(--primary-500) 0%, var(--primary-600) 100%); + border-radius: 6px; + transition: width 0.3s ease; + position: relative; +} + +.progress-bar::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%); + animation: shimmer 2s infinite; +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(100%); + } +} + +/* Checklist Styles */ +.checklist-section { + background: white; + border: 2px solid var(--gray-200); + border-radius: 16px; + overflow: hidden; + box-shadow: var(--shadow-lg); + margin-bottom: 28px; +} + +.checklist-header { + background: linear-gradient(135deg, var(--gray-50) 0%, var(--gray-100) 100%); + padding: 20px 28px; + border-bottom: 2px solid var(--gray-100); + display: flex; + align-items: center; + justify-content: space-between; +} + +.checklist-header h3 { + margin: 0; + font-size: 18px; + font-weight: 700; + color: var(--gray-800); + display: flex; + align-items: center; + gap: 12px; +} + +.checklist-progress { + display: flex; + align-items: center; + gap: 12px; + font-size: 14px; + color: var(--gray-600); +} + +.checklist-progress-bar { + width: 60px; + height: 6px; + background: var(--gray-200); + border-radius: 3px; + overflow: hidden; +} + +.checklist-progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--success-500) 0%, var(--success-600) 100%); + border-radius: 3px; + transition: width 0.3s ease; +} + +.checklist-content { + padding: 28px; +} + +.checklist-items { + margin-bottom: 24px; +} + +.checklist-item { + display: flex; + align-items: center; + gap: 16px; + padding: 16px; + border: 2px solid var(--gray-100); + border-radius: 12px; + margin-bottom: 12px; + transition: all 0.2s ease; + background: var(--gray-25); +} + +.checklist-item:hover { + border-color: var(--gray-200); + background: white; + box-shadow: var(--shadow-sm); +} + +.checklist-item.completed { + background: var(--success-50); + border-color: var(--success-200); +} + +.checklist-item.completed .checklist-text { + text-decoration: line-through; + opacity: 0.7; +} + +.checklist-checkbox { + width: 20px; + height: 20px; + border: 2px solid var(--gray-300); + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + flex-shrink: 0; +} + +.checklist-checkbox:hover { + border-color: var(--primary-500); + background: var(--primary-50); +} + +.checklist-checkbox.checked { + background: var(--success-500); + border-color: var(--success-500); + color: white; +} + +.checklist-text { + flex: 1; + font-size: 14px; + color: var(--gray-700); + font-weight: 500; + line-height: 1.4; +} + +.checklist-actions { + display: flex; + gap: 8px; + opacity: 0; + transition: opacity 0.2s ease; +} + +.checklist-item:hover .checklist-actions { + opacity: 1; +} + +.checklist-action-btn { + width: 28px; + height: 28px; + border: none; + background: var(--gray-200); + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s ease; + font-size: 12px; +} + +.checklist-action-btn:hover { + background: var(--gray-300); +} + +.checklist-action-btn.delete:hover { + background: var(--error-500); + color: white; +} + +.add-checklist-form { + display: flex; + gap: 12px; + align-items: center; +} + +.checklist-input { + flex: 1; + padding: 12px 16px; + border: 2px solid var(--gray-200); + border-radius: 10px; + font-size: 14px; + transition: all 0.2s ease; +} + +.checklist-input:focus { + border-color: var(--primary-500); + box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); + outline: none; +} + +.add-checklist-btn { + padding: 12px 20px; + background: var(--primary-500); + color: white; + border: none; + border-radius: 10px; + font-weight: 600; + font-size: 14px; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.add-checklist-btn:hover { + background: var(--primary-600); + transform: translateY(-1px); + box-shadow: var(--shadow-sm); +} + +.empty-checklist { + text-align: center; + padding: 40px 20px; + color: var(--gray-500); +} + +.empty-checklist i { + font-size: 48px; + opacity: 0.3; + margin-bottom: 16px; + display: block; +} + +@media (max-width: 768px) { + .task-grid { + grid-template-columns: 1fr; + } + + .task-meta { + flex-direction: column; + align-items: flex-start; + gap: 16px; + } + + .task-title { + font-size: 24px; + } + + .main-content { + padding: 20px; + } + + .task-header { + padding: 24px 20px; + } + + .section-content { + padding: 20px; + } + + .time-controls { + flex-direction: column; + } +} + +.form-container { + max-width: 800px; + margin: 0 auto; +} + +.form-card { + background: white; + border: 2px solid var(--gray-200); + border-radius: 16px; + box-shadow: var(--shadow-lg); + overflow: hidden; +} + +.form-header { + background: linear-gradient(135deg, var(--primary-600) 0%, var(--primary-700) 100%); + padding: 32px; + text-align: center; + color: white; + position: relative; +} + +.form-header::before { + content: ''; + position: absolute; + top: 0; + right: 0; + width: 100px; + height: 100px; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + transform: translate(20px, -20px); +} + +.task-icon { + width: 65px; + height: 65px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.2); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 28px; + margin: 0 auto 20px; + border: 2px solid rgba(255, 255, 255, 0.3); + box-shadow: var(--shadow-md); + position: relative; + z-index: 1; +} + +.form-header h2 { + margin: 0; + font-size: 28px; + font-weight: 700; + position: relative; + z-index: 1; +} + +.form-header p { + margin: 8px 0 0; + opacity: 0.9; + font-size: 16px; + position: relative; + z-index: 1; +} + +.form-body { + padding: 32px; +} + +.form-group { + margin-bottom: 26px; +} + +.form-label { + display: block; + margin-bottom: 10px; + font-weight: 600; + color: var(--gray-700); + font-size: 14px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.form-control, +.form-select { + width: 100%; + padding: 14px 18px; + border: 2px solid var(--gray-200); + border-radius: 10px; + background: white; + font-size: 14px; + color: var(--gray-700); + transition: all 0.2s ease; +} + +.form-control:focus, +.form-select:focus { + outline: none; + border-color: var(--primary-500); + box-shadow: 0 0 0 4px var(--primary-100); +} + +.form-control.is-invalid { + border-color: var(--error-500); + box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1); +} + +.invalid-feedback { + display: block; + margin-top: 8px; + font-size: 13px; + color: var(--error-600); + font-weight: 500; +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; +} + +.status-options, +.priority-options { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 14px; + margin-top: 10px; +} + +.status-option, +.priority-option { + position: relative; +} + +.status-option input, +.priority-option input { + position: absolute; + opacity: 0; +} + +.status-option label, +.priority-option label { + display: flex; + align-items: center; + padding: 14px 18px; + border: 2px solid var(--gray-200); + border-radius: 10px; + cursor: pointer; + transition: all 0.2s ease; + background: white; + font-weight: 600; + font-size: 13px; + justify-content: center; +} + +.status-option input:checked+label, +.priority-option input:checked+label { + border-color: var(--primary-500); + background: var(--primary-50); + color: var(--primary-700); + box-shadow: var(--shadow-sm); +} + +.status-dot, +.priority-dot { + width: 10px; + height: 10px; + border-radius: 50%; + margin-right: 8px; + flex-shrink: 0; +} + +.status-to-do .status-dot { + background: var(--primary-500); +} + +.status-in-progress .status-dot { + background: #f59e0b; +} + +.status-completed .status-dot { + background: var(--success-500); +} + +.priority-low .priority-dot { + background: var(--success-500); +} + +.priority-medium .priority-dot { + background: #f59e0b; +} + +.priority-high .priority-dot { + background: var(--error-500); +} + +.form-actions { + display: flex; + gap: 14px; + justify-content: space-between; + padding-top: 28px; + border-top: 2px solid var(--gray-100); + margin-top: 28px; +} + +.btn-secondary { + padding: 14px 26px; + border: 2px solid var(--gray-200); + background: white; + color: var(--gray-700); + border-radius: 10px; + text-decoration: none; + font-weight: 600; + transition: all 0.2s ease; + font-size: 14px; +} + +.btn-secondary:hover { + border-color: var(--gray-300); + background: var(--gray-50); + color: var(--gray-800); + transform: translateY(-1px); + box-shadow: var(--shadow-md); +} + +.btn-primary { + padding: 14px 34px; + background: var(--primary-600); + border: 2px solid var(--primary-600); + color: white; + border-radius: 10px; + font-weight: 600; + transition: all 0.2s ease; + cursor: pointer; + font-size: 14px; +} + +.btn-primary:hover { + background: var(--primary-700); + border-color: var(--primary-700); + transform: translateY(-1px); + box-shadow: var(--shadow-lg); +} + +.btn-danger { + padding: 14px 26px; + background: var(--error-500); + border: 2px solid var(--error-500); + color: white; + border-radius: 10px; + font-weight: 600; + transition: all 0.2s ease; + cursor: pointer; + font-size: 14px; +} + +.btn-danger:hover { + background: var(--error-600); + border-color: var(--error-600); + transform: translateY(-1px); + box-shadow: var(--shadow-lg); +} + +.input-icon { + position: relative; +} + +.input-icon .form-control { + padding-left: 50px; +} + +.input-icon i { + position: absolute; + left: 18px; + top: 50%; + transform: translateY(-50%); + color: var(--gray-400); + z-index: 2; +} + +/* Quill Editor Styling */ +.editor-container { + border: 2px solid var(--gray-200); + border-radius: 10px; + overflow: hidden; + transition: all 0.2s ease; +} + +.editor-container:focus-within { + border-color: var(--primary-500); + box-shadow: 0 0 0 4px var(--primary-100); +} + +.ql-toolbar { + border: none; + border-bottom: 1px solid var(--gray-200); + background: var(--gray-50); + padding: 12px; +} + +.ql-container { + border: none; + font-family: inherit; + font-size: 14px; +} + +.ql-editor { + min-height: 150px; + padding: 18px; + color: var(--gray-700); + line-height: 1.6; +} + +.ql-editor.ql-blank::before { + color: var(--gray-400); + font-style: normal; + left: 18px; + right: 18px; +} + +.ql-snow .ql-tooltip { + background: white; + border: 1px solid var(--gray-200); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.ql-snow .ql-picker-options { + background: white; + border: 1px solid var(--gray-200); + border-radius: 8px; + box-shadow: var(--shadow-lg); +} + +.danger-zone { + background: rgba(239, 68, 68, 0.05); + border: 2px solid rgba(239, 68, 68, 0.2); + border-radius: 14px; + padding: 24px; + margin-top: 36px; +} + +.danger-zone h6 { + color: var(--error-600); + margin-bottom: 14px; + font-weight: 700; + font-size: 16px; +} + +.danger-zone p { + color: var(--gray-600); + margin-bottom: 18px; + font-size: 14px; + line-height: 1.5; +} + + diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index e9fbde8..8335319 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -8,96 +8,357 @@ - + + -
-
-
-
-
- task manager -
-
-
- @csrf -
- - - @error('email') - {{ $message }} - @enderror -
-
- - - @error('password') - {{ $message }} - @enderror -
-
- - -
-
- -
-
+ -
diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index e97e69b..6a4521e 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -1,111 +1,567 @@ @extends('layouts.app') -@section('title') - Dashboard -@endsection + +@section('title', 'Dashboard') + +@section('page-title', 'Dashboard') + @section('content') -
-

Welcome to your Dashboard

-

This is your dashboard where you can manage your tasks, routines, notes, and files.

- -
-
-
-
-
Tasks
-

You have {{ $tasksCount }} tasks pending.

- View Tasks +
+ +
+
+
+

Good {{ now()->format('A') === 'AM' ? 'morning' : (now()->format('H') < 18 ? 'afternoon' : 'evening') }}, {{ Auth::user()->name }}! 👋

+

Here's what's happening with your tasks today.

+
+
+
+ + +
+
+
+
+ +
+
+
{{ $tasksCount }}
+
Active Tasks
+ + View all + +
-
-
-
-
Routines
-

You have {{ $routinesCount }} routines scheduled today.

- View Routines + +
+
+
+ +
+
+
{{ $projectsCount }}
+
Total Projects
+ + Manage + +
-
-
-
-
Notes
-

You have {{ $notesCount }} notes saved.

- View Notes + +
+
+
+ +
+
+
{{ $routinesCount }}
+
Today's Routines
+ + View routines + +
-
-
-
-
Files
-

You have {{ $filesCount }} files.

- View Files + +
+
+
+ +
+
+
{{ $notesCount }}
+
Saved Notes
+ + Browse notes + +
-
-
-
-
-
Recent Tasks
-
    - @foreach($recentTasks as $task) -
  • - {{ $task->title }} - {{ $task->status == 'to_do' ? 'To Do' : 'In Progress' }} -
  • - @endforeach -
+ +
+ +
+
+
+
+ + Productivity Overview +
+
+ +
+
+
+
+
+
+
{{ $completedTasksThisWeek }}
+
Tasks Completed
+
+12% from last week
+
+
+
+
+
{{ $completionRate }}%
+
Completion Rate
+
+5% improvement
+
+
+
+
+
{{ $activeProjects }}
+
Active Projects
+
No change
+
+
+
+
+
{{ $overdueTasks }}
+
Overdue Tasks
+
Needs attention
+
+
+
+
+ +
+
+
+
+ + +
+ + +
+
+

{{ now()->format('F Y') }}

+
+ + +
+
+
+
-
-
-
-
Today's Routines
-
    - @foreach($todayRoutines as $routine) -
  • - {{ $routine->title }} - {{ $routine->frequency }} -
  • - @endforeach -
+
+ + +
+
+
+
+

Task Distribution

+ {{ $tasksCount }} total tasks +
+
+ +
+
+
+ + To Do ({{ $taskStatusDistribution['to_do'] }}) +
+
+ + In Progress ({{ $taskStatusDistribution['in_progress'] }}) +
+
+ + Completed ({{ $taskStatusDistribution['completed'] }}) +
-
-
-
-
Recent Notes
-
    - @foreach($recentNotes as $note) -
  • - {{ $note->title }} -
  • - @endforeach -
+ + +
+
+
+

Priority Breakdown

+
+
+
+
+
+ High Priority + {{ $priorityDistribution['high'] }} +
+
+
+
+
+
+
+
+ Medium Priority + {{ $priorityDistribution['medium'] }} +
+
+
+
+
+
+
+
+ Low Priority + {{ $priorityDistribution['low'] }} +
+
+
+
+
-
-
-
-
Upcoming Reminders
-
    - @foreach($upcomingReminders as $reminder) -
  • - {{ $reminder->title }} - {{ $reminder->date->format('M d') }} {{ $reminder->time ? $reminder->time->format('H:i') : '' }} -
  • - @endforeach -
+ + +
+
+
+

Recent Activity

+ View all +
+
+ @foreach($recentTasks->take(4) as $task) +
+
+
+
{{ $task->title }}
+
{{ $task->project->name ?? 'No Project' }}
+
{{ $task->updated_at->diffForHumans() }}
+
+
+ @endforeach +
+
+
+
+
+ +
+
+
+
+ + Recent Tasks +
+ + View all + +
+
+ @forelse($recentTasks as $task) +
+
+ +
+
+
{{ $task->title }}
+
+ + {{ ucwords(str_replace('_', ' ', $task->status)) }} + + @if($task->due_date != null) + {{ \Carbon\Carbon::parse($task->due_date)->format('M d') }} + @endif +
+
+
+ @empty +
+ +

No recent tasks found

+ Create Project +
+ @endforelse +
+
+
+ + +
+
+
+
+ + Today's Routines +
+ + View all + +
+
+ @forelse($todayRoutines as $routine) +
+
+ +
+
+
{{ $routine->title }}
+
+ {{ ucfirst($routine->frequency) }} + @if($routine->time) + {{ $routine->time->format('H:i') }} + @endif +
+
+
+ @empty +
+ +

No routines for today

+ Create Routine +
+ @endforelse +
+
+
+ + +
+
+
+
+ + Recent Notes +
+ + View all + +
+
+ @forelse($recentNotes as $note) +
+
+ +
+
+
{{ $note->title }}
+
+ {{ $note->created_at->format('M d, Y') }} +
+
+
+ @empty +
+ +

No notes yet

+ Create Note +
+ @endforelse +
+
+
+ + +
+
+
+
+ + Upcoming Reminders +
+ + View all + +
+
+ @forelse($upcomingReminders as $reminder) +
+
+ +
+
+
{{ $reminder->title }}
+
+ + {{ $reminder->date->isToday() ? 'Today' : ($reminder->date->isPast() ? 'Overdue' : $reminder->date->format('M d')) }} + + @if($reminder->time) + {{ \Carbon\Carbon::parse($reminder->time)->format('H:i') }} + @endif +
+
+
+ @empty +
+ +

No upcoming reminders

+ Create Reminder +
+ @endforelse
@endsection + +@push('styles') + +@endpush +@push('scripts') + + +@endpush diff --git a/resources/views/errors/403.blade.php b/resources/views/errors/403.blade.php new file mode 100644 index 0000000..5d0d0ce --- /dev/null +++ b/resources/views/errors/403.blade.php @@ -0,0 +1,173 @@ + + + + + + Access Forbidden - Task Manager + + + + + + + + + +
+
+
+ +
+ +

403

+

Access Forbidden

+

+ You don't have permission to access this resource. + Please contact your administrator if you believe you should have access to this page. +

+ + + + Back to Dashboard + +
+
+ + + + diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php new file mode 100644 index 0000000..30e8481 --- /dev/null +++ b/resources/views/errors/404.blade.php @@ -0,0 +1,169 @@ + + + + + + Page Not Found - Task Manager + + + + + + + + + +
+
+
+ +
+ +

404

+

Page Not Found

+

+ Sorry, we couldn't find the page you're looking for. + The page might have been moved, deleted, or the URL might be incorrect. +

+ + + + Back to Dashboard + +
+
+ + + + diff --git a/resources/views/errors/500.blade.php b/resources/views/errors/500.blade.php new file mode 100644 index 0000000..27d8c4b --- /dev/null +++ b/resources/views/errors/500.blade.php @@ -0,0 +1,174 @@ + + + + + + Server Error - Task Manager + + + + + + + + + +
+
+
+ +
+ +

500

+

Server Error

+

+ Oops! Something went wrong on our end. + Our team has been notified and we're working to fix this issue. + Please try again in a few moments. +

+ + + + Back to Dashboard + +
+
+ + + + diff --git a/resources/views/files/create.blade.php b/resources/views/files/create.blade.php index 7903741..c0c81f1 100644 --- a/resources/views/files/create.blade.php +++ b/resources/views/files/create.blade.php @@ -1,43 +1,464 @@ @extends('layouts.app') +@section('title', 'Upload File') + @section('content') -
-

Upload File

-
-
-
- @csrf -
- - - @error('name') - {{ $message }} - @enderror -
-
- - - @error('file') - {{ $message }} - @enderror -
-
- - - @error('type') - {{ $message }} - @enderror -
- -
+ + +
+ +
+
+
+

+ Upload File +

+

Upload and organize your files with ease

+ + Back to Files +
+
+ + + + +
+
+
+ @csrf + +
+
+ +
+ + + @error('name') +
{{ $message }}
+ @enderror +
+ + +
+ +
+
+ +
+
Drag and drop your file here
+
or click to browse files
+ +
+
+ + + +
+ @error('file') +
{{ $message }}
+ @enderror +
+ +
+ + +
+
+
+ +
+
Project
+
+
+
+ +
+
Documents
+
+
+
+ +
+
Text
+
+
+
+ +
+
Code
+
+
+
+ +
+
Image
+
+
+ @error('type') +
{{ $message }}
+ @enderror +
+
+ + +
+ + + Cancel + + +
+
+
+
+
+ @endsection + +@push('scripts') + +@endpush diff --git a/resources/views/files/edit.blade.php b/resources/views/files/edit.blade.php index 8c2f53c..3190f68 100644 --- a/resources/views/files/edit.blade.php +++ b/resources/views/files/edit.blade.php @@ -1,44 +1,529 @@ @extends('layouts.app') +@section('title', 'Edit File') + @section('content') -
-

Edit File

-
-
-
- @csrf - @method('PUT') -
- - - @error('name') - {{ $message }} - @enderror -
-
- - - @error('file') - {{ $message }} - @enderror + + +
+ +
+
+
+

+ Edit File +

+

Update file information and replace if needed

+
+ + Back to Files + +
+
+ + + + + +
+
+
+ @switch($file->type) + @case('project') + + @break + @case('docs') + + @break + @case('txt') + + @break + @case('code') + + @break + @case('image') + + @break + @default + + @endswitch +
+
+
{{ $file->name }}
+
+ {{ ucfirst($file->type) }} + {{ $file->created_at->format('M d, Y') }} +
+
+
+ + Download Current File + +
+ +
+
+ + @csrf + @method('PUT') + +
+
+ +
+ + + @error('name') +
{{ $message }}
+ @enderror +
+ + +
+ +
+
+ +
+
Drag and drop new file here to replace
+
or click to browse files (leave blank to keep current file)
+ +
+
+ + + +
+ @error('file') +
{{ $message }}
+ @enderror +
+ + +
+ + +
+
+
+ +
+
Project
+
+
+
+ +
+
Documents
+
+
+
+ +
+
Text
+
+
+
+ +
+
Code
+
+
+
+ +
+
Image
+
+
+ @error('type') +
{{ $message }}
+ @enderror +
-
- - - @error('type') - {{ $message }} - @enderror + + +
+ + + Cancel + +
- - -
+
+
+
+ @endsection + +@push('scripts') + +@endpush diff --git a/resources/views/files/index.blade.php b/resources/views/files/index.blade.php index c4ffcfa..6dac17c 100644 --- a/resources/views/files/index.blade.php +++ b/resources/views/files/index.blade.php @@ -1,36 +1,339 @@ @extends('layouts.app') +@section('title', 'Files') + @section('content') -
-
-

Uploaded Files

- Upload File + + +
+ +
+
+
+

+ File Manager +

+

Manage and organize your uploaded files

+
+ + Upload New File + +
@if(session('success')) -
- {{ session('success') }} +
+ {{ session('success') }}
@endif -
- @foreach($files as $file) -
-
-
-
{{ $file->name }}
-

Type: {{ $file->type }}

- - -
- @csrf - @method('DELETE') - -
-
+ +
+
+
+
+ +
+
{{ $files->count() }}
+
Total Files
+
+
+
+
+
+ +
+
{{ $files->where('type', 'project')->count() }}
+
Project Files
+
+
+
+
+
+
+
{{ $files->where('type', 'docs')->count() }}
+
Documents
- @endforeach +
+
+
+
+ +
+
{{ $files->whereIn('type', ['code', 'txt'])->count() }}
+
Code & Text
+
+
+ + + @if($files->count() > 0) +
+ @foreach($files as $file) +
+
+
+
+
+ @switch($file->type) + @case('project') + + @break + @case('docs') + + @break + @case('txt') + + @break + @case('code') + + @break + @case('image') + + @break + @default + + @endswitch +
+ {{ ucfirst($file->type) }} +
+
{{ Str::limit($file->name, 30) }}
+
+
+

+ + {{ $file->created_at->format('M d, Y') }} +

+
+ + + + + + +
+ @csrf + @method('DELETE') + +
+
+
+
+
+ @endforeach +
+ @else +
+
+ +
+

No Files Found

+

Start by uploading your first file to get organized!

+ + Upload First File + +
+ @endif
+ @endsection diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index de04bec..2ebcb90 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -8,215 +8,748 @@ - - + + + + href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-icons/1.10.5/font/bootstrap-icons.min.css"> + + + @stack('styles') - + + + +
-
-
- +
+ + + @if(session('success')) +
+ +
+ @endif + + @if(session('error')) +
+ +
+ @endif + + @if(session('warning')) +
+ +
+ @endif + + @if(session('info')) +
+ +
+ @endif +
@yield('content')
-