From 593457204fdba5c559c6d65dcc3ef189027ac563 Mon Sep 17 00:00:00 2001 From: Arafat Hossain Ar Date: Fri, 12 Sep 2025 14:33:20 +0600 Subject: [PATCH 1/2] Add profile management and enhance task/reminder routes - Introduced profile routes for viewing, editing, and updating user profiles, including password management and avatar deletion. - Added task management routes for creating, editing, and deleting tasks. - Enhanced reminder routes with options for toggling completion, snoozing, and duplicating reminders. - Refactored dashboard route to use a dedicated DashboardController for better organization and maintainability. --- app/Http/Controllers/DashboardController.php | 176 ++ app/Http/Controllers/NoteController.php | 129 +- app/Http/Controllers/ProfileController.php | 114 + app/Http/Controllers/ReminderController.php | 290 ++- app/Http/Controllers/TaskController.php | 71 +- app/Models/Note.php | 49 + app/Models/Reminder.php | 204 ++ app/Models/Task.php | 1 + app/Models/User.php | 5 + ...218_add_enhanced_fields_to_notes_table.php | 30 + ...add_enhanced_fields_to_reminders_table.php | 42 + ...4754_add_profile_fields_to_users_table.php | 32 + database/seeders/TestDataSeeder.php | 179 ++ resources/views/auth/login.blade.php | 377 +++- resources/views/dashboard.blade.php | 1391 +++++++++++- resources/views/errors/403.blade.php | 173 ++ resources/views/errors/404.blade.php | 169 ++ resources/views/errors/500.blade.php | 174 ++ resources/views/files/create.blade.php | 491 +++- resources/views/files/edit.blade.php | 557 ++++- resources/views/files/index.blade.php | 347 ++- resources/views/layouts/app.blade.php | 788 +++++-- resources/views/notes/create.blade.php | 726 +++++- resources/views/notes/edit.blade.php | 770 ++++++- resources/views/notes/index.blade.php | 550 ++++- .../views/notes/partials/notes-grid.blade.php | 158 ++ resources/views/notes/show.blade.php | 417 ++++ resources/views/profile/edit.blade.php | 512 +++++ resources/views/profile/password.blade.php | 560 +++++ resources/views/profile/show.blade.php | 409 ++++ resources/views/projects/create.blade.php | 605 ++++- resources/views/projects/edit.blade.php | 685 +++++- resources/views/projects/index.blade.php | 715 +++++- resources/views/projects/show.blade.php | 771 ++++++- resources/views/reminders/create.blade.php | 563 ++++- resources/views/reminders/edit.blade.php | 566 ++++- resources/views/reminders/index.blade.php | 704 +++++- .../reminders/partials/list-view.blade.php | 161 ++ resources/views/reminders/show.blade.php | 563 +++++ resources/views/routines/create.blade.php | 528 ++++- resources/views/routines/daily.blade.php | 257 ++- resources/views/routines/edit.blade.php | 541 ++++- resources/views/routines/index.blade.php | 541 ++++- resources/views/routines/monthly.blade.php | 263 ++- resources/views/routines/weekly.blade.php | 257 ++- resources/views/tasks/create.blade.php | 613 ++++- resources/views/tasks/edit.blade.php | 688 +++++- resources/views/tasks/index.blade.php | 1374 ++++++++++-- resources/views/tasks/show.blade.php | 1981 ++++++++++++++--- routes/web.php | 50 +- 50 files changed, 20516 insertions(+), 1801 deletions(-) create mode 100644 app/Http/Controllers/DashboardController.php create mode 100644 app/Http/Controllers/ProfileController.php create mode 100644 database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php create mode 100644 database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php create mode 100644 database/migrations/2025_09_11_044754_add_profile_fields_to_users_table.php create mode 100644 database/seeders/TestDataSeeder.php create mode 100644 resources/views/errors/403.blade.php create mode 100644 resources/views/errors/404.blade.php create mode 100644 resources/views/errors/500.blade.php create mode 100644 resources/views/notes/partials/notes-grid.blade.php create mode 100644 resources/views/notes/show.blade.php create mode 100644 resources/views/profile/edit.blade.php create mode 100644 resources/views/profile/password.blade.php create mode 100644 resources/views/profile/show.blade.php create mode 100644 resources/views/reminders/partials/list-view.blade.php create mode 100644 resources/views/reminders/show.blade.php 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/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/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/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..c17c5fc 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -1,111 +1,1342 @@ @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..99b8265 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -8,215 +8,743 @@ - - + + + + 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')
-