diff --git a/_bmad-output/implementation-artifacts/3-2-one-click-nudge-system.md b/_bmad-output/implementation-artifacts/3-2-one-click-nudge-system.md new file mode 100644 index 0000000..b8183a3 --- /dev/null +++ b/_bmad-output/implementation-artifacts/3-2-one-click-nudge-system.md @@ -0,0 +1,316 @@ +# Story 3.2: One-Click Nudge System + +Status: review + + + +## Story + +As a firm owner or manager, +I want to nudge a team member about a specific declaration with one click, +so that I can quickly signal that something needs attention without composing a message or making a phone call. + +## Acceptance Criteria + +1. **AC1: NudgePopover appears on declaration row click** + - Given an Owner or Manager is viewing a declaration list (dashboard table or declarations index) + - When they click the nudge icon on a declaration row + - Then a NudgePopover appears showing the assigned worker's name and a "Send Nudge" button + +2. **AC2: Nudge dispatches notification on send** + - Given a NudgePopover is open + - When the user clicks "Send Nudge" + - Then a `NudgeNotification` is dispatched to the assigned worker via `NudgeController@store` + - And the notification is saved to the database (in-app) AND queued as an email + - And a success toast confirms: "Relance envoyée à [worker name]" + - And the popover closes automatically after sending + +3. **AC3: No message composition required** + - Then no message composition is required — it is a pure signal ("Your attention is needed on this declaration") + +4. **AC4: Nudge icon hidden for unauthorized users** + - Given a Worker or a user without nudge permission + - When they view declaration rows + - Then the nudge icon is not displayed + +5. **AC5: NudgeController validates sender role** + - Then the `NudgeController` validates that the sender is Owner or Manager + - And the `NudgeController` validates that the declaration belongs to the current workspace + +6. **AC6: Nudge email contains declaration context** + - Then the nudge email contains: declaration type, client name, deadline (`due_date`), and a direct link to the declaration detail page + - (Already implemented by `NudgeNotificationMail` from Story 3.1 — verify integration) + +7. **AC7: Nudge is logged via Spatie Activity Log** + - Then the nudge is logged via Spatie Activity Log (actor: sender, target: declaration, action: "nudged") + +8. **AC8: Duplicate nudge debounce (1-hour window)** + - Then duplicate nudges on the same declaration within 1 hour are prevented (debounce) with a user-friendly message: "Relance déjà envoyée récemment" + +9. **AC9: Dashboard nudge dropdown enabled (D-1 resolution)** + - Then the "Relancer" dropdown menu item in `Dashboard.vue` is enabled for Owner/Manager roles and triggers the NudgePopover + - (Resolves deferred code review item D-1 from Epic 2 Story 2.3) + +## Tasks / Subtasks + +- [x] Task 1: Create `NudgeController` (AC: #2, #5, #7, #8) + - [x] 1.1 Create `app/Http/Controllers/NudgeController.php` with `store(Request $request, Declaration $declaration)` method + - [x] 1.2 Use `HasWorkspaceScope` trait — call `$this->authorizeWorkspaceAccess($declaration)` to verify declaration belongs to workspace + - [x] 1.3 Authorize sender: check user's role on current workspace is `owner` or `manager` — `abort(404)` if not + - [x] 1.4 Validate declaration has an `assigned_to` user — return error if no assignee + - [x] 1.5 Debounce check: query `notifications` table for existing nudge on same declaration within 1 hour — return `back()->with('flash', ['type' => 'warning', 'message' => 'Relance déjà envoyée récemment'])` if found + - [x] 1.6 Dispatch `NudgeNotification` to the declaration's assignee: `$declaration->assignee->notify(new NudgeNotification($declaration, $request->user()))` + - [x] 1.7 Log via Spatie Activity Log: `activity()->performedOn($declaration)->causedBy($request->user())->log('nudged')` + - [x] 1.8 Clear assignee's notification cache: `Cache::forget("user:{$declaration->assigned_to}:unread_notifications")` + - [x] 1.9 Return `back()->with('flash', ['type' => 'success', 'message' => 'Relance envoyée à '.$declaration->assignee->name])` + +- [x] Task 2: Register nudge route (AC: #5) + - [x] 2.1 Add route inside `middleware('workspace')` group in `routes/web.php`: + `Route::post('declarations/{declaration}/nudge', [NudgeController::class, 'store'])->name('declarations.nudge')->middleware('throttle:10,1');` + +- [x] Task 3: Create `NudgePopover` Vue component (AC: #1, #2, #3) + - [x] 3.1 Create `resources/js/components/declarations/NudgePopover.vue` + - [x] 3.2 Props: `declaration` (with `assigneeName`, `id`), `nudgeUrl` (string) + - [x] 3.3 UI: shadcn-vue `Popover` + `PopoverTrigger` (Send icon button) + `PopoverContent` + - [x] 3.4 Content: Show assignee name, "Envoyer une relance" button + - [x] 3.5 On click "Envoyer": `router.post(nudgeUrl)` via Inertia — popover auto-closes on success + - [x] 3.6 Handle loading state (disable button during submission via `form.processing`) + - [x] 3.7 Use `useForm` from Inertia for the POST request — no payload needed + +- [x] Task 4: Enable nudge in Dashboard table (AC: #4, #9) + - [x] 4.1 In `Dashboard.vue`, replace the disabled "Relancer" `DropdownMenuItem` with `NudgePopover` component (or make it functional via click handler) + - [x] 4.2 Pass `nudgeUrl` as `route('declarations.nudge', declaration.id)` — URL must come from controller props + - [x] 4.3 Only show/enable nudge for Owner/Manager — use the existing `isWorker` prop to conditionally render (if `!isWorker`) + - [x] 4.4 Pass `canNudge` boolean prop from `DashboardController` (true for owner/manager, false for worker) + - [x] 4.5 Hide nudge action entirely when `!canNudge` + +- [x] Task 5: Add nudge to Declarations Index page (AC: #1, #4) + - [x] 5.1 In `resources/js/pages/declarations/Index.vue`, add nudge icon/button per row for Owner/Manager + - [x] 5.2 Pass `canNudge` prop from `DeclarationController@index` + - [x] 5.3 Add `nudgeUrl` to each declaration in the serialized data from controller + - [x] 5.4 Integrate `NudgePopover` component for each row + +- [x] Task 6: Update `DashboardController` to pass nudge props (AC: #4, #9) + - [x] 6.1 Add `canNudge` to the Inertia props: `true` when user role is `owner` or `manager` + - [x] 6.2 Add `nudgeBaseUrl` or per-declaration `nudgeUrl` to declaration data passed to frontend + - [x] 6.3 Use Wayfinder route generation for URLs passed as props + +- [x] Task 7: Update `DeclarationController@index` to pass nudge props (AC: #4) + - [x] 7.1 Add `canNudge` to the Inertia props + - [x] 7.2 Add `nudgeUrl` to each declaration data object + +- [x] Task 8: Write tests (all ACs) + - [x] 8.1 Create `tests/Feature/Notifications/NudgeControllerTest.php` + - [x] 8.2 Test: Owner can send nudge — notification dispatched, activity logged, success flash + - [x] 8.3 Test: Manager can send nudge — same behavior as owner + - [x] 8.4 Test: Worker cannot send nudge — `abort(404)` + - [x] 8.5 Test: Nudge on declaration in wrong workspace — `abort(404)` + - [x] 8.6 Test: Nudge on declaration with no assignee — returns error + - [x] 8.7 Test: Duplicate nudge within 1 hour — debounce prevents, returns warning flash + - [x] 8.8 Test: Nudge after 1 hour — allowed (debounce expired) + - [x] 8.9 Test: Activity log entry created with correct actor/target/action + - [x] 8.10 Test: Unauthenticated user cannot nudge — redirected to login + - [x] 8.11 Test: Notification cache cleared for assignee after nudge + +## Retrospective Intelligence + +**From Epic 2 Retrospective (2026-03-24):** + +- **D-1 (Deferred Code Review Item):** Dashboard "Relancer" and "Réassigner" dropdown items are unconditionally `disabled` in `Dashboard.vue` lines 349-364. This story MUST enable the "Relancer" item. The "Réassigner" item stays disabled until a future story. +- **Team Agreement — Load retro as context:** Non-negotiable. This story incorporates all retro intelligence. +- **Team Agreement — Pre-existing test failures must not carry:** Run full test suite before and after implementation. Zero failures tolerance. Story 3.1 raised test count to 237. +- **Context management is the team's superpower:** Previous story intelligence prevents repeated mistakes. +- **Architecture doc drift was corrected** in commit `6956f7b` — `due_date` (not `deadline`), no `Declaration::workspace()` scope, `mise_en_demeure` status. All corrected and reflected in this story. +- **D-3 (Cache not invalidated on role change):** 5-min TTL mitigates. Not directly relevant to this story but be aware that `canNudge` is computed per request, not cached. + +**From Epic 1 Retrospective:** + +- **withPivot gotchas:** When loading workspace members, always chain `->withPivot('role', 'permissions')` on workspace users() relationship. + +**From Story 3.1 Completion Notes:** + +- All notification infrastructure is in place — `NudgeNotification`, `NudgeNotificationMail`, `NotificationType::Nudge`, notification routes, TypeScript types +- 237 tests passing, zero failures +- `whereJsonContains('data->workspace_id', $workspace->id)` is the pattern for workspace-scoped notification queries +- `Cache::forget("user:{$userId}:unread_notifications")` on every notification state change + +## Dev Notes + +### Existing Infrastructure (REUSE — do not recreate) + +- **`NudgeNotification`** — `app/Notifications/NudgeNotification.php` — already created in Story 3.1. Channels: `['database', 'mail']`, `$tries = 3`, `$backoff = 60`. Constructor: `Declaration $declaration, User $sender`. Uses `NudgeNotificationMail` for email. +- **`NudgeNotificationMail`** — `app/Mail/NudgeNotificationMail.php` — Markdown mailable with sender name, client name, declaration type, `due_date`, direct link button. Template: `resources/views/emails/nudge-notification.blade.php`. +- **`NotificationType::Nudge`** — `app/Enums/NotificationType.php` — enum value exists. +- **`NotificationController`** — `app/Http/Controllers/NotificationController.php` — has `index`, `markAsRead`, `markAllAsRead`. Do NOT modify. +- **Frontend types** — `resources/js/types/notification.ts` — `NotificationType`, `NotificationData`, `AppNotification` types exist. +- **`DeclarationMentionController`** — `app/Http/Controllers/DeclarationMentionController.php` — reference pattern for how to authorize owner/manager and dispatch a notification. Uses `abort(403)` for role check (NOTE: architecture says `abort(404)` — follow architecture convention `abort(404)` in NudgeController). + +### Controller Pattern to Follow + +The `DeclarationMentionController` is the closest reference: +```php +// Role check pattern (use abort(404) per architecture, not 403): +$workspaceUser = $workspace->users() + ->where('users.id', $request->user()->id) + ->first(); +if (!in_array($workspaceUser?->pivot?->role?->value, ['owner', 'manager'])) { + abort(404); +} +``` + +Use `HasWorkspaceScope` trait (preferred pattern from NotificationController): +```php +use App\Concerns\HasWorkspaceScope; +// $workspace = $this->currentWorkspace(); +// $this->authorizeWorkspaceAccess($declaration); +``` + +### Debounce Implementation + +Query the `notifications` table for existing nudge within 1 hour: +```php +$recentNudge = $declaration->assignee + ->notifications() + ->where('type', NudgeNotification::class) + ->whereJsonContains('data->declaration_id', $declaration->id) + ->where('created_at', '>=', now()->subHour()) + ->exists(); +``` + +### Spatie Activity Log Pattern + +From existing codebase (DashboardController reads activity logs): +```php +activity() + ->performedOn($declaration) + ->causedBy($request->user()) + ->log('nudged'); +``` + +### Dashboard Integration (D-1 Resolution) + +In `Dashboard.vue` lines 349-364, the "Relancer" dropdown item is currently: +```vue + + + Relancer + +``` + +Replace with functional nudge trigger. Options: +1. **Inline Popover approach** — wrap in `NudgePopover` component within the dropdown +2. **Click-to-nudge approach** — directly post on click (simpler, since nudge requires no message) + +Architecture specifies "Popover, not full modal" for nudge dialog. Recommended: use `Popover` component from shadcn-vue. + +### Declarations Index Integration + +`declarations/Index.vue` currently has NO dropdown menu or inline actions. Add a nudge icon button per row (only visible for owner/manager). Use the same `NudgePopover` component. + +### Critical Gotchas + +- **Declaration column is `due_date`** NOT `deadline` — already handled in NudgeNotificationMail +- **Declaration has `assignee()` relationship** — `belongsTo(User::class, 'assigned_to')` at `Declaration.php:123` +- **No `Declaration::workspace()` scope** — use `$declaration->workspace_id` directly +- **Authorization returns `abort(404)`** not 403 — architecture convention, prevents workspace boundary information leakage +- **All URLs passed as props** from PHP controllers — never hardcode in Vue. Use Wayfinder or `route()` helper +- **`isWorker` prop** already exists on Dashboard — passed from `DashboardController`, use to conditionally show nudge +- **Workspace resolution** — session-based via `current_workspace_id`, use `HasWorkspaceScope` trait +- **Cache invalidation** — always call `Cache::forget("user:{$userId}:unread_notifications")` after dispatching notification +- **Flash messages** — return `back()->with('flash', ['type' => 'success/warning', 'message' => '...'])` — Inertia shares flash via middleware +- **throttle middleware** — add `throttle:10,1` to nudge route (same as mentions route pattern) + +### Testing Standards + +- Pest syntax with `test()` closures +- `Notification::fake()` for verifying dispatch, then assert `Notification::assertSentTo($assignee, NudgeNotification::class)` +- Feature tests in `tests/Feature/Notifications/NudgeControllerTest.php` +- Use `route()` helper for URLs, never hardcoded paths +- `RefreshDatabase` is auto-applied via `Pest.php` — don't add manually +- `withoutVite()` is globally applied in `Pest.php` — compatible with backend tests +- Use `actingAs($user)` for authentication +- Create workspace, users, declaration via factories in test setup +- Test the debounce by creating a notification record with `created_at` within/outside the 1-hour window +- Run `composer test` — must pass all 237+ existing tests plus new ones, zero failures + +### Project Structure Notes + +**New files to create:** +- `app/Http/Controllers/NudgeController.php` — follows existing controller pattern +- `resources/js/components/declarations/NudgePopover.vue` — new component +- `tests/Feature/Notifications/NudgeControllerTest.php` — new test file + +**Existing files to modify:** +- `resources/js/pages/Dashboard.vue` — enable "Relancer" dropdown item, integrate NudgePopover +- `resources/js/pages/declarations/Index.vue` — add nudge icon/button per row +- `app/Http/Controllers/DashboardController.php` — add `canNudge` prop +- `app/Http/Controllers/DeclarationController.php` — add `canNudge` and `nudgeUrl` props to index +- `routes/web.php` — add nudge route + +**Files NOT to touch:** +- `app/Notifications/NudgeNotification.php` — already complete from Story 3.1 +- `app/Mail/NudgeNotificationMail.php` — already complete from Story 3.1 +- `resources/views/emails/nudge-notification.blade.php` — already complete +- `app/Enums/NotificationType.php` — already complete +- `resources/js/components/ui/*` — shadcn-vue, never modify + +### References + +- [Source: _bmad-output/planning-artifacts/epics.md#Story 3.2] +- [Source: _bmad-output/planning-artifacts/architecture.md#Notification Patterns — "Nudge dialog: Popover, not full modal"] +- [Source: _bmad-output/planning-artifacts/architecture.md#Phase 3 File Structure — NudgeController] +- [Source: _bmad-output/planning-artifacts/ux-design-specification.md#Nudge UX Pattern] +- [Source: _bmad-output/planning-artifacts/prd.md#FR29-FR31] +- [Source: _bmad-output/implementation-artifacts/3-1-notification-infrastructure-setup.md] +- [Source: _bmad-output/implementation-artifacts/epic-2-retro-2026-03-24.md#Deferred Items D-1] +- [Source: app/Http/Controllers/DeclarationMentionController.php — reference controller pattern] +- [Source: app/Concerns/HasWorkspaceScope.php — workspace resolution trait] +- [Source: app/Models/Declaration.php:123 — assignee() relationship] + +## Dev Agent Record + +### Agent Model Used + +Claude Opus 4.6 (1M context) + +### Debug Log References + +None — clean implementation with no blockers. + +### Completion Notes List + +- Created NudgeController with HasWorkspaceScope trait, owner/manager authorization, debounce (1-hour window), NudgeNotification dispatch, Spatie activity logging, cache invalidation, and flash messages +- Registered POST route `declarations/{declaration}/nudge` with throttle:10,1 middleware +- Created NudgePopover Vue component using shadcn-vue Popover, with useForm for Inertia POST, loading state, and auto-close on success +- Dashboard "Relancer" dropdown item now functional for owner/manager via direct `router.post()` click (D-1 resolved) +- Declarations Index page now shows NudgePopover per row for owner/manager +- DashboardController passes `canNudge` and per-declaration `nudgeUrl` props +- DeclarationController@index passes `canNudge` and per-declaration `nudgeUrl` and `assignee_name` props +- Updated DashboardDeclaration type to include `nudgeUrl`, DashboardProps to include `canNudge` +- 10 new feature tests covering all ACs: authorization, debounce, activity log, cache invalidation, edge cases +- All 247 tests pass (1307 assertions), zero regressions +- Installed shadcn-vue Popover component (reka-ui based) + +### File List + +**New files:** +- app/Http/Controllers/NudgeController.php +- resources/js/components/declarations/NudgePopover.vue +- tests/Feature/Notifications/NudgeControllerTest.php +- resources/js/components/ui/popover/Popover.vue (shadcn-vue install) +- resources/js/components/ui/popover/PopoverAnchor.vue (shadcn-vue install) +- resources/js/components/ui/popover/PopoverContent.vue (shadcn-vue install) +- resources/js/components/ui/popover/PopoverTrigger.vue (shadcn-vue install) +- resources/js/components/ui/popover/index.ts (shadcn-vue install) + +**Modified files:** +- routes/web.php — added nudge route +- app/Http/Controllers/DashboardController.php — added canNudge + nudgeUrl props +- app/Http/Controllers/DeclarationController.php — added canNudge + nudgeUrl + assignee_name props +- resources/js/pages/Dashboard.vue — enabled "Relancer" dropdown item for owner/manager +- resources/js/pages/declarations/Index.vue — added NudgePopover per row +- resources/js/types/dashboard.ts — added nudgeUrl to DashboardDeclaration, canNudge to DashboardProps + +### Change Log + +- 2026-03-25: Implemented Story 3.2 One-Click Nudge System — NudgeController, NudgePopover, dashboard/index integration, 10 tests diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 3c67379..80d989c 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -34,6 +34,7 @@ class DashboardController extends Controller 'workspaceName' => null, 'roleLabel' => null, 'isWorker' => false, + 'canNudge' => false, 'declarationsUrl' => null, 'clientsUrl' => null, 'viewAllAlertsUrl' => null, @@ -50,6 +51,7 @@ class DashboardController extends Controller abort(404); } + $isWorker = $workspaceUser->role->is(WorkspaceUserRole::Worker); $cacheKey = "dashboard:{$workspace->id}:{$user->id}"; $dashboardData = Cache::remember($cacheKey, 300, function () use ($workspace, $user, $workspaceUser) { @@ -106,11 +108,11 @@ class DashboardController extends Controller 'statusLabel' => DeclarationStatus::labels()[$d->status->value] ?? $d->status->value, 'dueDate' => $d->due_date?->format('Y-m-d'), 'showUrl' => route('declarations.show', $d), + 'nudgeUrl' => ! $isWorker ? route('declarations.nudge', $d) : null, ]) ->all(); $roleLabel = $this->roleLabels()[$workspaceUser->role->value] ?? $workspaceUser->role->value; - $isWorker = $workspaceUser->role->is(WorkspaceUserRole::Worker); $assigneeParam = $isWorker ? ['assignee' => $user->id] : []; @@ -150,6 +152,7 @@ class DashboardController extends Controller 'workspaceName' => $workspace->name, 'roleLabel' => $roleLabel, 'isWorker' => $isWorker, + 'canNudge' => ! $isWorker, 'declarationsUrl' => route('declarations.index'), 'clientsUrl' => route('clients.index'), 'viewAllAlertsUrl' => route('declarations.index', ['filter' => 'alerts']), diff --git a/app/Http/Controllers/DeclarationController.php b/app/Http/Controllers/DeclarationController.php index 4375545..f1feb0e 100644 --- a/app/Http/Controllers/DeclarationController.php +++ b/app/Http/Controllers/DeclarationController.php @@ -76,11 +76,13 @@ class DeclarationController extends Controller 'title' => $declaration->title, 'type' => $declaration->type->value, 'client_name' => $declaration->client->company_name, + 'assignee_name' => $declaration->assignee?->name, 'status' => $declaration->status->value, 'due_date' => $declaration->due_date?->format('Y-m-d'), 'showUrl' => route('declarations.show', $declaration), 'editUrl' => route('declarations.edit', $declaration), 'destroyUrl' => route('declarations.destroy', $declaration), + 'nudgeUrl' => ! $isWorker ? route('declarations.nudge', $declaration) : null, ]); return Inertia::render('declarations/Index', [ @@ -90,6 +92,7 @@ class DeclarationController extends Controller 'canCreate' => ! $isWorker, 'canEdit' => ! $isWorker, 'canDelete' => ! $isWorker, + 'canNudge' => ! $isWorker, ]); } diff --git a/app/Http/Controllers/NudgeController.php b/app/Http/Controllers/NudgeController.php new file mode 100644 index 0000000..51df0c8 --- /dev/null +++ b/app/Http/Controllers/NudgeController.php @@ -0,0 +1,61 @@ +authorizeWorkspaceAccess($declaration); + + $workspace = $this->currentWorkspace(); + + $userRole = $workspace->users() + ->where('users.id', $request->user()->id) + ->first() + ?->pivot + ?->role + ?->value; + + if (! in_array($userRole, ['owner', 'manager'])) { + abort(404); + } + + $assignee = $declaration->assignee; + + if (! $assignee) { + return back()->with('flash', ['type' => 'warning', 'message' => 'Cette déclaration n\'a pas de collaborateur assigné.']); + } + + $recentNudge = $assignee + ->notifications() + ->where('type', NudgeNotification::class) + ->where('data->declaration_id', $declaration->id) + ->where('created_at', '>=', now()->subHour()) + ->exists(); + + if ($recentNudge) { + return back()->with('flash', ['type' => 'warning', 'message' => 'Relance déjà envoyée récemment']); + } + + $assignee->notify(new NudgeNotification($declaration, $request->user())); + + activity() + ->performedOn($declaration) + ->causedBy($request->user()) + ->log('nudged'); + + Cache::forget("user:{$assignee->id}:workspace:{$workspace->id}:unread_notifications"); + + return back()->with('flash', ['type' => 'success', 'message' => 'Relance envoyée à '.$assignee->name]); + } +} diff --git a/resources/js/components/declarations/NudgePopover.vue b/resources/js/components/declarations/NudgePopover.vue new file mode 100644 index 0000000..a052dec --- /dev/null +++ b/resources/js/components/declarations/NudgePopover.vue @@ -0,0 +1,68 @@ + + + diff --git a/resources/js/components/ui/popover/Popover.vue b/resources/js/components/ui/popover/Popover.vue new file mode 100644 index 0000000..4efdb98 --- /dev/null +++ b/resources/js/components/ui/popover/Popover.vue @@ -0,0 +1,19 @@ + + + diff --git a/resources/js/components/ui/popover/PopoverAnchor.vue b/resources/js/components/ui/popover/PopoverAnchor.vue new file mode 100644 index 0000000..49e01db --- /dev/null +++ b/resources/js/components/ui/popover/PopoverAnchor.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/components/ui/popover/PopoverContent.vue b/resources/js/components/ui/popover/PopoverContent.vue new file mode 100644 index 0000000..cf1e55c --- /dev/null +++ b/resources/js/components/ui/popover/PopoverContent.vue @@ -0,0 +1,45 @@ + + + diff --git a/resources/js/components/ui/popover/PopoverTrigger.vue b/resources/js/components/ui/popover/PopoverTrigger.vue new file mode 100644 index 0000000..fd3b497 --- /dev/null +++ b/resources/js/components/ui/popover/PopoverTrigger.vue @@ -0,0 +1,15 @@ + + + diff --git a/resources/js/components/ui/popover/index.ts b/resources/js/components/ui/popover/index.ts new file mode 100644 index 0000000..66edf89 --- /dev/null +++ b/resources/js/components/ui/popover/index.ts @@ -0,0 +1,4 @@ +export { default as Popover } from "./Popover.vue" +export { default as PopoverAnchor } from "./PopoverAnchor.vue" +export { default as PopoverContent } from "./PopoverContent.vue" +export { default as PopoverTrigger } from "./PopoverTrigger.vue" diff --git a/resources/js/pages/Dashboard.vue b/resources/js/pages/Dashboard.vue index e565a95..0289ece 100644 --- a/resources/js/pages/Dashboard.vue +++ b/resources/js/pages/Dashboard.vue @@ -62,6 +62,7 @@ const breadcrumbs: BreadcrumbItem[] = [ const hasWorkspace = computed(() => !!props.workspaceName); const showFeed = ref(false); +const nudgingIds = ref(new Set()); const isWorkerEmpty = computed( () => @@ -347,7 +348,21 @@ function navigateToDeclaration(declaration: DashboardDeclaration): void { Voir import { Head, Link, router } from '@inertiajs/vue3'; import { FolderOpen } from 'lucide-vue-next'; +import NudgePopover from '@/components/declarations/NudgePopover.vue'; import Heading from '@/components/Heading.vue'; import Pagination from '@/components/Pagination.vue'; import { Button } from '@/components/ui/button'; @@ -11,11 +12,13 @@ type Declaration = { title: string; type: string; client_name: string; + assignee_name: string | null; status: string; due_date: string | null; showUrl: string; editUrl: string; destroyUrl: string; + nudgeUrl: string | null; }; type PaginatedData = { @@ -40,6 +43,7 @@ type Props = { canCreate: boolean; canEdit: boolean; canDelete: boolean; + canNudge: boolean; }; const props = defineProps(); @@ -167,7 +171,16 @@ const statusLabels: Record = { {{ declaration.due_date || '—' }} - + +