-
Notifications
You must be signed in to change notification settings - Fork 16
Add profile management and enhance task/reminder routes #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- 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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This pull request significantly enhances the task and routine management system while adding comprehensive profile management features. The changes modernize the user interface with improved styling, enhanced functionality, and better organization.
Key Changes
- Profile Management: Added complete profile routes for viewing, editing, and updating user profiles, including password management and avatar deletion
- Enhanced Task System: Expanded task management with improved creation, editing, and deletion capabilities using modern UI components
- Advanced Reminder System: Added comprehensive reminder functionality with completion toggling, snoozing, and duplication features
- UI/UX Improvements: Implemented modern design patterns with enhanced styling, better responsive layouts, and improved user interactions
Reviewed Changes
Copilot reviewed 47 out of 50 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| resources/views/tasks/edit.blade.php | Completely redesigned task editing interface with rich text editor, improved form validation, and modern styling |
| resources/views/tasks/create.blade.php | Enhanced task creation form with better UX, input validation, and visual design improvements |
| resources/views/routines/*.blade.php | Modernized routine management views with improved layouts, better filtering, and enhanced visual hierarchy |
| resources/views/reminders/*.blade.php | Added comprehensive reminder system with advanced features like snoozing, completion tracking, and modern UI |
| resources/views/projects/*.blade.php | Enhanced project management interface with better progress visualization and improved team member management |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| const status = data.is_completed ? 'completed' : 'reactivated'; | ||
| showNotification(`Reminder ${status} successfully!`); |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dynamic error message construction could result in unclear messages. Consider using specific, predefined messages for better user clarity: showNotification(data.is_completed ? 'Reminder marked as completed!' : 'Reminder reactivated successfully!')
| const status = data.is_completed ? 'completed' : 'reactivated'; | |
| showNotification(`Reminder ${status} successfully!`); | |
| // Use explicit, predefined messages for clarity | |
| showNotification(data.is_completed ? 'Reminder marked as completed!' : 'Reminder reactivated successfully!'); |
| alert('Please enter a task title.'); | ||
| return; |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using browser alert() for validation messages provides poor user experience. Consider using a more modern notification system or inline validation feedback that matches the existing UI design.
| if (!title) { | ||
| e.preventDefault(); | ||
| alert('Please enter a task title.'); |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using browser alert() for validation messages provides poor user experience. Consider using a more modern notification system or inline validation feedback that matches the existing UI design.
| if (dueDate < today) { | ||
| e.preventDefault(); | ||
| alert('Due date cannot be in the past.'); |
Copilot
AI
Sep 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using browser alert() for validation messages provides poor user experience. Consider using a more modern notification system or inline validation feedback that matches the existing UI design.
|
Caution Review failedFailed to post review comments Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds a new dashboard with metrics and productivity data, expands CRUD and AJAX behaviors for notes and reminders (including recurrence), introduces full profile management, enhances tasks controller flows, updates models with casts/scopes/utilities, adds schema changes via migrations, seeds test data, and overhauls numerous Blade UIs with new CSS assets. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Browser
participant DashboardController
participant DB as Models/DB
User->>Browser: Open Dashboard
Browser->>DashboardController: GET /dashboard
DashboardController->>DB: Fetch counts, recent items, distributions
DB-->>DashboardController: Data
DashboardController-->>Browser: Render dashboard view
User->>Browser: Change period (week/month/year)
Browser->>DashboardController: GET /dashboard/productivity?period=...
DashboardController->>DB: Aggregate completed tasks per period
DB-->>DashboardController: Labels/Data
DashboardController-->>Browser: JSON {labels, data}
Browser-->>User: Update Chart.js line chart
sequenceDiagram
autonumber
actor User
participant Browser
participant NoteController
participant DB as Note Model
User->>Browser: Type search / toggle filters
Browser->>NoteController: GET /notes (AJAX with filters)
NoteController->>DB: Apply scopes (search, byCategory, favorites)
DB-->>NoteController: Filtered notes
NoteController-->>Browser: JSON {html, count}
Browser-->>User: Replace grid/list partial
User->>Browser: Click Favorite
Browser->>NoteController: PATCH /notes/{id}/toggle-favorite
NoteController->>DB: Toggle is_favorite
DB-->>NoteController: Updated note
NoteController-->>Browser: JSON {is_favorite}
Browser-->>User: Update icon/state
sequenceDiagram
autonumber
actor User
participant Browser
participant ReminderController
participant Reminder as Reminder Model
User->>Browser: Click Complete on reminder
Browser->>ReminderController: POST /reminders/{id}/toggle-complete
ReminderController->>Reminder: markAsCompleted()
alt Recurring
Reminder->>Reminder: createNextOccurrence()
end
ReminderController-->>Browser: JSON {is_completed, completed_at}
Browser-->>User: Update card state
User->>Browser: Snooze (enter minutes)
Browser->>ReminderController: POST /reminders/{id}/snooze {minutes}
ReminderController->>Reminder: snooze(minutes)
ReminderController-->>Browser: JSON {snooze_until}
Browser-->>User: Show snoozed state
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 55
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
database/seeders/DatabaseSeeder.php (1)
16-21: Avoid hard-coded admin credentials; gate seeding in productionSeeding a fixed admin with a known password is risky.
public function run(): void { - User::factory()->create([ + if (app()->isProduction()) { + // Avoid creating default admin in production + return; + } + User::factory()->create([ 'name' => 'Arafat Hossain', 'email' => 'admin@example.com', - 'password' => bcrypt('secret'), + 'password' => bcrypt('secret'), ]); }Optionally switch to Hash::make and source the password from env for local/testing. I can provide a hardened variant if desired.
app/Models/User.php (1)
45-47: Hash passwords via model cast (critical)With password mass-assignable, ensure automatic hashing to prevent plaintext storage.
- protected $casts = [ - 'email_verified_at' => 'datetime', - ]; + protected $casts = [ + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + ];As per Laravel 11 best practices, the hashed cast is the safest default. Based on learnings.
resources/views/layouts/app.blade.php (1)
4-12: Add CSRF meta tag to enable AJAX POSTs (prevents 419).AJAX in reminders uses X-CSRF-TOKEN from a meta tag that’s missing here. Add it in the head.
Apply this diff:
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="csrf-token" content="{{ csrf_token() }}"> <title> @yield('title') | Task Manager </title>
♻️ Duplicate comments (1)
resources/views/reminders/index.blade.php (1)
631-635: Prefer explicit messages for clarity (duplicate of prior review).Use predefined messages instead of templating status strings.
- const status = data.is_completed ? 'completed' : 'reactivated'; - showNotification(`Reminder ${status} successfully!`); + showNotification(data.is_completed ? 'Reminder marked as completed!' : 'Reminder reactivated successfully!');
🧹 Nitpick comments (76)
public/assets/tasks/style.css (4)
226-233: Consolidate/namespace duplicate priority/status badge/dot stylesMultiple definitions for .priority-badge, .status-badge, and dot modifiers appear in separate sections. Consider consolidating or namespacing (e.g., .task-card .priority-badge vs .task-header .priority-badge) to reduce cascade surprises.
Please confirm intended scopes so we can propose a precise consolidation.
Also applies to: 605-617, 648-668, 689-699, 1479-1510
596-603: Duplicate .task-meta selector with different rulesThere are two .task-meta blocks in the file (Lines 218-224 and here). To avoid unintended overrides, scope them to their containers (e.g., .task-card .task-meta and .task-header .task-meta).
355-368: Add visible focus styles for keyboard accessibilityMany interactive elements define :hover but lack :focus-visible. Add a consistent outline for accessibility.
+/* Accessible focus styles */ +.view-toggle button:focus-visible, +.quick-action-btn:focus-visible, +.task-btn:focus-visible, +.btn-primary:focus-visible, +.btn-secondary:focus-visible, +.btn-danger:focus-visible, +.header-action-btn:focus-visible, +.add-task-btn:focus-visible { + outline: 3px solid var(--primary-300); + outline-offset: 2px; +}Also applies to: 466-485, 286-298, 1541-1558, 1521-1539, 1560-1577, 63-73, 300-309, 481-497
1053-1062: Respect prefers-reduced-motionAdd a reduced-motion guard for shimmer and motion-heavy hover/drag effects.
+@media (prefers-reduced-motion: reduce) { + * { + animation: none !important; + transition: none !important; + } + .progress-bar::after { + animation: none !important; + } + .task-card:hover, + .btn-start:hover, + .btn-pause:hover, + .btn-stop:hover { + transform: none !important; + } +}Also applies to: 187-197, 917-945, 929-933, 941-945
resources/views/errors/403.blade.php (1)
13-148: Move large inline CSS to a versioned asset or Blade component.Reduces duplication (similar blocks across views), improves caching and maintainability; consider Vite-pipelined CSS.
database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php (1)
14-18: Consider indexing columns used for filtering.If you filter by category or is_favorite, add indexes to avoid table scans.
Example:
Schema::table('notes', function (Blueprint $table) { - $table->string('category')->nullable()->after('content'); + $table->string('category')->nullable()->after('content')->index(); $table->json('tags')->nullable()->after('category'); - $table->boolean('is_favorite')->default(false)->after('tags'); + $table->boolean('is_favorite')->default(false)->after('tags')->index(); });If your test environment uses SQLite, confirm migrations can drop multiple columns without extra packages. Newer Laravel versions handle this, but older SQLite builds may not.
resources/views/routines/index.blade.php (1)
6-212: Centralize repeated inline CSS.This CSS largely duplicates edit/create/weekly views. Extract to a shared stylesheet or Blade component for consistency and caching.
resources/views/routines/create.blade.php (1)
6-197: Centralize repeated inline CSS.Same suggestion as edit/index/weekly views for maintainability and performance.
resources/views/routines/weekly.blade.php (2)
218-226: Avoid decoding JSON twice per render; compute once.Minor perf/readability improvement.
-<div class="routine-meta"> - <div class="meta-item"> - <i class="fas fa-calendar-week"></i> - <span>{{ implode(', ', array_slice(json_decode($routine->weeks, true) ?? [], 0, 3)) }}{{ count(json_decode($routine->weeks, true) ?? []) > 3 ? '...' : '' }}</span> - </div> +@php($weeks = json_decode($routine->weeks, true) ?? []) +<div class="routine-meta"> + <div class="meta-item"> + <i class="fas fa-calendar-week"></i> + <span>{{ implode(', ', array_slice($weeks, 0, 3)) }}{{ count($weeks) > 3 ? '...' : '' }}</span> + </div>
6-177: Centralize repeated inline CSS.Same patterned CSS as other routine views; extract to a shared asset.
resources/views/projects/create.blade.php (6)
20-25: Remove duplicate .btn-primary:hover ruleThe later block (Lines 260-265) overrides the earlier (Lines 20-25). Drop the earlier to avoid confusion.
- .btn-primary:hover { - background: var(--primary-700); - border-color: var(--primary-700); - transform: translateY(-1px); - box-shadow: var(--shadow-md); - }Also applies to: 260-265
505-519: Set initial end_date min based on default start_dateCurrently only updates on change. Initialize once on load to enforce constraints immediately.
document.addEventListener('DOMContentLoaded', function() { const form = document.getElementById('createProjectForm'); const startDateInput = document.getElementById('start_date'); const endDateInput = document.getElementById('end_date'); + // Initialize min end date on load if start date has a default + if (startDateInput?.value) { + endDateInput.min = startDateInput.value; + } // Auto-set minimum end date based on start date startDateInput.addEventListener('change', function() {
545-563: Guard against Quill not loading from CDNAvoid runtime errors if the CDN fails. Early-return if Quill is unavailable.
- // Initialize Quill editor - var quill = new Quill('#quill-editor', { + // Initialize Quill editor + if (typeof window.Quill === 'undefined') { + console.error('Quill failed to load.'); + return; + } + var quill = new Quill('#quill-editor', { theme: 'snow', placeholder: 'Describe your project goals, objectives, and key deliverables...', modules: {
566-569: Use clipboard API for initial Quill contentPreserves formatting and produces correct Delta state.
- if (initialContent) { - quill.root.innerHTML = initialContent; - } + if (initialContent) { + quill.clipboard.dangerouslyPasteHTML(initialContent); + }
571-579: Bind submit handler to the specific formAvoids accidental binding to the wrong form if more are added.
- // Update hidden textarea before form submission - document.querySelector('form').addEventListener('submit', function() { - document.getElementById('description').value = quill.root.innerHTML; - }); + // Update hidden textarea before form submission + form.addEventListener('submit', function() { + document.getElementById('description').value = quill.root.innerHTML; + });
584-585: Add SRI to CDN assetsInclude integrity and crossorigin to mitigate CDN tampering.
-<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet"> -<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script> +<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet" integrity="..." crossorigin="anonymous"> +<script src="https://cdn.quilljs.com/1.3.6/quill.min.js" integrity="..." crossorigin="anonymous"></script>Fetch the exact integrity hashes from Quill’s official CDN or subresource-integrity catalog.
database/migrations/2024_05_21_195305_create_tasks_table.php (1)
23-23: Make estimated_hours unsignedNegative hours don’t make sense; enforce at the schema level.
- $table->decimal('estimated_hours', 5, 2)->nullable(); + $table->unsignedDecimal('estimated_hours', 5, 2)->nullable();If unsignedDecimal isn’t available in your DB/driver, use:
$table->decimal('estimated_hours', 5, 2)->unsigned()->nullable();app/Models/Task.php (1)
19-20: Cast estimated_hours to decimal for consistent serializationEnsures consistent precision in arrays/JSON and avoids string casting differences across drivers.
Add this property to the model:
protected $casts = [ 'estimated_hours' => 'decimal:2', ];app/Models/User.php (1)
81-88: Fix docblock to match relation (minor)Doc says “calendar events” but relation is files.
- /** - * Get the calendar events for the user. - */ + /** + * Get the files for the user. + */resources/views/errors/500.blade.php (1)
8-12: CDN assets lack SRI/CSP considerationAdd Subresource Integrity and crossorigin on CDN links or serve via Vite/asset() to align with CSP and avoid supply‑chain risk. Consider consolidating styles into your layout to reduce duplication.
resources/views/reminders/show.blade.php (2)
6-272: Inline CSS is large; move to versioned assetsExtract styles into a shared CSS (e.g., via Vite) to improve cacheability, reduce HTML size, and respect CSP.
481-499: Gate actions with policiesHide UI when unauthorized to reduce 403s and accidental attempts.
Apply:
- <form action="{{ route('reminders.duplicate', $reminder) }}" method="POST" class="d-inline"> + @can('create', \App\Models\Reminder::class) + <form action="{{ route('reminders.duplicate', $reminder) }}" method="POST" class="d-inline"> @csrf <button type="submit" class="btn-modern btn-info" onclick="return confirm('Create a duplicate of this reminder?')"> <i class="fas fa-copy"></i>Duplicate </button> </form> + @endcan - - <a href="{{ route('reminders.edit', $reminder) }}" class="btn-modern btn-primary"> + @can('update', $reminder) + <a href="{{ route('reminders.edit', $reminder) }}" class="btn-modern btn-primary"> <i class="fas fa-edit"></i>Edit </a> + @endcan - - <form action="{{ route('reminders.destroy', $reminder) }}" method="POST" class="d-inline"> + @can('delete', $reminder) + <form action="{{ route('reminders.destroy', $reminder) }}" method="POST" class="d-inline"> @csrf @method('DELETE') <button type="submit" class="btn-modern btn-danger" onclick="return confirm('Are you sure you want to delete this reminder?')"> <i class="fas fa-trash"></i>Delete </button> </form> + @endcanresources/views/files/index.blade.php (2)
227-265: Counts derived from $files may be incorrect/expensive with paginationIf $files is a paginator, ->where(...) isn’t available and counts are per‑page, not global. Compute stats in the controller and pass as separate vars, or use $files->getCollection() only if intentionally per‑page.
Would you like a quick refactor snippet for the controller to provide total/project/docs/code counts?
309-318: Gate edit/delete with policiesHide actions when unauthorized.
- <a href="{{ route('files.edit', $file->id) }}" class="btn-modern btn-warning" title="Edit"> + @can('update', $file) + <a href="{{ route('files.edit', $file->id) }}" class="btn-modern btn-warning" title="Edit"> <i class="fas fa-edit"></i> </a> + @endcan - <form action="{{ route('files.destroy', $file->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this file?');"> + @can('delete', $file) + <form action="{{ route('files.destroy', $file->id) }}" method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this file?');"> @csrf @method('DELETE') <button type="submit" class="btn-modern btn-danger" title="Delete"> <i class="fas fa-trash"></i> </button> </form> + @endcanresources/views/reminders/partials/list-view.blade.php (2)
95-106: Authorize per‑item actionsWrap Complete/Reactivate and Delete with policies to prevent exposing actions when user lacks permission.
- @if(!$reminder->is_completed) - <button onclick="toggleComplete({{ $reminder->id }})" class="action-btn success" title="Mark as Complete"> + @can('update', $reminder) + @if(!$reminder->is_completed) + <button onclick="toggleComplete({{ $reminder->id }})" class="action-btn success" title="Mark as Complete"> <i class="fas fa-check"></i> <span>Complete</span> </button> @else <button onclick="toggleComplete({{ $reminder->id }})" class="action-btn" title="Mark as Incomplete"> <i class="fas fa-undo"></i> <span>Reactivate</span> </button> @endif + @endcan @@ - <form action="{{ route('reminders.destroy', $reminder) }}" method="POST" style="display: inline;" onsubmit="return confirm('Are you sure you want to delete this reminder?')"> + @can('delete', $reminder) + <form action="{{ route('reminders.destroy', $reminder) }}" method="POST" style="display: inline;" onsubmit="return confirm('Are you sure you want to delete this reminder?')"> @csrf @method('DELETE') <button type="submit" class="action-btn danger" title="Delete"> <i class="fas fa-trash"></i> <span>Delete</span> </button> </form> + @endcanAlso applies to: 108-114, 125-133
142-153: Filter defaults may be nullrequest('category')/request('priority') can be null; comparisons to 'all' may mislead empty state messaging. Use defaults.
- @if(request('search') || request('category') !== 'all' || request('priority') !== 'all') + @if(request('search') || request('category', 'all') !== 'all' || request('priority', 'all') !== 'all') @@ - @if(!request('search') && request('category') === 'all' && request('priority') === 'all') + @if(!request('search') && request('category', 'all') === 'all' && request('priority', 'all') === 'all')resources/views/errors/404.blade.php (1)
8-12: CDN assets lack SRI/CSP considerationSame as 500: add SRI+crossorigin or bundle via Vite to align with CSP and reduce duplication.
database/migrations/2025_09_11_044754_add_profile_fields_to_users_table.php (1)
15-20: Allow longer URLs forwebsiteSome valid URLs exceed 255 chars. Use a larger length (e.g., 2048) to avoid truncation.
- $table->string('website')->nullable()->after('location'); + $table->string('website', 2048)->nullable()->after('location');database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php (1)
15-26: Portability and indexing for new reminder fields
- Enums: Database portability issue (notably Postgres leaves enum types behind after drop). Prefer
stringwith app-level constants/validation, or ensure type cleanup in down() for PG.- Timezones: Use
timestampTzfor user-facing times (completed_at,snooze_until).- Indexing: Add indexes to common filters to avoid table scans (
is_completed,snooze_until,notification_sent, optionallypriority,recurrence_type).Incremental improvements without changing enums:
- $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->timestampTz('completed_at')->nullable()->after('is_completed'); + $table->timestampTz('snooze_until')->nullable()->after('completed_at')->index(); + $table->boolean('notification_sent')->default(false)->after('snooze_until')->index(); - $table->boolean('is_completed')->default(false)->after('recurrence_interval'); + $table->boolean('is_completed')->default(false)->after('recurrence_interval')->index(); - $table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium')->after('description'); + $table->enum('priority', ['low', 'medium', 'high', 'urgent'])->default('medium')->after('description')->index(); - $table->enum('recurrence_type', ['none', 'daily', 'weekly', 'monthly', 'yearly'])->default('none')->after('is_recurring'); + $table->enum('recurrence_type', ['none', 'daily', 'weekly', 'monthly', 'yearly'])->default('none')->after('is_recurring')->index();If you keep enums on Postgres, consider dropping enum types in down():
// In down(), after dropColumn(...): if (Schema::getConnection()->getDriverName() === 'pgsql') { DB::statement('DROP TYPE IF EXISTS reminders_priority;'); DB::statement('DROP TYPE IF EXISTS reminders_recurrence_type;'); }Please confirm the primary DB driver (MySQL vs Postgres) so we can tailor the migration accordingly. Based on learnings
resources/views/notes/show.blade.php (4)
338-362: Harden favorite toggle fetch (content negotiation + basic error handling)Ensure JSON response and avoid silent failures.
- fetch(`/notes/${noteId}/toggle-favorite`, { + fetch(`/notes/${noteId}/toggle-favorite`, { method: 'PATCH', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, - 'Content-Type': 'application/json', + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-Requested-With': 'XMLHttpRequest', } }) - .then(response => response.json()) + .then(response => response.ok ? response.json() : Promise.reject(response)) .then(data => { if (data.success) { this.classList.toggle('active', data.is_favorite); // Update status badge const statusBadge = document.querySelector('.meta-value .badge'); if (statusBadge) { if (data.is_favorite) { statusBadge.className = 'badge bg-warning'; statusBadge.textContent = 'Favorite'; } else { statusBadge.className = 'badge bg-secondary'; statusBadge.textContent = 'Regular'; } } } - }); + }) + .catch(() => alert('Failed to toggle favorite. Please try again.'));
206-211: Add aria-labels to icon-only buttonsImprove screen reader usability for icon-only controls.
- <button class="favorite-btn {{ $note->is_favorite ? 'active' : '' }}" - data-note-id="{{ $note->id }}" title="Toggle Favorite"> + <button class="favorite-btn {{ $note->is_favorite ? 'active' : '' }}" + data-note-id="{{ $note->id }}" title="Toggle Favorite" aria-label="Toggle favorite"> <i class="fas fa-star"></i> </button> ... - <a href="{{ route('notes.edit', $note->id) }}" class="btn btn-outline-primary btn-sm"> + <a href="{{ route('notes.edit', $note->id) }}" class="btn btn-outline-primary btn-sm" aria-label="Edit note"> <i class="fas fa-edit me-2"></i>Edit Note </a> - <button class="btn btn-outline-success btn-sm" onclick="duplicateNote({{ $note->id }})"> + <button class="btn btn-outline-success btn-sm" onclick="duplicateNote({{ $note->id }})" aria-label="Duplicate note"> <i class="fas fa-copy me-2"></i>Duplicate Note </button> - <button class="btn btn-outline-info btn-sm" onclick="copyToClipboard()"> + <button class="btn btn-outline-info btn-sm" onclick="copyToClipboard()" aria-label="Copy note content"> <i class="fas fa-clipboard me-2"></i>Copy Content </button>Also applies to: 306-313, 311-315
262-264: Use multibyte-safe length for character count
strlencounts bytes. PreferStr::lengthfor proper character count.- <span class="meta-value">{{ strlen(strip_tags($note->content)) }} chars</span> + <span class="meta-value">{{ \Illuminate\Support\Str::length(strip_tags($note->content)) }} chars</span>
6-177: Move inline styles to a versioned assetLarge inline CSS hinders caching and maintainability. Extract to a stylesheet (e.g.,
public/assets/notes/show.css) and include via your layout’s asset pipeline.resources/views/notes/partials/notes-grid.blade.php (1)
25-30: Add aria-labels to icon-only favorite buttonsImprove accessibility for keyboard and screen readers.
- <button class="note-favorite {{ $note->is_favorite ? 'active' : '' }}" - data-note-id="{{ $note->id }}" title="Toggle Favorite"> + <button class="note-favorite {{ $note->is_favorite ? 'active' : '' }}" + data-note-id="{{ $note->id }}" title="Toggle Favorite" aria-label="Toggle favorite"> <i class="fas fa-star"></i> </button> ... - <button class="note-favorite {{ $note->is_favorite ? 'active' : '' }}" - data-note-id="{{ $note->id }}" title="Toggle Favorite"> + <button class="note-favorite {{ $note->is_favorite ? 'active' : '' }}" + data-note-id="{{ $note->id }}" title="Toggle Favorite" aria-label="Toggle favorite"> <i class="fas fa-star"></i> </button>Also applies to: 84-89
resources/views/profile/password.blade.php (1)
542-557: Avoid alert() for validation feedback; use inline invalid states.Prefer setting .is-invalid with contextual feedback near fields instead of alert(), for accessibility and UX consistency.
resources/views/routines/daily.blade.php (1)
218-227: Safer days rendering and resilient decoding.Consider decoding/casting days at the model level (e.g., $casts = ['days' => 'array']) and here just
implode(', ', $routine->days ?? [])to avoid json_decode in the view and improve robustness.resources/views/notes/create.blade.php (2)
506-507: Initialize tags from hidden input (old values/draft).Parse existing CSV into the tags array on load.
- let tags = []; + let tags = (tagsHidden.value || '') + .split(',') + .map(t => t.trim()) + .filter(Boolean); + if (tags.length) { + updateTagsDisplay(); + updateHiddenInput(); + }
264-265: Add integrity and crossorigin attributes to Quill CDN linksInclude SRI for both CSS and JS:
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet" integrity="sha384-07UbSXbd8HpaOfxZsiO6Y8H1HTX6v0J96b5qP6PKSpYEuSZSYD4GFFHlLRjvjVrL" crossorigin="anonymous"> <script src="https://cdn.quilljs.com/1.3.6/quill.min.js" integrity="sha384-AOnYUgW6MQR78EvTqtkbNavz7dVI7dtLm7QdcIMBoy06adLIzNT40hHPYl9Rr5m5" crossorigin="anonymous"></script>Also apply at lines 474–475.
app/Http/Controllers/ProfileController.php (1)
92-97: Consider password rotation UX.After updating, consider flashing a message to re-login on sensitive routes or invalidating existing tokens if APIs are used.
resources/views/profile/show.blade.php (2)
274-299: Reduce DB calls for stats.Each
->count()triggers a query. Precompute counts in the controller and pass them to the view, or eager load counts if relations support it.
394-401: “Password last updated” may be inaccurate.Using
$user->updated_atreflects any profile change, not password changes. Track a dedicatedpassword_changed_atif you need accurate display.resources/views/reminders/create.blade.php (1)
424-469: Make recurring fields conditionally required.When
is_recurringis checked, enforce required validation client-side (and server-side) forrecurrence_typeandrecurrence_intervalto prevent inconsistent submissions.resources/views/projects/index.blade.php (3)
436-446: Avoid N+1 on tasks and team membersThis view calls $project->tasks and team members per item. Eager-load in controller: Project::with(['tasks', 'users'])->... to prevent N+1.
466-471: Implement deadline sortingend_date sorting currently returns 0. Add a data attribute and sort by it.
- data-created="{{ $project->created_at->timestamp }}"> + data-created="{{ $project->created_at->timestamp }}" + data-end="{{ optional($project->end_date)->timestamp ?? 0 }}">- case 'end_date': - // Implement date sorting if needed - return 0; + case 'end_date': + return parseInt(b.dataset.end) - parseInt(a.dataset.end);Also applies to: 624-644
420-427: Improve accessibility of view toggle buttonsAdd aria-label and aria-pressed updates.
- <button class="view-toggle-btn active" data-view="grid"> + <button class="view-toggle-btn active" data-view="grid" aria-label="Grid view" aria-pressed="true"> @@ - <button class="view-toggle-btn" data-view="list"> + <button class="view-toggle-btn" data-view="list" aria-label="List view" aria-pressed="false">Update toggleView to set aria-pressed accordingly.
resources/views/files/create.blade.php (2)
429-434: Make drag‑and‑drop file assignment cross‑browserSetting fileInput.files directly may fail on Safari/Firefox. Use DataTransfer when available and fall back.
fileUploadArea.addEventListener('drop', (e) => { e.preventDefault(); fileUploadArea.classList.remove('dragover'); - fileInput.files = e.dataTransfer.files; - handleFileSelect(); + const files = e.dataTransfer.files; + if (window.DataTransfer && files && files.length) { + try { + const dt = new DataTransfer(); + Array.from(files).forEach(f => dt.items.add(f)); + fileInput.files = dt.files; + } catch (_) { + fileInput.files = files; // fallback + } + } + handleFileSelect(); });
324-329: Optional: constrain selectable file typesAdd accept to hint allowed types and improve UX.
- <input type="file" + <input type="file" + accept=".pdf,.doc,.docx,.txt,.png,.jpg,.jpeg,.zip"resources/views/projects/show.blade.php (1)
514-660: Performance: eager-load tasks and usersThis view computes multiple aggregates and lists users. Eager-load in controller: Project::with(['tasks', 'users'])->findOrFail($id).
database/seeders/TestDataSeeder.php (3)
46-55: $project2 is unusedEither seed tasks for it or drop the variable to satisfy static analysis.
- $project2 = Project::firstOrCreate( + Project::firstOrCreate([Based on static analysis hints]
72-88: $task variable is unusedNo need to capture return value from firstOrCreate in this loop.
- foreach ($taskData as $data) { - $task = Task::firstOrCreate( + foreach ($taskData as $data) { + Task::firstOrCreate([Based on static analysis hints]
135-154: Use Carbon instances for casted date/time fieldsReminder::$casts sets 'date' as datetime. Provide Carbon for 'date' and full 'H:i:s' time string for consistency.
- 'date' => now()->addHours(2)->format('Y-m-d'), - 'time' => now()->addHours(2)->format('H:i'), + 'date' => now()->addHours(2), + 'time' => now()->addHours(2)->format('H:i:s'), @@ - 'date' => now()->addDays(20)->format('Y-m-d'), - 'time' => '09:00', + 'date' => now()->addDays(20), + 'time' => '09:00:00',resources/views/layouts/app.blade.php (1)
541-548: Avoid querying Eloquent directly in Blade (move counts to controller/composer).Multiple DB calls in the view hurt performance and testability. Pass $projectCount/$taskCount from a controller or a view composer and cache if needed.
Also applies to: 553-555
resources/views/projects/edit.blade.php (2)
641-644: Bind submit handler to the intended form (avoid selecting a different form).Use the existing form variable; don’t query the first form in the DOM.
- document.querySelector('form').addEventListener('submit', function() { + form.addEventListener('submit', function() { document.getElementById('description').value = quill.root.innerHTML; });
407-413: Remove unused $projectColor (or use it).Computed but not applied; delete the block or use it to theme the header/avatar.
resources/views/reminders/index.blade.php (1)
468-471: Label/value mismatch in View select.Value “list” is labeled “Grid View.” Rename the label to “List View” or adjust value to “grid” for consistency.
- <option value="list" {{ $view === 'list' ? 'selected' : '' }}>Grid View</option> + <option value="list" {{ $view === 'list' ? 'selected' : '' }}>List View</option>app/Http/Controllers/NoteController.php (1)
33-40: DRY: centralize categories retrieval.You repeat the categories query in index/create/edit; extract to a private helper or a view composer.
Also applies to: 53-59, 101-107
app/Http/Controllers/DashboardController.php (3)
53-57: Exclude completed reminders from “Upcoming reminders”.Only show active upcoming items.
- $upcomingReminders = $user->reminders() - ->where('date', '>=', now()) + $upcomingReminders = $user->reminders() + ->where('is_completed', false) + ->where('date', '>=', now()) ->orderBy('date') ->take(5) ->get();
133-168: Reduce N+1 queries in productivity data (group counts).Current loops run a query per day/month. Use grouped queries and map results to labels.
- 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(); - } + case 'week': + $startDate = now()->startOfWeek(); + $endDate = now()->endOfWeek(); + $labels = collect(range(0, 6)) + ->map(fn ($i) => $startDate->copy()->addDays($i)->format('M j')) + ->all(); + $rows = $user->tasks() + ->selectRaw('DATE(updated_at) as d, COUNT(*) as c') + ->where('status', 'completed') + ->whereBetween('updated_at', [$startDate, $endDate]) + ->groupBy('d') + ->pluck('c', 'd'); + $data = collect(range(0, 6))->map(function ($i) use ($startDate, $rows) { + $key = $startDate->copy()->addDays($i)->toDateString(); + return (int) ($rows[$key] ?? 0); + })->all(); break;Apply the same pattern for 'month' and 'year' (group by DATE and MONTH respectively).
25-33: Many individual count queries; consider aggregating.You can compute task status/priority distributions and totals in fewer queries using conditional sums, improving load time for busy dashboards.
Also applies to: 78-99
resources/views/profile/edit.blade.php (2)
258-260: Use UTF‑8 safe initials (avoid breaking multibyte names).Prefer Str helpers instead of substr/strtoupper for names with non‑ASCII characters.
- <div class="avatar-placeholder-edit" id="avatar-preview"> - {{ strtoupper(substr($user->name, 0, 2)) }} - </div> + <div class="avatar-placeholder-edit" id="avatar-preview"> + {{ \Illuminate\Support\Str::of($user->name)->substr(0, 2)->upper() }} + </div>
481-501: Harden avatar delete fetch (headers + non‑JSON/204 handling + user feedback).Add X-Requested-With, handle non‑JSON responses, and surface errors to users.
- fetch('{{ route("profile.avatar.delete") }}', { - method: 'DELETE', - headers: { - 'X-CSRF-TOKEN': '{{ csrf_token() }}', - 'Accept': 'application/json', - } - }) - .then(response => response.json()) - .then(data => { - if (data.success) { - location.reload(); - } - }) + fetch('{{ route("profile.avatar.delete") }}', { + method: 'DELETE', + headers: { + 'X-CSRF-TOKEN': '{{ csrf_token() }}', + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'application/json', + } + }) + .then(async (response) => { + // Handle 204 or non-JSON gracefully + const isJson = (response.headers.get('content-type') || '').includes('application/json'); + const payload = isJson ? await response.json().catch(() => ({})) : {}; + if (!response.ok) throw new Error(payload.message || `HTTP ${response.status}`); + if (payload.success !== false) location.reload(); + }) .catch(error => { - console.error('Error:', error); + console.error('Error:', error); + alert('Failed to remove avatar. Please try again.'); });resources/views/auth/login.blade.php (1)
295-303: Improve form autofill/accessibility with autocomplete hints.Add autocomplete to help password managers and a11y.
- <input + <input type="email" name="email" id="email" class="form-control @error('email') is-invalid @enderror" placeholder="Enter your email" value="{{ old('email') }}" - required + required + autocomplete="username email" autofocus >- <input + <input type="password" name="password" id="password" class="form-control @error('password') is-invalid @enderror" placeholder="Enter your password" - required + required + autocomplete="current-password" >resources/views/dashboard.blade.php (3)
97-103: Wire up period selector to refresh chart data.Currently unused. Add change handler to re‑fetch with period.
- <select class="form-select form-select-sm" id="periodSelect"> + <select class="form-select form-select-sm" id="periodSelect" aria-label="Select period for productivity chart"> <option value="week">This Week</option> <option value="month">This Month</option> <option value="year">This Year</option> </select>And in the scripts block:
document.addEventListener('DOMContentLoaded', function() { + const periodSelect = document.getElementById('periodSelect'); // Productivity Chart const chartCanvas = document.getElementById('productivityChart'); @@ - // Fetch productivity data - fetch('{{ route("dashboard.productivity-data") }}') + function loadProductivity(period) { + const url = new URL('{{ route("dashboard.productivity-data") }}', window.location.origin); + if (period) url.searchParams.set('period', period); + return fetch(url.toString()) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { - console.log('Productivity chart data received:', data); - - new Chart(ctx, { + if (window.productivityChart) { + window.productivityChart.data.labels = data.labels; + window.productivityChart.data.datasets[0].data = data.data; + window.productivityChart.update(); + return; + } + window.productivityChart = new Chart(ctx, { type: 'line', data: { labels: data.labels, datasets: [{ label: 'Tasks Completed', data: data.data, @@ } }); - - console.log('Productivity chart initialized successfully'); }); - .catch(error => { - console.error('Error loading productivity data:', error); - }); + } + + loadProductivity(periodSelect?.value); + periodSelect?.addEventListener('change', () => loadProductivity(periodSelect.value));
454-565: Remove or gate console logging.Ship without verbose console logs; gate behind env flag if needed.
- console.log('DOM loaded, initializing charts...'); + // if (window.APP_DEBUG) console.log('DOM loaded, initializing charts...'); @@ - console.error('Productivity chart canvas element not found'); + // if (window.APP_DEBUG) console.error('Productivity chart canvas element not found'); @@ - console.log('Productivity chart canvas found'); + // if (window.APP_DEBUG) console.log('Productivity chart canvas found'); @@ - console.log('Productivity chart data received:', data); + // if (window.APP_DEBUG) console.log('Productivity chart data received:', data); @@ - console.log('Productivity chart initialized successfully'); + // if (window.APP_DEBUG) console.log('Productivity chart initialized successfully'); @@ - console.error('Error loading productivity data:', error); + // if (window.APP_DEBUG) console.error('Error loading productivity data:', error); @@ - console.log('Status chart canvas found'); + // if (window.APP_DEBUG) console.log('Status chart canvas found'); @@ - console.log('Status chart data:', statusData); + // if (window.APP_DEBUG) console.log('Status chart data:', statusData); @@ - console.log('Status chart initialized successfully'); + // if (window.APP_DEBUG) console.log('Status chart initialized successfully'); @@ - console.error('Status chart canvas element not found'); + // if (window.APP_DEBUG) console.error('Status chart canvas element not found');
268-271: Replace placeholder “View all” link with a real route.Avoid dead links; point to an activity or tasks page.
- <a href="#" class="view-all-link">View all</a> + <a href="{{ route('projects.index') }}" class="view-all-link">View all</a>resources/views/files/edit.blade.php (2)
494-499: Drag‑and‑drop: use DataTransfer to set input files (better cross‑browser).Direct assignment to input.files may not work in all browsers. Use DataTransfer.
- fileUploadArea.addEventListener('drop', (e) => { + fileUploadArea.addEventListener('drop', (e) => { e.preventDefault(); fileUploadArea.classList.remove('dragover'); - fileInput.files = e.dataTransfer.files; + const dt = new DataTransfer(); + Array.from(e.dataTransfer.files).forEach(f => dt.items.add(f)); + fileInput.files = dt.files; handleFileSelect(); });Please test in Safari and Firefox. If issues persist, fallback to showing the file name and instructing users to click to select the file.
415-446: Make type options keyboard‑accessible (role, tabindex, key handlers).Improve a11y for the clickable cards.
- <div class="type-options"> + <div class="type-options" role="radiogroup" aria-label="File type"> - <div class="type-option {{ $file->type === 'project' ? 'selected' : '' }}" data-type="project"> + <div class="type-option {{ $file->type === 'project' ? 'selected' : '' }}" data-type="project" role="radio" tabindex="0" aria-checked="{{ $file->type === 'project' ? 'true' : 'false' }}"> @@ - <div class="type-option {{ $file->type === 'docs' ? 'selected' : '' }}" data-type="docs"> + <div class="type-option {{ $file->type === 'docs' ? 'selected' : '' }}" data-type="docs" role="radio" tabindex="0" aria-checked="{{ $file->type === 'docs' ? 'true' : 'false' }}"> @@ - <div class="type-option {{ $file->type === 'txt' ? 'selected' : '' }}" data-type="txt"> + <div class="type-option {{ $file->type === 'txt' ? 'selected' : '' }}" data-type="txt" role="radio" tabindex="0" aria-checked="{{ $file->type === 'txt' ? 'true' : 'false' }}"> @@ - <div class="type-option {{ $file->type === 'code' ? 'selected' : '' }}" data-type="code"> + <div class="type-option {{ $file->type === 'code' ? 'selected' : '' }}" data-type="code" role="radio" tabindex="0" aria-checked="{{ $file->type === 'code' ? 'true' : 'false' }}"> @@ - <div class="type-option {{ $file->type === 'image' ? 'selected' : '' }}" data-type="image"> + <div class="type-option {{ $file->type === 'image' ? 'selected' : '' }}" data-type="image" role="radio" tabindex="0" aria-checked="{{ $file->type === 'image' ? 'true' : 'false' }}">Add keyboard handling in the script:
typeOptions.forEach(option => { option.addEventListener('click', () => { typeOptions.forEach(opt => opt.classList.remove('selected')); option.classList.add('selected'); typeInput.value = option.dataset.type; + typeOptions.forEach(opt => opt.setAttribute('aria-checked', opt === option ? 'true' : 'false')); }); + option.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + option.click(); + } + }); });app/Models/Note.php (3)
34-37: Make excerpt Unicode‑safe and avoid double strip_tags.Strip first, then mb‑safe truncate; trim trailing spaces.
- public function getExcerptAttribute() - { - return strip_tags(substr($this->content, 0, 150)) . (strlen(strip_tags($this->content)) > 150 ? '...' : ''); - } + public function getExcerptAttribute() + { + $plain = trim(strip_tags((string) $this->content)); + return \Illuminate\Support\Str::limit($plain, 150); + }
49-52: Parse time with expected format to avoid timezone/locale ambiguity.Prefer createFromFormat for a pure time column.
- public function getFormattedTimeAttribute() - { - return $this->time ? Carbon::parse($this->time)->format('g:i A') : null; - } + public function getFormattedTimeAttribute() + { + if (!$this->time) return null; + // Supports 'H:i:s' and 'H:i' + $time = is_string($this->time) ? $this->time : (string) $this->time; + $fmt = strlen($time) === 5 ? 'H:i' : 'H:i:s'; + return Carbon::createFromFormat($fmt, $time)->format('g:i A'); + }
64-71: Guard empty search to avoid unnecessary LIKE scans.Short‑circuit when $search is empty; reduces full table scans.
- 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}%"); - }); - } + public function scopeSearch($query, $search) + { + $term = trim((string) $search); + if ($term === '') return $query; + return $query->where(function ($q) use ($term) { + $q->where('title', 'like', "%{$term}%") + ->orWhere('content', 'like', "%{$term}%") + ->orWhere('category', 'like', "%{$term}%"); + }); + }resources/views/reminders/edit.blade.php (1)
6-214: Consider extracting inline styles to a versioned CSS assetLarge inline CSS reduces cacheability and bloats the view. Move to an asset (e.g., resources/css/reminders.css) and include via the build pipeline for better caching and maintainability.
resources/views/notes/edit.blade.php (1)
272-274: CDN assets: add SRI or self-host for CSP/offlineInclude Subresource Integrity and crossorigin for the Quill CDN links or serve locally via your asset pipeline to improve security and reliability.
Example:
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet" integrity="sha384-...your-SRI..." crossorigin="anonymous"> <script src="https://cdn.quilljs.com/1.3.6/quill.js" integrity="sha384-...your-SRI..." crossorigin="anonymous"></script>Also applies to: 532-534
app/Http/Controllers/ReminderController.php (2)
266-270: Avoid multiple DB refreshesfresh() is called twice. Refresh once and reuse:
- return response()->json([ - 'success' => true, - 'is_completed' => $reminder->fresh()->is_completed, - 'completed_at' => $reminder->fresh()->completed_at?->format('M j, Y g:i A') - ]); + $reminder->refresh(); + return response()->json([ + 'success' => true, + 'is_completed' => $reminder->is_completed, + 'completed_at' => $reminder->completed_at?->format('M j, Y g:i A'), + ]);
76-84: Reduce N+1 counts for stats (optional)Computing each stat with separate queries can be consolidated if performance becomes a concern (e.g., single grouped counts or cached metrics).
app/Models/Reminder.php (2)
97-114: Account for snoozed reminders in is_overdue / is_due_soon.Snoozed items should not appear overdue/soon until snooze_until passes.
Apply this diff:
public function getIsOverdueAttribute() { - if ($this->is_completed || !$this->formatted_date_time) { + if ($this->is_completed || !$this->formatted_date_time) { return false; } - - return $this->formatted_date_time->isPast(); + if ($this->snooze_until && $this->snooze_until->isFuture()) { + return false; + } + return $this->formatted_date_time->isPast(); } @@ public function getIsDueSoonAttribute() { - if ($this->is_completed || !$this->formatted_date_time) { + if ($this->is_completed || !$this->formatted_date_time) { return false; } - - return $this->formatted_date_time->isBetween(now(), now()->addHours(24)); + if ($this->snooze_until && $this->snooze_until->isFuture()) { + return false; + } + return $this->formatted_date_time->isBetween(now(), now()->addHours(24)); }
172-184: Wrap complete + next-occurrence creation in a transaction.Prevents partial state if replication/save fails after marking completed.
Proposed change (outside this hunk):
use Illuminate\Support\Facades\DB;Then update markAsCompleted:
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(); - } + DB::transaction(function () { + $this->update([ + 'is_completed' => true, + 'completed_at' => now(), + ]); + if ($this->is_recurring && $this->recurrence_type !== self::RECURRENCE_NONE) { + $this->createNextOccurrence(); + } + }); }Also applies to: 194-213
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (49)
app/Http/Controllers/DashboardController.php(1 hunks)app/Http/Controllers/NoteController.php(1 hunks)app/Http/Controllers/ProfileController.php(1 hunks)app/Http/Controllers/ReminderController.php(2 hunks)app/Http/Controllers/TaskController.php(2 hunks)app/Models/Note.php(2 hunks)app/Models/Reminder.php(1 hunks)app/Models/Task.php(1 hunks)app/Models/User.php(1 hunks)database/migrations/2024_05_21_195305_create_tasks_table.php(1 hunks)database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php(1 hunks)database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php(1 hunks)database/migrations/2025_09_11_044754_add_profile_fields_to_users_table.php(1 hunks)database/seeders/DatabaseSeeder.php(1 hunks)database/seeders/TestDataSeeder.php(1 hunks)public/assets/dashboard/style.css(1 hunks)public/assets/tasks/style.css(1 hunks)resources/views/auth/login.blade.php(1 hunks)resources/views/dashboard.blade.php(1 hunks)resources/views/errors/403.blade.php(1 hunks)resources/views/errors/404.blade.php(1 hunks)resources/views/errors/500.blade.php(1 hunks)resources/views/files/create.blade.php(1 hunks)resources/views/files/edit.blade.php(1 hunks)resources/views/files/index.blade.php(1 hunks)resources/views/layouts/app.blade.php(1 hunks)resources/views/notes/create.blade.php(1 hunks)resources/views/notes/edit.blade.php(1 hunks)resources/views/notes/index.blade.php(1 hunks)resources/views/notes/partials/notes-grid.blade.php(1 hunks)resources/views/notes/show.blade.php(1 hunks)resources/views/profile/edit.blade.php(1 hunks)resources/views/profile/password.blade.php(1 hunks)resources/views/profile/show.blade.php(1 hunks)resources/views/projects/create.blade.php(1 hunks)resources/views/projects/edit.blade.php(1 hunks)resources/views/projects/index.blade.php(1 hunks)resources/views/projects/show.blade.php(1 hunks)resources/views/reminders/create.blade.php(1 hunks)resources/views/reminders/edit.blade.php(1 hunks)resources/views/reminders/index.blade.php(1 hunks)resources/views/reminders/partials/list-view.blade.php(1 hunks)resources/views/reminders/show.blade.php(1 hunks)resources/views/routines/create.blade.php(1 hunks)resources/views/routines/daily.blade.php(1 hunks)resources/views/routines/edit.blade.php(1 hunks)resources/views/routines/index.blade.php(1 hunks)resources/views/routines/monthly.blade.php(1 hunks)resources/views/routines/weekly.blade.php(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
app/Http/Controllers/DashboardController.php (7)
app/Models/User.php (7)
User(10-98)tasks(60-63)routines(68-71)notes(76-79)reminders(89-92)files(84-87)projects(52-55)app/Models/Project.php (1)
Project(8-72)app/Models/Task.php (2)
Task(7-50)user(22-25)app/Models/Note.php (2)
Note(8-72)user(29-32)app/Models/Reminder.php (2)
Reminder(9-228)user(53-56)app/Models/Routine.php (1)
Routine(7-27)app/Models/File.php (1)
File(8-23)
app/Models/Note.php (2)
app/Models/Reminder.php (5)
user(53-56)getFormattedDateAttribute(58-61)getFormattedTimeAttribute(63-66)scopeByCategory(157-160)scopeSearch(162-170)app/Models/User.php (1)
User(10-98)
app/Http/Controllers/TaskController.php (4)
app/Models/User.php (3)
User(10-98)tasks(60-63)projects(52-55)app/Models/Project.php (4)
Project(8-72)user(32-35)tasks(37-40)users(68-71)app/Models/Task.php (3)
project(27-30)user(22-25)Task(7-50)app/Models/ChecklistItem.php (1)
task(18-21)
database/seeders/TestDataSeeder.php (8)
app/Models/User.php (1)
User(10-98)app/Models/Project.php (1)
Project(8-72)app/Models/Task.php (2)
Task(7-50)user(22-25)app/Models/Note.php (2)
Note(8-72)user(29-32)app/Models/Reminder.php (2)
Reminder(9-228)user(53-56)app/Models/Routine.php (1)
Routine(7-27)app/Models/File.php (1)
File(8-23)app/Models/ChecklistItem.php (2)
ChecklistItem(8-22)task(18-21)
app/Http/Controllers/NoteController.php (3)
app/Http/Controllers/ReminderController.php (1)
index(13-116)app/Models/Note.php (2)
user(29-32)Note(8-72)app/Models/User.php (1)
notes(76-79)
database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php (2)
database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php (2)
up(12-19)down(24-29)database/migrations/2025_09_11_044754_add_profile_fields_to_users_table.php (2)
up(12-21)down(26-31)
database/migrations/2025_09_10_195218_add_enhanced_fields_to_notes_table.php (1)
database/migrations/2025_09_10_201320_add_enhanced_fields_to_reminders_table.php (2)
up(12-27)down(32-41)
app/Http/Controllers/ReminderController.php (3)
app/Http/Controllers/NoteController.php (2)
index(12-49)create(51-61)app/Models/Reminder.php (4)
user(53-56)Reminder(9-228)markAsCompleted(173-184)snooze(186-192)app/Models/User.php (1)
reminders(89-92)
app/Models/Reminder.php (2)
app/Models/User.php (1)
User(10-98)app/Http/Controllers/ReminderController.php (2)
update(203-239)snooze(273-289)
🪛 Biome (2.1.2)
public/assets/tasks/style.css
[error] 781-781: Unexpected shorthand property padding after padding-left
(lint/suspicious/noShorthandPropertyOverrides)
🪛 PHPMD (2.15.0)
database/seeders/TestDataSeeder.php
46-46: Avoid unused local variables such as '$project2'. (undefined)
(UnusedLocalVariable)
73-73: Avoid unused local variables such as '$task'. (undefined)
(UnusedLocalVariable)
| $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(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Same for update(): avoid $request->all().
- $request->validate([
+ $validated = $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',
]);
-
- $data = $request->all();
+ $data = $validated;
// Process tags
if ($request->filled('tags')) {
$data['tags'] = array_map('trim', explode(',', $request->tags));
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $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(); | |
| $validated = $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', | |
| ]); | |
| $data = $validated; | |
| // Process tags | |
| if ($request->filled('tags')) { | |
| $data['tags'] = array_map('trim', explode(',', $request->tags)); | |
| } |
🤖 Prompt for AI Agents
In app/Http/Controllers/NoteController.php around lines 117 to 128, you're
calling $request->all() after validation which can include unexpected or
malicious input; replace it by using the validated data only. Capture the
validated array (e.g. $data = $request->validate([ ...rules... ]);) or
explicitly pull permitted fields ($data =
$request->only(['title','content','category','tags','is_favorite','date','time']));
apply the same change in update(): avoid $request->all(), use validated() /
validate() or only() to ensure only allowed fields are used.
| $request->validate([ | ||
| 'current_password' => ['required', 'current_password'], | ||
| 'password' => ['required', 'confirmed', Password::min(8)], | ||
| ]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Enforce strong password rules to match UI guidance.
Strengthen validation to require mixed case, numbers, and symbols and check against known breaches.
- $request->validate([
- 'current_password' => ['required', 'current_password'],
- 'password' => ['required', 'confirmed', Password::min(8)],
- ]);
+ $request->validate([
+ 'current_password' => ['required', 'current_password'],
+ 'password' => [
+ 'required',
+ 'confirmed',
+ Password::min(8)->mixedCase()->numbers()->symbols()->uncompromised(),
+ ],
+ ]);Optionally, log out other sessions after a successful change:
// Auth::logoutOtherDevices($request->password);🤖 Prompt for AI Agents
In app/Http/Controllers/ProfileController.php around lines 87 to 90, the
password validation is too weak; update the validator to enforce mixed case,
numbers, and symbols and check against compromised passwords by using the
Password rule chain (e.g., min length, mixedCase(), numbers(), symbols(), and
uncompromised()); apply this Password rule to the 'password' field and keep
'current_password' validation, and after a successful password update optionally
call Auth::logoutOtherDevices($request->password) to log out other sessions.
| 'description' => 'Building a new company website', | ||
| 'status' => 'in_progress', | ||
| 'start_date' => now()->subDays(10), | ||
| 'end_date' => now()->addDays(20), | ||
| 'budget' => 5000.00, | ||
| ] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Project::status column vs accessor conflict
Project defines getStatusAttribute(), overriding the stored column. Setting 'status' here won’t be surfaced by $project->status and also conflicts with UI status vocabulary.
- Prefer deriving status at runtime (keep accessor) and remove 'status' from seed attributes; or
- Rename accessor to getComputedStatusAttribute() and use a separate stored column (e.g., raw_status) if persistence is intended; update views to use the computed status mapping.
This avoids confusion across seeds and views. Based on relevant code snippets.
Also applies to: 49-55
🤖 Prompt for AI Agents
In database/seeders/TestDataSeeder.php around lines 38-44 (and also apply same
change to lines 49-55), the seeder is setting the 'status' attribute which
conflicts with Project::getStatusAttribute() accessor; remove the 'status'
key/value from the seed arrays so the stored model data doesn't try to override
the computed accessor-derived status (leave all other keys intact), and rerun
tests to ensure views use the accessor-computed status; if instead you intended
to persist a raw status, rename the accessor to getComputedStatusAttribute() and
rename the stored column (e.g., raw_status) in both the seeder and model and
update views to use the computed accessor accordingly.
| .task-title { | ||
| font-size: 16px; | ||
| font-weight: 600; | ||
| color: var(--gray-900); | ||
| margin-bottom: 8px; | ||
| line-height: 1.4; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Scope duplicate .task-title styles to prevent global overrides
Same class is defined twice with different sizes; later rule will upsize all titles.
- .task-title {
+ .task-card .task-title {
font-size: 16px;
font-weight: 600;
color: var(--gray-900);
margin-bottom: 8px;
line-height: 1.4;
}- .task-title {
+ .task-header .task-title {
font-size: 32px;
font-weight: 800;
color: var(--gray-900);
margin-bottom: 16px;
line-height: 1.2;
}Also applies to: 553-559
🤖 Prompt for AI Agents
In public/assets/tasks/style.css around lines 199-205 (and also at 553-559) the
.task-title class is defined twice with conflicting font sizes causing the later
rule to globally override titles; scope the selector to the specific
component/container where this size should apply (for example .task-card
.task-title or .tasks-list .task-title), or consolidate/remove the duplicate
rule so only the intended scoped rule remains, ensuring other .task-title usages
keep their intended sizes.
| .task-meta { | ||
| display: flex; | ||
| justify-content: between; | ||
| align-items: center; | ||
| gap: 10px; | ||
| margin-bottom: 12px; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix invalid justify-content value
Use space-between; current value is ignored by browsers.
.task-meta {
display: flex;
- justify-content: between;
+ justify-content: space-between;
align-items: center;
gap: 10px;
margin-bottom: 12px;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| .task-meta { | |
| display: flex; | |
| justify-content: between; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 12px; | |
| } | |
| .task-meta { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| gap: 10px; | |
| margin-bottom: 12px; | |
| } |
🤖 Prompt for AI Agents
In public/assets/tasks/style.css around lines 218 to 224, the .task-meta rule
uses an invalid justify-content value "between" which is ignored by browsers;
replace it with the correct value "space-between" (i.e., set justify-content:
space-between;) so the flex items are spaced as intended and keep the existing
align-items, gap, and margin-bottom declarations.
| @foreach (['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] as $day) | ||
| <div class="checkbox-item"> | ||
| <input class="form-check-input" type="checkbox" name="days[]" value="{{ $day }}" id="{{ $day }}" | ||
| {{ in_array($day, json_decode($routine->days, true) ?? []) ? 'checked' : '' }}> | ||
| <label class="form-check-label" for="{{ $day }}">{{ ucfirst($day) }}</label> | ||
| </div> | ||
| @endforeach | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Preserve checkbox selections on validation errors.
Use old() fallbacks so previously selected days/weeks/months remain checked.
-<input class="form-check-input" type="checkbox" name="days[]" value="{{ $day }}" id="{{ $day }}"
- {{ in_array($day, json_decode($routine->days, true) ?? []) ? 'checked' : '' }}>
+<input class="form-check-input" type="checkbox" name="days[]" value="{{ $day }}" id="{{ $day }}"
+ {{ in_array($day, old('days', json_decode($routine->days, true) ?? [])) ? 'checked' : '' }}>-<input class="form-check-input" type="checkbox" name="weeks[]" value="{{ $i }}" id="week{{ $i }}"
- {{ in_array($i, json_decode($routine->weeks, true) ?? []) ? 'checked' : '' }}>
+<input class="form-check-input" type="checkbox" name="weeks[]" value="{{ $i }}" id="week{{ $i }}"
+ {{ in_array($i, old('weeks', json_decode($routine->weeks, true) ?? [])) ? 'checked' : '' }}>-<input class="form-check-input" type="checkbox" name="months[]" value="{{ $index + 1 }}" id="month{{ $index + 1 }}"
- {{ in_array($index + 1, json_decode($routine->months, true) ?? []) ? 'checked' : '' }}>
+<input class="form-check-input" type="checkbox" name="months[]" value="{{ $index + 1 }}" id="month{{ $index + 1 }}"
+ {{ in_array($index + 1, old('months', json_decode($routine->months, true) ?? [])) ? 'checked' : '' }}>Also applies to: 312-316, 322-325, 341-344
🤖 Prompt for AI Agents
In resources/views/routines/edit.blade.php around lines 292-299 (and similarly
for 312-316, 322-325, 341-344), the checkbox inputs use the model's stored JSON
directly and do not preserve user selections after validation errors; update
each checkbox checked determination to prefer old() values first (e.g.
old('days') or old('weeks')/old('months') if present) and fall back to
json_decode($routine->days/ weeks/ months, true) so previously selected items
remain checked after validation fails; ensure you handle nulls by casting to an
array when checking in_array.
| document.addEventListener('DOMContentLoaded', function() { | ||
| const frequencySelect = document.getElementById('frequency'); | ||
| const daysSection = document.getElementById('days'); | ||
| const weeksSection = document.getElementById('weeks'); | ||
| const monthsSection = document.getElementById('months'); | ||
| frequencySelect.addEventListener('change', function() { | ||
| // Hide all sections first | ||
| daysSection.style.display = 'none'; | ||
| weeksSection.style.display = 'none'; | ||
| monthsSection.style.display = 'none'; | ||
| // Show relevant section based on selection | ||
| switch(this.value) { | ||
| case 'daily': | ||
| daysSection.style.display = 'block'; | ||
| break; | ||
| case 'weekly': | ||
| weeksSection.style.display = 'block'; | ||
| break; | ||
| case 'monthly': | ||
| monthsSection.style.display = 'block'; | ||
| break; | ||
| } | ||
| }); | ||
| // Initialize on page load | ||
| frequencySelect.dispatchEvent(new Event('change')); | ||
| }); | ||
| </script> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disable inputs in hidden sections to prevent unintended submission.
Hidden groups still submit values if previously selected. Disable non‑active sections when frequency changes.
-document.addEventListener('DOMContentLoaded', function() {
- const frequencySelect = document.getElementById('frequency');
- const daysSection = document.getElementById('days');
- const weeksSection = document.getElementById('weeks');
- const monthsSection = document.getElementById('months');
-
- frequencySelect.addEventListener('change', function() {
- // Hide all sections first
- daysSection.style.display = 'none';
- weeksSection.style.display = 'none';
- monthsSection.style.display = 'none';
-
- // Show relevant section based on selection
- switch(this.value) {
- case 'daily':
- daysSection.style.display = 'block';
- break;
- case 'weekly':
- weeksSection.style.display = 'block';
- break;
- case 'monthly':
- monthsSection.style.display = 'block';
- break;
- }
- });
-
- // Initialize on page load
- frequencySelect.dispatchEvent(new Event('change'));
-});
+document.addEventListener('DOMContentLoaded', function () {
+ const frequencySelect = document.getElementById('frequency');
+ const sections = {
+ daily: document.getElementById('days'),
+ weekly: document.getElementById('weeks'),
+ monthly: document.getElementById('months'),
+ };
+
+ function setSectionVisibility(selected) {
+ Object.entries(sections).forEach(([key, el]) => {
+ const show = key === selected;
+ el.style.display = show ? 'block' : 'none';
+ el.querySelectorAll('input, select, textarea').forEach(inp => {
+ inp.disabled = !show;
+ });
+ el.setAttribute('aria-hidden', show ? 'false' : 'true');
+ });
+ }
+
+ frequencySelect.addEventListener('change', function () {
+ setSectionVisibility(this.value);
+ });
+
+ setSectionVisibility(frequencySelect.value);
+});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| document.addEventListener('DOMContentLoaded', function() { | |
| const frequencySelect = document.getElementById('frequency'); | |
| const daysSection = document.getElementById('days'); | |
| const weeksSection = document.getElementById('weeks'); | |
| const monthsSection = document.getElementById('months'); | |
| frequencySelect.addEventListener('change', function() { | |
| // Hide all sections first | |
| daysSection.style.display = 'none'; | |
| weeksSection.style.display = 'none'; | |
| monthsSection.style.display = 'none'; | |
| // Show relevant section based on selection | |
| switch(this.value) { | |
| case 'daily': | |
| daysSection.style.display = 'block'; | |
| break; | |
| case 'weekly': | |
| weeksSection.style.display = 'block'; | |
| break; | |
| case 'monthly': | |
| monthsSection.style.display = 'block'; | |
| break; | |
| } | |
| }); | |
| // Initialize on page load | |
| frequencySelect.dispatchEvent(new Event('change')); | |
| }); | |
| </script> | |
| document.addEventListener('DOMContentLoaded', function () { | |
| const frequencySelect = document.getElementById('frequency'); | |
| const sections = { | |
| daily: document.getElementById('days'), | |
| weekly: document.getElementById('weeks'), | |
| monthly: document.getElementById('months'), | |
| }; | |
| function setSectionVisibility(selected) { | |
| Object.entries(sections).forEach(([key, el]) => { | |
| const show = key === selected; | |
| el.style.display = show ? 'block' : 'none'; | |
| el.querySelectorAll('input, select, textarea').forEach(inp => { | |
| inp.disabled = !show; | |
| }); | |
| el.setAttribute('aria-hidden', show ? 'false' : 'true'); | |
| }); | |
| } | |
| frequencySelect.addEventListener('change', function () { | |
| setSectionVisibility(this.value); | |
| }); | |
| setSectionVisibility(frequencySelect.value); | |
| }); | |
| </script> |
🤖 Prompt for AI Agents
In resources/views/routines/edit.blade.php around lines 412 to 441, the JS only
hides non-active sections but does not disable their form controls, so values
from previously-selected hidden sections can still be submitted; update the
frequency change handler to, after hiding all sections, query each section's
form controls (input, select, textarea, button) and set disabled = true for
controls inside hidden sections and disabled = false for controls inside the
shown section, and run this same enable/disable logic during the initial
dispatchEvent so controls are in the correct state on page load.
| display: flex; | ||
| align-items: center; | ||
| justify-content: between; | ||
| gap: 0.75rem; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix invalid CSS value.
justify-content: between is invalid. Use space-between.
-justify-content: between;
+justify-content: space-between;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| display: flex; | |
| align-items: center; | |
| justify-content: between; | |
| gap: 0.75rem; | |
| } | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: 0.75rem; | |
| } |
🤖 Prompt for AI Agents
In resources/views/routines/index.blade.php around lines 76 to 80 the CSS rule
uses an invalid value "justify-content: between"; replace it with the correct
value "justify-content: space-between" so the flex container distributes items
as intended and remove any trailing syntax errors.
| <span>{{ implode(', ', array_map(function ($month) { | ||
| return DateTime::createFromFormat('!m', $month)->format('F'); | ||
| }, json_decode($routine->months, true) ?? [])) }}</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Harden month-name mapping (avoid errors on invalid values).
DateTime::createFromFormat can return false; guard and map safely.
-<span>{{ implode(', ', array_map(function ($month) {
- return DateTime::createFromFormat('!m', $month)->format('F');
-}, json_decode($routine->months, true) ?? [])) }}</span>
+<span>{{ implode(', ',
+ array_values(array_filter(array_map(function ($month) {
+ $dt = DateTime::createFromFormat('!m', (int) $month);
+ return $dt ? $dt->format('F') : null;
+ }, json_decode($routine->months, true) ?? [])))
+) }}</span>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <span>{{ implode(', ', array_map(function ($month) { | |
| return DateTime::createFromFormat('!m', $month)->format('F'); | |
| }, json_decode($routine->months, true) ?? [])) }}</span> | |
| <span>{{ implode(', ', | |
| array_values(array_filter(array_map(function ($month) { | |
| $dt = DateTime::createFromFormat('!m', (int) $month); | |
| return $dt ? $dt->format('F') : null; | |
| }, json_decode($routine->months, true) ?? []))) | |
| ) }}</span> |
🤖 Prompt for AI Agents
resources/views/routines/index.blade.php around lines 416 to 418: the inline
mapping uses DateTime::createFromFormat which can return false for invalid month
values, causing warnings or errors; change the logic to first json_decode safely
into an array, then iterate/filter the values so that for each month you call
createFromFormat and only if it returns a DateTime object you call ->format('F')
and include it in the result; finally implode the filtered, formatted month
names (this ensures invalid or non-numeric values are skipped and avoids calling
methods on false).
| <div class="routine-meta"> | ||
| <div class="meta-item"> | ||
| <i class="fas fa-calendar-alt"></i> | ||
| <span>{{ implode(', ', array_map(function($month) { | ||
| return DateTime::createFromFormat('!m', $month)->format('M'); | ||
| }, array_slice(json_decode($routine->months, true) ?? [], 0, 3))) }}{{ count(json_decode($routine->months, true) ?? []) > 3 ? '...' : '' }}</span> | ||
| </div> | ||
| <div class="meta-item"> | ||
| <i class="fas fa-clock"></i> | ||
| <span>{{ $routine->start_time }} - {{ $routine->end_time }}</span> | ||
| </div> | ||
| </div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guard against invalid month values in months JSON
DateTime::createFromFormat can return false for invalid values; calling ->format then errors.
- <span>{{ implode(', ', array_map(function($month) {
- return DateTime::createFromFormat('!m', $month)->format('M');
- }, array_slice(json_decode($routine->months, true) ?? [], 0, 3))) }}{{ count(json_decode($routine->months, true) ?? []) > 3 ? '...' : '' }}</span>
+ @php
+ $months = array_slice((array) json_decode($routine->months, true) ?? [], 0, 3);
+ $labels = [];
+ foreach ($months as $m) {
+ $m = (int) $m;
+ if ($m >= 1 && $m <= 12) { $labels[] = \Carbon\Carbon::createFromDate(null, $m, 1)->format('M'); }
+ }
+ @endphp
+ <span>{{ implode(', ', $labels) }}{{ ((is_array(json_decode($routine->months, true)) ? count(json_decode($routine->months, true)) : 0) > 3) ? '...' : '' }}</span>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div class="routine-meta"> | |
| <div class="meta-item"> | |
| <i class="fas fa-calendar-alt"></i> | |
| <span>{{ implode(', ', array_map(function($month) { | |
| return DateTime::createFromFormat('!m', $month)->format('M'); | |
| }, array_slice(json_decode($routine->months, true) ?? [], 0, 3))) }}{{ count(json_decode($routine->months, true) ?? []) > 3 ? '...' : '' }}</span> | |
| </div> | |
| <div class="meta-item"> | |
| <i class="fas fa-clock"></i> | |
| <span>{{ $routine->start_time }} - {{ $routine->end_time }}</span> | |
| </div> | |
| </div> | |
| <div class="routine-meta"> | |
| <div class="meta-item"> | |
| <i class="fas fa-calendar-alt"></i> | |
| @php | |
| // Decode and slice the first 3 months, guard against non-array | |
| $months = array_slice((array) json_decode($routine->months, true) ?? [], 0, 3); | |
| $labels = []; | |
| foreach ($months as $m) { | |
| $m = (int) $m; | |
| // Only accept valid month numbers | |
| if ($m >= 1 && $m <= 12) { | |
| $labels[] = \Carbon\Carbon::createFromDate(null, $m, 1)->format('M'); | |
| } | |
| } | |
| @endphp | |
| <span> | |
| {{ implode(', ', $labels) }} | |
| {{ (is_array(json_decode($routine->months, true)) && count(json_decode($routine->months, true)) > 3) ? '...' : '' }} | |
| </span> | |
| </div> | |
| <div class="meta-item"> | |
| <i class="fas fa-clock"></i> | |
| <span>{{ $routine->start_time }} - {{ $routine->end_time }}</span> | |
| </div> | |
| </div> |
🤖 Prompt for AI Agents
In resources/views/routines/monthly.blade.php around lines 218 to 229, the code
calls DateTime::createFromFormat(...)->format(...) without checking for false
which will error for invalid month values; change the logic to decode the months
JSON into an array, normalize and filter values to valid month integers (1–12),
map those validated values to month names (either via DateTime only after
confirming createFromFormat returned a DateTime or by using a simple month-name
lookup), take the first three, implode them, and then append '...' if the total
count of valid months exceeds three; ensure you handle null/invalid JSON by
treating it as an empty array.
Summary by CodeRabbit
New Features
UI/Style
Chores