Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccd199a695 | ||
|
|
f7b50e8715 | ||
|
|
5147c8ed8d | ||
|
|
3914fd5432 | ||
|
|
4d6da2c5f5 | ||
|
|
313e2d2396 | ||
|
|
810d1850ae | ||
|
|
cd7fae3841 | ||
|
|
32e3496248 | ||
|
|
0e78be0eae | ||
|
|
17caabbb3e | ||
|
|
3f4945c20d | ||
|
|
65bf75ed77 | ||
|
|
802294d6a2 | ||
|
|
5e52e4fe78 | ||
|
|
f4a8bdd134 | ||
|
|
850ae337ca | ||
|
|
4adda6e53b | ||
|
|
de6f4e5b2f | ||
|
|
2e2ddd5b74 | ||
|
|
c58196293f | ||
|
|
8657d75682 | ||
|
|
71d9d1b63e | ||
|
|
68995a9f4c | ||
|
|
3e998a483e |
@@ -47,6 +47,7 @@ class BuildProjectBacklogPayload
|
|||||||
'tickets' => TicketResource::collection(
|
'tickets' => TicketResource::collection(
|
||||||
Ticket::query()
|
Ticket::query()
|
||||||
->with([
|
->with([
|
||||||
|
'assignee',
|
||||||
'ticketPriority',
|
'ticketPriority',
|
||||||
'ticketStatus.allowedTargetStatuses.boardColumn',
|
'ticketStatus.allowedTargetStatuses.boardColumn',
|
||||||
'ticketStatus.boardColumn',
|
'ticketStatus.boardColumn',
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class BuildProjectBoardPayload
|
|||||||
'ticketStatuses',
|
'ticketStatuses',
|
||||||
'tickets' => fn ($query) => $query
|
'tickets' => fn ($query) => $query
|
||||||
->with([
|
->with([
|
||||||
|
'assignee',
|
||||||
'ticketPriority',
|
'ticketPriority',
|
||||||
'ticketStatus.allowedTargetStatuses.boardColumn',
|
'ticketStatus.allowedTargetStatuses.boardColumn',
|
||||||
'ticketStatus.boardColumn',
|
'ticketStatus.boardColumn',
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ class ListAssignedTicketsByVisibleProject
|
|||||||
->where('assignee_user_id', $assignee->id)
|
->where('assignee_user_id', $assignee->id)
|
||||||
->whereIn('project_id', $visibleProjectIds)
|
->whereIn('project_id', $visibleProjectIds)
|
||||||
->with([
|
->with([
|
||||||
|
'acceptanceCriteria',
|
||||||
'assignee',
|
'assignee',
|
||||||
'project',
|
|
||||||
'ticketPriority',
|
'ticketPriority',
|
||||||
'ticketStatus.boardColumn',
|
'ticketStatus.boardColumn',
|
||||||
'ticketType',
|
'ticketType',
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\Tickets\ListAssignedTicketsByVisibleProject;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Inertia\Inertia;
|
||||||
|
use Inertia\Response;
|
||||||
|
|
||||||
|
class DashboardController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly ListAssignedTicketsByVisibleProject $listAssignedTicketsByVisibleProject,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function __invoke(Request $request): Response
|
||||||
|
{
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
return Inertia::render('Dashboard', [
|
||||||
|
'assignedTicketsByProject' => $this->listAssignedTicketsByVisibleProject->handle($user, $user),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,11 @@ class HandleAppearance
|
|||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next): Response
|
public function handle(Request $request, Closure $next): Response
|
||||||
{
|
{
|
||||||
View::share('appearance', $request->cookie('appearance') ?? 'system');
|
$appearance = $request->cookie('appearance');
|
||||||
|
$appearance = in_array($appearance, ['light', 'dark', 'system'], true) ? $appearance : 'system';
|
||||||
|
|
||||||
|
$request->attributes->set('appearance', $appearance);
|
||||||
|
View::share('appearance', $appearance);
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Http\Middleware;
|
|||||||
|
|
||||||
use App\Actions\Permissions\PermissionService;
|
use App\Actions\Permissions\PermissionService;
|
||||||
use App\Enums\Permission;
|
use App\Enums\Permission;
|
||||||
|
use App\Models\Project;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Middleware;
|
use Inertia\Middleware;
|
||||||
|
|
||||||
@@ -46,6 +47,11 @@ class HandleInertiaRequests extends Middleware
|
|||||||
return [
|
return [
|
||||||
...parent::share($request),
|
...parent::share($request),
|
||||||
'name' => config('app.name'),
|
'name' => config('app.name'),
|
||||||
|
'appearance' => $request->attributes->get('appearance', 'system'),
|
||||||
|
'localization' => [
|
||||||
|
'locale' => app()->getLocale(),
|
||||||
|
'timeZone' => config('app.timezone'),
|
||||||
|
],
|
||||||
'auth' => [
|
'auth' => [
|
||||||
'permissions' => [
|
'permissions' => [
|
||||||
'canManageTeams' => $user !== null && ($this->permissionService->canManageAnyTeam($user) || $this->permissionService->allows($user, Permission::TeamCreate)),
|
'canManageTeams' => $user !== null && ($this->permissionService->canManageAnyTeam($user) || $this->permissionService->allows($user, Permission::TeamCreate)),
|
||||||
@@ -55,6 +61,36 @@ class HandleInertiaRequests extends Middleware
|
|||||||
'user' => $user,
|
'user' => $user,
|
||||||
],
|
],
|
||||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||||
|
'navigation' => fn (): array => [
|
||||||
|
'project' => $this->projectNavigation($request),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{id: int, name: string, key: string, canManage: bool, canCreateTickets: bool}|null
|
||||||
|
*/
|
||||||
|
private function projectNavigation(Request $request): ?array
|
||||||
|
{
|
||||||
|
$project = $request->route('project');
|
||||||
|
$user = $request->user();
|
||||||
|
|
||||||
|
if (! $project instanceof Project || $user === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$permissions = $this->permissionService->effectivePermissions($user, $project);
|
||||||
|
|
||||||
|
if (! in_array(Permission::ProjectRead, $permissions, true)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => $project->id,
|
||||||
|
'name' => $project->name,
|
||||||
|
'key' => $project->key,
|
||||||
|
'canManage' => in_array(Permission::ProjectAdmin, $permissions, true),
|
||||||
|
'canCreateTickets' => in_array(Permission::ProjectWrite, $permissions, true),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
# UI Rework 1: Englische Lokalisierungsgrundlage
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: Keine.
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Englische Lokalisierungsgrundlage als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/app.ts](../../resources/js/app.ts); [resources/js/ssr.ts](../../resources/js/ssr.ts); [resources/js/types](../../resources/js/types). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Eine zentrale Übersetzungsstruktur für Vue einführen.
|
|
||||||
2. Englischen Katalog, Interpolation, Pluralformen und Formatierung festlegen.
|
|
||||||
3. Serverseitige Validierungstexte einheitlich Englisch halten.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Neue UI-Texte verwenden Schlüssel.
|
|
||||||
- [ ] Fehlende Schlüssel fallen im Test auf.
|
|
||||||
- [ ] Spätere Sprachen benötigen keine Änderungen an Komponenten.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# UI Rework 10: Persönliches Dashboard mit vorhandenen Daten
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 8](epic-1-issue-8.md), [Issue 11](epic-1-issue-11.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Persönliches Dashboard mit vorhandenen Daten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/Dashboard.vue](../../resources/js/pages/Dashboard.vue); [app/Actions/Tickets/ListAssignedTicketsByVisibleProject.php](../../app/Actions/Tickets/ListAssignedTicketsByVisibleProject.php); [routes/web.php](../../routes/web.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Persönliches Dashboard — 30b69ed84e5c4bdf8cd3fe26d7a9e803. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Dashboard aus zugewiesenen Tickets und sichtbaren Projekten aufbauen.
|
|
||||||
2. Vorhandene Action über einen Inertia-Controller und Resources anbinden.
|
|
||||||
3. Aus dem Mockup nicht belegbare Kennzahlen und Aktivitätsfeeds weglassen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Keine Platzhalterdaten.
|
|
||||||
- [ ] Keine fremden Tickets.
|
|
||||||
- [ ] Leere und gefüllte Dashboards funktionieren.
|
|
||||||
- [ ] Neue Datenweitergabe ist durch Featuretests abgesichert.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/DashboardTest.php](../../tests/Feature/DashboardTest.php), [tests/Feature/Api/AssignedTicketsApiTest.php](../../tests/Feature/Api/AssignedTicketsApiTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# UI Rework 11: Ticketkarten und wiederverwendbare Ticketlisten
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 3](epic-1-issue-3.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Ticketkarten und wiederverwendbare Ticketlisten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/components](../../resources/js/components); [resources/js/types/models/ticket-model.ts](../../resources/js/types/models/ticket-model.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Board View — 1a8363ad911a494f92cc4468955ce2f2. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Neue Karten und Listenzeilen mit Ticketkey, Titel, Typ, Priorität, Zuweisung und Blockerzustand entwickeln.
|
|
||||||
2. Darstellung mit und ohne optionale Daten definieren.
|
|
||||||
3. Gemeinsame Props und Ereignisse für Board, Backlog und Dashboard festlegen. Ticketlinks über Wayfinder aufbauen und Fokusführung sowie Touch-Ziele prüfen; die Komponenten dürfen keine eigenen Datenabfragen auslösen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Lange Titel bleiben bedienbar.
|
|
||||||
- [ ] Prioritäten und Status stammen aus Projektdaten.
|
|
||||||
- [ ] Links, Fokus und Touch-Ziele funktionieren.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 12: Boardansicht für Desktop und Mobilgeräte
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 8](epic-1-issue-8.md), [Issue 11](epic-1-issue-11.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Boardansicht für Desktop und Mobilgeräte als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Board.vue](../../resources/js/pages/projects/Board.vue); [app/Actions/Projects/BuildProjectBoardPayload.php](../../app/Actions/Projects/BuildProjectBoardPayload.php); [app/Http/Resources/BoardColumnResource.php](../../app/Http/Resources/BoardColumnResource.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Board View — 1a8363ad911a494f92cc4468955ce2f2. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Horizontales Board mit konfigurierten Spalten neu bauen.
|
|
||||||
2. Mobil eine Spalte mit Auswahl anbieten.
|
|
||||||
3. Statusunterteilung, Zähler, Leerzustände und vorhandene Statusauswahl darstellen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Beliebige Spaltenanzahl funktioniert.
|
|
||||||
- [ ] Mehrere Status pro Spalte bleiben sichtbar.
|
|
||||||
- [ ] Board und Backlog behalten ihre bestehende fachliche Trennung.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardControllerTest.php](../../tests/Feature/Projects/ProjectBoardControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
# UI Rework 13: Drag-and-drop für bestehende Statuswechsel
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 5](epic-1-issue-5.md), [Issue 12](epic-1-issue-12.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Drag-and-drop für bestehende Statuswechsel als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Board.vue](../../resources/js/pages/projects/Board.vue); [app/Http/Requests/MoveProjectTicketRequest.php](../../app/Http/Requests/MoveProjectTicketRequest.php); [app/Actions/Tickets/MoveProjectTicket.php](../../app/Actions/Tickets/MoveProjectTicket.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Board View — 1a8363ad911a494f92cc4468955ce2f2. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Ziehen per Maus und Touch ergänzen.
|
|
||||||
2. Zulässige Ziele aus Workflowdaten ableiten.
|
|
||||||
3. Bei mehreren Zielstatus Auswahl zeigen.
|
|
||||||
4. Vorhandenen Statusendpunkt über Wayfinder verwenden und Fehler zurückrollen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Unzulässige Ziele sind gesperrt.
|
|
||||||
- [ ] Alternative Tastatur-/Menübedienung bleibt möglich.
|
|
||||||
- [ ] Kein doppeltes Worklog.
|
|
||||||
- [ ] Keine persistierte freie Kartenreihenfolge.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardControllerTest.php](../../tests/Feature/Projects/ProjectBoardControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 14: Backlog und Ticketbaum
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 11](epic-1-issue-11.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Backlog und Ticketbaum als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Backlog.vue](../../resources/js/pages/projects/Backlog.vue); [resources/js/components/TicketTreeList.vue](../../resources/js/components/TicketTreeList.vue); [app/Actions/Projects/BuildProjectBacklogPayload.php](../../app/Actions/Projects/BuildProjectBacklogPayload.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Backlog mit vorhandenen Filtern und Hierarchie neu darstellen.
|
|
||||||
2. Eltern-Kind-Beziehungen, einklappbare Ebenen und vorhandene Aktionen integrieren.
|
|
||||||
3. Mobile Listenansicht erstellen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Verschachtelte Tickets und leere Treffer funktionieren.
|
|
||||||
- [ ] Berechtigungen und Statusübergänge bleiben erhalten.
|
|
||||||
- [ ] Filter verändern keine fachlichen Daten.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBacklogTest.php](../../tests/Feature/Projects/ProjectBacklogTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 15: Neue Ticketdetailansicht und Seitenstruktur
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md), [Issue 11](epic-1-issue-11.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Neue Ticketdetailansicht und Seitenstruktur als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Actions/Projects/BuildProjectTicketDetailPayload.php](../../app/Actions/Projects/BuildProjectTicketDetailPayload.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Ticket Detail View — 934e3eb46764456195f0eb627e67a363. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Detailansicht in klar abgegrenzte Komponenten aufteilen.
|
|
||||||
2. Desktop-Inhalt und Metadatenbereich sowie mobile Stapelansicht neu aufbauen.
|
|
||||||
3. Stabile Einhängepunkte für Bearbeitung, Checkliste, Beziehungen, Historie und Git definieren.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Alle bestehenden Informationen bleiben zugänglich.
|
|
||||||
- [ ] Keine bestehende Funktion wird beim Umbau entfernt.
|
|
||||||
- [ ] Direkte Ticket-URLs funktionieren.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 16: Ticketanlage und Bearbeitungsformulare
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 5](epic-1-issue-5.md), [Issue 15](epic-1-issue-15.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Ticketanlage und Bearbeitungsformulare als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/TicketCreate.vue](../../resources/js/pages/projects/TicketCreate.vue); [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Http/Requests/StoreProjectTicketRequest.php](../../app/Http/Requests/StoreProjectTicketRequest.php); [app/Http/Requests/UpdateProjectTicketRequest.php](../../app/Http/Requests/UpdateProjectTicketRequest.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Ticket Detail View — 934e3eb46764456195f0eb627e67a363. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Formulare für vorhandene Ticketattribute neu erstellen.
|
|
||||||
2. Gemeinsame Feldkomponenten für Anlage und Bearbeitung verwenden.
|
|
||||||
3. Projektkonfigurierte Felder, Autor-/Story-Point-Schalter und Validierung erhalten.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Anlage und Bearbeitung speichern dieselben Attribute wie bisher.
|
|
||||||
- [ ] Fehler und ungespeicherte Änderungen sind abgedeckt.
|
|
||||||
- [ ] Gesperrte Aktionen bleiben serverseitig abgesichert.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 17: Akzeptanzkriterien-Checkliste
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 16](epic-1-issue-16.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Akzeptanzkriterien-Checkliste als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/lib/acceptance-criteria.ts](../../resources/js/lib/acceptance-criteria.ts); [resources/js/types/models/acceptance-criteria-model.ts](../../resources/js/types/models/acceptance-criteria-model.ts); [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Ticket Detail View — 934e3eb46764456195f0eb627e67a363. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Bestehende hierarchische Checkliste als neue Komponente aufbauen.
|
|
||||||
2. Hinzufügen, Bearbeiten, Verschachteln und Abhaken entsprechend dem Bestand erhalten.
|
|
||||||
3. In Anlage und Detail integrieren.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Hierarchie und Reihenfolge bleiben beim Speichern erhalten.
|
|
||||||
- [ ] Bestehende Kriterien gehen nicht verloren.
|
|
||||||
- [ ] Touch und Tastatur funktionieren.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 18: Ticketbeziehungen und Hierarchie
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 15](epic-1-issue-15.md), [Issue 16](epic-1-issue-16.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Ticketbeziehungen und Hierarchie als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Http/Controllers/ProjectTicketLinkController.php](../../app/Http/Controllers/ProjectTicketLinkController.php); [resources/js/components/TicketTreeList.vue](../../resources/js/components/TicketTreeList.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Eltern-/Kindanzeige und Ticketverknüpfungen neu gestalten.
|
|
||||||
2. Bestehende Linktypen und Richtung verwenden.
|
|
||||||
3. Erlaubte Hinzufüge-/Entfernaktionen integrieren.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Bestehende Hierarchievalidierung bleibt wirksam.
|
|
||||||
- [ ] Linkrichtung wird korrekt angezeigt.
|
|
||||||
- [ ] Kein neuer Beziehungstyp wird eingeführt.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketLinkControllerTest.php](../../tests/Feature/Projects/ProjectTicketLinkControllerTest.php), [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 19: Blocker und Ticketänderungshistorie
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 15](epic-1-issue-15.md), [Issue 16](epic-1-issue-16.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Blocker und Ticketänderungshistorie als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [resources/js/types/models/ticket-worklog-entry-model.ts](../../resources/js/types/models/ticket-worklog-entry-model.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Blockieren/Entblockieren und Kommentarerfassung neu darstellen.
|
|
||||||
2. Vorhandenes Worklog als lesbare Historie integrieren.
|
|
||||||
3. Mobile Darstellung und lokale Datumsformatierung nutzen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Blockieren erfordert weiterhin Kommentar.
|
|
||||||
- [ ] Historie zeigt echte Ereignisse und Akteure.
|
|
||||||
- [ ] Keine neue Kommentar- oder Aktivitätsfunktion.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketControllerTest.php](../../tests/Feature/Projects/ProjectTicketControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# UI Rework 2: Emerald-Designsystem und Hell/Dunkel/System
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 1](epic-1-issue-1.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Emerald-Designsystem und Hell/Dunkel/System als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/css/app.css](../../resources/css/app.css); [resources/js/composables/useAppearance.ts](../../resources/js/composables/useAppearance.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Farben, Typografie, Abstände, Radien und Fokuszustände aus Stitch als semantische Tokens definieren.
|
|
||||||
2. Inter lokal ausliefern.
|
|
||||||
3. Hell/Dunkel/System einschließlich initialem Laden umsetzen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Beide Themes sind lesbar.
|
|
||||||
- [ ] Systemwechsel aktualisieren die Ansicht.
|
|
||||||
- [ ] Reduzierte Bewegung wird berücksichtigt.
|
|
||||||
- [ ] Keine Starterkit-Optik.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 20: Git-Bereich und Branch-Erstellung am Ticket
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 5](epic-1-issue-5.md), [Issue 15](epic-1-issue-15.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Git-Bereich und Branch-Erstellung am Ticket als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Ticket.vue](../../resources/js/pages/projects/Ticket.vue); [app/Http/Controllers/ProjectTicketBranchController.php](../../app/Http/Controllers/ProjectTicketBranchController.php); [resources/js/types/models/ticket-repository-branch-model.ts](../../resources/js/types/models/ticket-repository-branch-model.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Branch-/Pull-Request-Anzeigen sowie bestehenden Branch-Erstellablauf in eigene neue Komponenten überführen.
|
|
||||||
2. Repositoryauswahl, Namensvorschlag und vorhandene Prüfungen erhalten.
|
|
||||||
3. Laufende Anfragen, fehlenden Git-Zugriff und Providerfehler als eigene Zustände abbilden. Den Weg zu vorhandenen Verbindungseinstellungen anbieten, Formulareingaben bei Fehlern erhalten und externe Git-Links entsprechend kennzeichnen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Fehlende Git-Verbindung und Providerfehler sind verständlich.
|
|
||||||
- [ ] Branch-Erstellung funktioniert.
|
|
||||||
- [ ] Fremde Git-Links öffnen extern.
|
|
||||||
- [ ] Bestehende Zuordnungsregeln bleiben erhalten.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketBranchControllerTest.php](../../tests/Feature/Projects/ProjectTicketBranchControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 21: Profil und Erscheinungsbild
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Profil und Erscheinungsbild als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/settings/Profile.vue](../../resources/js/pages/settings/Profile.vue); [resources/js/pages/settings/Appearance.vue](../../resources/js/pages/settings/Appearance.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Profilfelder, Avatar-Upload/Entfernen und Erscheinungsbild neu gestalten.
|
|
||||||
2. Gemeinsame Einstellungsnavigation aufbauen.
|
|
||||||
3. Datenschutz durch bestehende Rechte und Endpunkte erhalten.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Avatar und Profiländerungen funktionieren.
|
|
||||||
- [ ] Hell/Dunkel/System bleibt nach Neustart erhalten.
|
|
||||||
- [ ] Uploadfehler und mobile Dateiauswahl sind berücksichtigt.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Settings/ProfileUpdateTest.php](../../tests/Feature/Settings/ProfileUpdateTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 22: Sicherheit und Anwendungsschlüssel
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 6](epic-1-issue-6.md), [Issue 7](epic-1-issue-7.md), [Issue 21](epic-1-issue-21.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Sicherheit und Anwendungsschlüssel als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/settings/Security.vue](../../resources/js/pages/settings/Security.vue); [resources/js/pages/settings/Applications.vue](../../resources/js/pages/settings/Applications.vue); [resources/js/components/TwoFactorSetupModal.vue](../../resources/js/components/TwoFactorSetupModal.vue); [resources/js/components/TwoFactorRecoveryCodes.vue](../../resources/js/components/TwoFactorRecoveryCodes.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Passwortverwaltung, 2FA-Einrichtung, Recovery-Codes und vorhandene Anwendungsschlüssel neu darstellen.
|
|
||||||
2. Sensible Bestätigungsabläufe erhalten.
|
|
||||||
3. Zustände vor und nach 2FA-Bestätigung sowie beim Anzeigen und Erneuern von Recovery-Codes anhand der bestehenden Endpunkte abbilden. Schlüsselanzeige und Kopierfunktion an die vorhandene Freigabelogik binden; fehlgeschlagene Bestätigungen direkt am Formular erklären.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] 2FA-Aktivieren/Deaktivieren und Recovery-Codes funktionieren.
|
|
||||||
- [ ] Schlüssel werden weiterhin nur nach bestehender Logik angezeigt.
|
|
||||||
- [ ] Abbruch und Fehler sind sicher.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Settings/SecurityTest.php](../../tests/Feature/Settings/SecurityTest.php), [tests/Feature/Settings/UserApplicationTest.php](../../tests/Feature/Settings/UserApplicationTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 23: Persönliche Git-Verbindungen
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 21](epic-1-issue-21.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Persönliche Git-Verbindungen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/settings/GitConnections.vue](../../resources/js/pages/settings/GitConnections.vue); [app/Http/Controllers/Settings/GitConnectionController.php](../../app/Http/Controllers/Settings/GitConnectionController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Vorhandene Git-Verbindungen und Formulare neu gestalten.
|
|
||||||
2. Provider- und Serverangaben klar darstellen.
|
|
||||||
3. Validierungs- und Providerfehler in das neue Rückmeldesystem integrieren.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Erstellen, Bearbeiten und Entfernen bleiben möglich.
|
|
||||||
- [ ] Zugangsdaten werden nicht zusätzlich offengelegt.
|
|
||||||
- [ ] Leere Listen und fehlende Berechtigungen funktionieren.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Settings/GitConnectionTest.php](../../tests/Feature/Settings/GitConnectionTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 24: Allgemeine Projekteinstellungen und Vererbung
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 9](epic-1-issue-9.md), [Issue 5](epic-1-issue-5.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Allgemeine Projekteinstellungen und Vererbung als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/settings/General.vue](../../resources/js/pages/projects/settings/General.vue); [resources/js/components/projects/TemplateInheritancePanel.vue](../../resources/js/components/projects/TemplateInheritancePanel.vue); [app/Http/Controllers/ProjectController.php](../../app/Http/Controllers/ProjectController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Einstellungsnavigation und allgemeine Projektseite neu aufbauen.
|
|
||||||
2. Vererbung und lokale Overrides nachvollziehbar anzeigen.
|
|
||||||
3. Bestehende Vorlagenwechsel-/Reset- und Löschbestätigungen erhalten.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Geerbte und lokale Werte sind unterscheidbar.
|
|
||||||
- [ ] Destruktive Aktionen erfordern bestehende Bestätigung.
|
|
||||||
- [ ] Keine neue Vorlagenlogik.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTemplateOverrideTest.php](../../tests/Feature/Projects/ProjectTemplateOverrideTest.php), [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 25: Workflow-Einstellungen für Spalten und Status
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 24](epic-1-issue-24.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Workflow-Einstellungen für Spalten und Status als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/settings/Board.vue](../../resources/js/pages/projects/settings/Board.vue); [app/Http/Controllers/ProjectBoardSettingsController.php](../../app/Http/Controllers/ProjectBoardSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Spalten, Statuszuordnung, Namen und vorhandene Sortierung neu bearbeiten lassen.
|
|
||||||
2. Backlogstatus und Löschbedingungen aus dem Bestand übernehmen.
|
|
||||||
3. Übergangsmatrix als separate Komponente vorbereiten.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Vorhandene CRUD-Aktionen funktionieren.
|
|
||||||
- [ ] Gültige Positionen bleiben erhalten.
|
|
||||||
- [ ] Genutzte Status und Spalten werden nicht unzulässig entfernt.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardSettingsControllerTest.php](../../tests/Feature/Projects/ProjectBoardSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 26: Bearbeitung erlaubter Statusübergänge
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 25](epic-1-issue-25.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Bearbeitung erlaubter Statusübergänge als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/settings/Board.vue](../../resources/js/pages/projects/settings/Board.vue); [app/Http/Controllers/ProjectBoardSettingsController.php](../../app/Http/Controllers/ProjectBoardSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Vorhandene Übergangsregeln als übersichtliche, mobil bedienbare Oberfläche neu erstellen.
|
|
||||||
2. Nur existierende Workflowfähigkeiten abbilden.
|
|
||||||
3. Gespeicherten Zustand und Fehler klar anzeigen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Erlaubte und verbotene Übergänge werden korrekt gespeichert.
|
|
||||||
- [ ] Board-Dropziele entsprechen der Konfiguration.
|
|
||||||
- [ ] Keine neue Workflow-Engine.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectBoardSettingsControllerTest.php](../../tests/Feature/Projects/ProjectBoardSettingsControllerTest.php), [tests/Feature/Projects/ProjectBoardControllerTest.php](../../tests/Feature/Projects/ProjectBoardControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 27: Tickettypen, Prioritäten und Anzeigeoptionen
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 24](epic-1-issue-24.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Tickettypen, Prioritäten und Anzeigeoptionen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/settings/Metadata.vue](../../resources/js/pages/projects/settings/Metadata.vue); [app/Http/Controllers/ProjectTicketMetadataSettingsController.php](../../app/Http/Controllers/ProjectTicketMetadataSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Typen, Prioritäten und bestehende Autor-/Story-Point-Optionen neu darstellen.
|
|
||||||
2. Hinzufügen, Umbenennen, Reihenfolge und bestehende Ersetzungsregeln beim Löschen erhalten.
|
|
||||||
3. Geschützte und bearbeitbare Einträge anhand vorhandener Berechtigungen unterscheiden. Vor dem Löschen die benötigte Ersatzzuordnung erklären und Validierungsfehler am betroffenen Eintrag anzeigen, ohne projektseitige Regeln zu verändern.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Geschützte Typen bleiben geschützt.
|
|
||||||
- [ ] Löschersatz und Reihenfolge funktionieren.
|
|
||||||
- [ ] Inhalte bleiben projektkonfigurierbar.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php](../../tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 28: Benutzerdefinierte Felder und Linktypen
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 27](epic-1-issue-27.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Benutzerdefinierte Felder und Linktypen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/settings/Metadata.vue](../../resources/js/pages/projects/settings/Metadata.vue); [app/Http/Controllers/ProjectTicketMetadataSettingsController.php](../../app/Http/Controllers/ProjectTicketMetadataSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Bestehende Felddefinitionen und Linktypen in separate neue Einstellungsabschnitte aufteilen.
|
|
||||||
2. Vorhandene Feldtypen, Richtung und Gegenbezeichnungen erhalten.
|
|
||||||
3. Anlage, Bearbeitung und vorhandene Löschabläufe über die bestehenden Endpunkte anbinden. Ungültige Definitionen mit konkreten Feldfehlern zurückmelden und bestehende Schutzregeln für bereits verwendete Felder und Linktypen in der Oberfläche nachvollziehbar machen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Keine neuen Feldtypen.
|
|
||||||
- [ ] Vorhandene Feldwerte bleiben erhalten.
|
|
||||||
- [ ] Linktypen und Gegenrichtung werden korrekt angezeigt und bearbeitet.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php](../../tests/Feature/Projects/ProjectTicketMetadataSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 29: Repository-Einstellungen des Projekts
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 23](epic-1-issue-23.md), [Issue 24](epic-1-issue-24.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Repository-Einstellungen des Projekts als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/settings/Repositories.vue](../../resources/js/pages/projects/settings/Repositories.vue); [app/Http/Controllers/ProjectRepositorySettingsController.php](../../app/Http/Controllers/ProjectRepositorySettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Projekt-Repository-Verbindungen mit vorhandener Discovery, Auswahl und Bearbeitung neu aufbauen.
|
|
||||||
2. Persönliche Verbindungen und Projektzuordnung klar unterscheiden.
|
|
||||||
3. Ladezustände, leere Suchergebnisse, fehlende persönliche Verbindung und Providerfehler getrennt darstellen. Auf die vorhandenen persönlichen Git-Einstellungen verlinken und nach erfolgreicher Zuordnung die Projektliste aus der Serverantwort aktualisieren.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Vorhandene Provider werden unterstützt.
|
|
||||||
- [ ] Fehlende Verbindung und Providerfehler sind abgedeckt.
|
|
||||||
- [ ] Tickets nutzen weiterhin dieselben Repositories.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectRepositorySettingsControllerTest.php](../../tests/Feature/Projects/ProjectRepositorySettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# UI Rework 3: Neue grundlegende UI-Komponenten
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 1](epic-1-issue-1.md), [Issue 2](epic-1-issue-2.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Neue grundlegende UI-Komponenten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/components/ui](../../resources/js/components/ui). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Buttons, Eingaben, Auswahlfelder, Checkboxen, Badges und Avatare neu gestalten.
|
|
||||||
2. Zugängliches Verhalten vorhandener Headless-Primitiven wiederverwenden.
|
|
||||||
3. Öffentliche Props für Folgetickets stabil festlegen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Tastatur, Fokus, disabled, invalid und loading funktionieren.
|
|
||||||
- [ ] Sichtbare Texte sind übersetzbar.
|
|
||||||
- [ ] Komponenten funktionieren in beiden Themes.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 30: Projekt-Benachrichtigungseinstellungen
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 24](epic-1-issue-24.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Projekt-Benachrichtigungseinstellungen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/settings/Notifications.vue](../../resources/js/pages/projects/settings/Notifications.vue); [app/Http/Controllers/ProjectNotificationSettingsController.php](../../app/Http/Controllers/ProjectNotificationSettingsController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Bestehende Aktionen, Trigger, Filter und Nachrichtenvorlagen neu gestalten.
|
|
||||||
2. Vorhandene Delivery-Informationen und Validierung nutzen.
|
|
||||||
3. Sensible Webhookfelder sorgfältig darstellen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Bestehende Benachrichtigungen lassen sich unverändert konfigurieren.
|
|
||||||
- [ ] Keine neuen Trigger oder Push-Dienste.
|
|
||||||
- [ ] Fehlerfälle sind getestet.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectNotificationSettingsControllerTest.php](../../tests/Feature/Projects/ProjectNotificationSettingsControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 31: Verwaltung wiederverwendbarer Projektvorlagen
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 9](epic-1-issue-9.md), [Issue 24](epic-1-issue-24.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Verwaltung wiederverwendbarer Projektvorlagen als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/project-templates/Index.vue](../../resources/js/pages/project-templates/Index.vue); [resources/js/pages/project-templates/Create.vue](../../resources/js/pages/project-templates/Create.vue); [resources/js/pages/project-templates/Edit.vue](../../resources/js/pages/project-templates/Edit.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Vorlagenübersicht, Erstellung und Bearbeitung neu aufbauen.
|
|
||||||
2. Auswirkungen auf vererbende Projekte und Bestätigungen darstellen.
|
|
||||||
3. Interne Basisvorlagen weiterhin ausblenden.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Vorhandene Erstell-/Bearbeitungsflüsse funktionieren.
|
|
||||||
- [ ] Unveränderliche Slugs bleiben gesperrt.
|
|
||||||
- [ ] Keine Änderungen an Vererbungsregeln.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectTemplateControllerTest.php](../../tests/Feature/Projects/ProjectTemplateControllerTest.php), [tests/Feature/Projects/ProjectTemplateOverrideTest.php](../../tests/Feature/Projects/ProjectTemplateOverrideTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# UI Rework 32: Teams und Mitgliedschaft
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Teams und Mitgliedschaft als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/teams/Index.vue](../../resources/js/pages/teams/Index.vue); [app/Http/Controllers/TeamController.php](../../app/Http/Controllers/TeamController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Teamübersicht, Teamverwaltung und Mitgliedsauswahl im neuen Design umsetzen.
|
|
||||||
2. Alle bestehenden Rechte und Aktionen übernehmen.
|
|
||||||
3. Mobile Bedienung sicherstellen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Team- und Mitgliedsaktionen entsprechen dem Bestand.
|
|
||||||
- [ ] Fehlende Rechte und Validierungsfehler sind abgedeckt.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Teams/TeamControllerTest.php](../../tests/Feature/Teams/TeamControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 33: Projektzugriff und Rechtezuweisung
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 8](epic-1-issue-8.md), [Issue 32](epic-1-issue-32.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Projektzugriff und Rechtezuweisung als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Access.vue](../../resources/js/pages/projects/Access.vue); [app/Http/Controllers/ProjectController.php](../../app/Http/Controllers/ProjectController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Direkte Benutzerrechte und Teamrechte neu darstellen.
|
|
||||||
2. Effektive Rechte und Bearbeitungsgrenzen nachvollziehbar machen.
|
|
||||||
3. Bestehende Zuweisungsendpunkte weiterverwenden.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Keine neue Rollenlogik.
|
|
||||||
- [ ] Rechte gelten weiterhin additiv nach Bestand.
|
|
||||||
- [ ] Unberechtigte Zuweisungen werden serverseitig abgewiesen.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php), [tests/Feature/Permissions/PermissionServiceTest.php](../../tests/Feature/Permissions/PermissionServiceTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 34: Benutzeradministration
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Benutzeradministration als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/admin/users/Index.vue](../../resources/js/pages/admin/users/Index.vue); [resources/js/pages/admin/users/Create.vue](../../resources/js/pages/admin/users/Create.vue); [resources/js/pages/admin/users/Edit.vue](../../resources/js/pages/admin/users/Edit.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Übersicht, Benutzeranlage und Bearbeitung vollständig neu gestalten.
|
|
||||||
2. Vorhandene Felder, Adminoptionen und erzwungenen Passwortwechsel erhalten.
|
|
||||||
3. Verfügbare Aktionen aus den bestehenden Berechtigungen ableiten, sensible Änderungen bestätigen lassen und Validierungsfehler direkt den Eingaben zuordnen. Nach Speichern oder Abbruch zur passenden Übersicht zurückführen, ohne neue Rollen oder Verwaltungsaktionen einzuführen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Bestehende Adminaktionen funktionieren.
|
|
||||||
- [ ] Nicht-Admins erhalten keinen Zugriff.
|
|
||||||
- [ ] Formfehler und sensible Aktionen sind abgedeckt.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Admin/UserManagementControllerTest.php](../../tests/Feature/Admin/UserManagementControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# UI Rework 35: Öffentliche Einstiegsseite
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 2](epic-1-issue-2.md), [Issue 3](epic-1-issue-3.md), [Issue 6](epic-1-issue-6.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Öffentliche Einstiegsseite als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/Welcome.vue](../../resources/js/pages/Welcome.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
Relevanter Stitch-Screen: Landing Page - Open Source & Self-Hosted — 41a744db2b1f4e01a9c597835b58759e. Zugriff über Stitch-MCP, Projekt 9754281177402648015.
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Neue kompakte öffentliche Einstiegsseite mit KanbanGit-Branding und Anmeldung entwickeln.
|
|
||||||
2. Stitch-Landingpage ausschließlich als Stilreferenz nutzen.
|
|
||||||
3. Nur vorhandene Funktionen beschreiben.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Keine erfundenen Produktfunktionen, Tarife oder Statistiken.
|
|
||||||
- [ ] Responsiv und lokalisierbar.
|
|
||||||
- [ ] Einstieg in Anmeldung funktioniert.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 36: Tauri-Grundgerüst und macOS-Entwicklungsbuild
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: Keine.
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Tauri-Grundgerüst und macOS-Entwicklungsbuild als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native/` (in diesem Ticket neu anzulegen); [.gitignore](../../.gitignore). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Remote-Inhalte und native Berechtigungen](https://v2.tauri.app/security/capabilities/).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Isoliertes Tauri-2-Projekt im gemeinsamen Repository anlegen.
|
|
||||||
2. Minimale lokale Startansicht und Server-WebView erstellen.
|
|
||||||
3. Entwicklungsbuild auf macOS mit vorhandenem KanbanGit prüfen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Laravel bleibt am bisherigen Ort.
|
|
||||||
- [ ] macOS-App startet und zeigt die Serveroberfläche.
|
|
||||||
- [ ] Keine allgemeine native API für Remote-Inhalte.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 37: Serverauswahl, Sitzungen und Verbindungsfehler
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 36](epic-1-issue-36.md), [Issue 6](epic-1-issue-6.md), [Issue 7](epic-1-issue-7.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Serverauswahl, Sitzungen und Verbindungsfehler als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native` (entsteht ab Issue 36); [resources/js/pages/auth](../../resources/js/pages/auth). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Remote-Inhalte und native Berechtigungen](https://v2.tauri.app/security/capabilities/).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Eine aktive, frei wählbare HTTPS-Installation speichern.
|
|
||||||
2. Serverwechsel und lokale Fehleransicht umsetzen.
|
|
||||||
3. Sitzungen, Logout, 2FA, Start ohne Verbindung und Formularfehler prüfen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Kein Zertifikats-Bypass.
|
|
||||||
- [ ] Sitzungserhalt folgt Laravel.
|
|
||||||
- [ ] Serverwechsel übernimmt keine fremden Zugangsdaten.
|
|
||||||
- [ ] Bestehende Eingaben werden bei Verbindungsfehler nicht durch einen leeren Bildschirm ersetzt.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 38: Android-App und Plattformanpassung
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 37](epic-1-issue-37.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Android-App und Plattformanpassung als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native (Android-Plattformprojekt)` (entsteht ab Issue 36). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Android-Ziel konfigurieren und Entwicklungsbuild erstellen.
|
|
||||||
2. Zurück-Taste, Systemleisten, Safe Areas und Bildschirmtastatur anpassen.
|
|
||||||
3. Bestehende Server-/Anmeldeabläufe prüfen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] App startet auf Emulator und geeignetem Gerät.
|
|
||||||
- [ ] Login, 2FA, Navigation und Wiederaufnahme funktionieren.
|
|
||||||
- [ ] Keine eigenständige zweite Fachoberfläche.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 39: iOS/iPadOS-App für lokale Xcode-Builds
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 37](epic-1-issue-37.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
iOS/iPadOS-App für lokale Xcode-Builds als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native (Apple-Plattformprojekt)` (entsteht ab Issue 36). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. IOS/iPadOS-Ziel für lokalen Xcode-Build konfigurieren.
|
|
||||||
2. WKWebView-Sitzungen, Safe Areas, Tastatur und Größenwechsel prüfen.
|
|
||||||
3. Benötigte lokale Einrichtung im Ticket dokumentieren.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Simulatorbuild funktioniert.
|
|
||||||
- [ ] Gerätebuild nutzt vom Nutzer bereitgestellte Signierung.
|
|
||||||
- [ ] Kein TestFlight- oder Store-Upload.
|
|
||||||
- [ ] IPad bleibt bedienbar.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# UI Rework 4: Responsive Anwendungsnavigation
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 3](epic-1-issue-3.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Responsive Anwendungsnavigation als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/layouts](../../resources/js/layouts); [resources/js/components/AppSidebar.vue](../../resources/js/components/AppSidebar.vue); [resources/js/types/navigation.ts](../../resources/js/types/navigation.ts). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Neue Sidebar, obere Werkzeugleiste, Projektnavigation und mobile Navigation aufbauen.
|
|
||||||
2. Nur vorhandene und erlaubte Bereiche verlinken.
|
|
||||||
3. Aktuelle Auswahl und kompakte Bildschirmgrößen berücksichtigen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Alle bestehenden Bereiche bleiben erreichbar.
|
|
||||||
- [ ] Rollen sehen nur erlaubte Links.
|
|
||||||
- [ ] Browser-Zurück und mobile Navigation funktionieren.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 40: App-Links, externe Links und Dateiauswahl
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 20](epic-1-issue-20.md), [Issue 21](epic-1-issue-21.md), [Issue 38](epic-1-issue-38.md), [Issue 39](epic-1-issue-39.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
App-Links, externe Links und Dateiauswahl als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native` (entsteht ab Issue 36); [resources/js/components](../../resources/js/components). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Remote-Inhalte und native Berechtigungen](https://v2.tauri.app/security/capabilities/).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Interne App-Links zur ausgewählten Installation und externe Browserlinks umsetzen.
|
|
||||||
2. Avatar-Dateiauswahl, Zwischenablage und Browser-Zurück plattformübergreifend absichern.
|
|
||||||
3. Native Schnittstellen eng begrenzen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Fremde Links wechseln nicht ungefragt den Server.
|
|
||||||
- [ ] Externe Git-Links funktionieren.
|
|
||||||
- [ ] Uploads und Kopieren funktionieren auf allen App-Plattformen.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 41: Signierter macOS-Release-Download
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 40](epic-1-issue-40.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Signierter macOS-Release-Download als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native` (entsteht ab Issue 36); [.github/workflows](../../.github/workflows). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [macOS-Signierung](https://v2.tauri.app/distribute/sign/macos/).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Reproduzierbaren macOS-Releasebuild mit Signierung und Notarisierung vorbereiten.
|
|
||||||
2. Zertifikate nur als externe Secrets erwarten.
|
|
||||||
3. Installationsartefakt für Git-Releases erzeugen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Builds verwenden keine eingecheckten Schlüssel.
|
|
||||||
- [ ] Signiertes Artefakt lässt sich mit bereitgestellten Voraussetzungen installieren.
|
|
||||||
- [ ] Fehlende Secrets werden klar gemeldet.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# UI Rework 42: Signiertes Android-APK für Git-Releases
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 40](epic-1-issue-40.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Signiertes Android-APK für Git-Releases als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native` (entsteht ab Issue 36); [.github/workflows](../../.github/workflows). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/), [Android-Releasevorbereitung](https://developer.android.com/studio/publish/preparing).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Reproduzierbaren signierten APK-Releasebuild konfigurieren.
|
|
||||||
2. Dauerhafte Signing-Identität über Secrets beziehen.
|
|
||||||
3. Versionierung und Upgrade vorhandener Installationen berücksichtigen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] APK ist direkt installierbar.
|
|
||||||
- [ ] Updates behalten Daten/Sitzungen entsprechend Plattformverhalten.
|
|
||||||
- [ ] Schlüssel fehlen im Repository.
|
|
||||||
- [ ] Kein Play-Store-Upload.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# UI Rework 43: Getrennte Prüf- und Build-Pipelines
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 39](epic-1-issue-39.md), [Issue 41](epic-1-issue-41.md), [Issue 42](epic-1-issue-42.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Getrennte Prüf- und Build-Pipelines als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [Dockerfile](../../Dockerfile); [.github/workflows/ci_cd.yml](../../.github/workflows/ci_cd.yml); [.github/tests/test_and_check_app/action.yml](../../.github/tests/test_and_check_app/action.yml). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
Technische Referenz: [Tauri 2](https://v2.tauri.app/).
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Laravel/Web-Prüfungen von Tauri-Builds trennen.
|
|
||||||
2. Passende Linux-/macOS-Runner festlegen.
|
|
||||||
3. Änderungen nach Pfaden zuordnen, gemeinsame Änderungen vollständig prüfen.
|
|
||||||
4. Bestehendes Serverrelease erhalten.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Serverimage enthält keine native Toolchain.
|
|
||||||
- [ ] Erforderliche Prüfungen werden nicht übersprungen.
|
|
||||||
- [ ] macOS/Android-Artefakte sind getrennt.
|
|
||||||
- [ ] iOS bleibt Selbstbau.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 44: Web-Abnahme und Entfernung des alten Frontends
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 1](epic-1-issue-1.md), [Issue 2](epic-1-issue-2.md), [Issue 3](epic-1-issue-3.md), [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md), [Issue 6](epic-1-issue-6.md), [Issue 7](epic-1-issue-7.md), [Issue 8](epic-1-issue-8.md), [Issue 9](epic-1-issue-9.md), [Issue 10](epic-1-issue-10.md), [Issue 11](epic-1-issue-11.md), [Issue 12](epic-1-issue-12.md), [Issue 13](epic-1-issue-13.md), [Issue 14](epic-1-issue-14.md), [Issue 15](epic-1-issue-15.md), [Issue 16](epic-1-issue-16.md), [Issue 17](epic-1-issue-17.md), [Issue 18](epic-1-issue-18.md), [Issue 19](epic-1-issue-19.md), [Issue 20](epic-1-issue-20.md), [Issue 21](epic-1-issue-21.md), [Issue 22](epic-1-issue-22.md), [Issue 23](epic-1-issue-23.md), [Issue 24](epic-1-issue-24.md), [Issue 25](epic-1-issue-25.md), [Issue 26](epic-1-issue-26.md), [Issue 27](epic-1-issue-27.md), [Issue 28](epic-1-issue-28.md), [Issue 29](epic-1-issue-29.md), [Issue 30](epic-1-issue-30.md), [Issue 31](epic-1-issue-31.md), [Issue 32](epic-1-issue-32.md), [Issue 33](epic-1-issue-33.md), [Issue 34](epic-1-issue-34.md), [Issue 35](epic-1-issue-35.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Web-Abnahme und Entfernung des alten Frontends als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js](../../resources/js); [resources/css](../../resources/css); [tests/Feature](../../tests/Feature). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Alle vorhandenen Seiten und Aktionen gegen die Bestandsliste prüfen.
|
|
||||||
2. Desktop/Tablet/Mobil sowie Hell/Dunkel/System visuell und funktional abnehmen.
|
|
||||||
3. Nur nicht mehr referenzierte alte UI-Dateien entfernen.
|
|
||||||
4. Größere neue Befunde in eigene Folgeaufgaben abgrenzen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Keine Starterkit-Seite oder unübersetzte neue UI-Texte.
|
|
||||||
- [ ] Keine verlorenen Bestandsfunktionen.
|
|
||||||
- [ ] Keine bestehenden Tests entfernt.
|
|
||||||
- [ ] Relevante Tests, Build, Lint und abschließender Typecheck bestehen.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# UI Rework 45: Plattformübergreifende App-Abnahme
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 40](epic-1-issue-40.md), [Issue 41](epic-1-issue-41.md), [Issue 42](epic-1-issue-42.md), [Issue 43](epic-1-issue-43.md), [Issue 44](epic-1-issue-44.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Plattformübergreifende App-Abnahme als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: `native` (entsteht ab Issue 36); [resources/js](../../resources/js). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. macOS, Android und iOS/iPadOS mit fertigem Webfrontend prüfen.
|
|
||||||
2. Serverwechsel, Sessionablauf, Serverupdate, Verbindungsabbruch, Links und Dateiauswahl abnehmen.
|
|
||||||
3. Dokumentierte reproduzierbare Befunde mit klaren Reproduktionsschritten festhalten.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Alle vereinbarten App-Abläufe bestehen.
|
|
||||||
- [ ] Keine Produktionsdaten als Testfixtures verändert.
|
|
||||||
- [ ] Releasewege entsprechen Vereinbarung.
|
|
||||||
- [ ] Größere Defekte werden nicht im Abnahmeticket versteckt.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Zunächst den im Ticket genannten Plattformbuild und die beschriebenen Abläufe prüfen. Für neue App-Logik gezielte Tests für Navigation, Zustände oder URL-Verarbeitung ergänzen; Plattformverhalten im passenden Simulator bzw. auf einem geeigneten Gerät prüfen. Bei Vue/TypeScript-Änderungen abschließend `npm run types:check`; bei Backend-Anpassungen betroffene PHPUnit-Tests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
# UI Rework 5: Dialoge, Rückmeldungen und Eingabeschutz
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 3](epic-1-issue-3.md), [Issue 4](epic-1-issue-4.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Dialoge, Rückmeldungen und Eingabeschutz als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/components](../../resources/js/components); [resources/js/composables](../../resources/js/composables). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Dialoge, Menüs, Leer-, Lade- und Fehlerzustände vereinheitlichen.
|
|
||||||
2. Formfehler und Wiederholaktionen definieren.
|
|
||||||
3. Ungespeicherte Eingaben gegen Navigation und fehlgeschlagene Requests absichern.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Keine verlorenen Eingaben bei Requestfehlern.
|
|
||||||
- [ ] Dialogfokus wird korrekt zurückgegeben.
|
|
||||||
- [ ] Netzwerkfehler und serverseitige Validierung sind unterscheidbar.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 6: Anmeldung, E-Mail-Verifikation und 2FA-Challenge
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Anmeldung, E-Mail-Verifikation und 2FA-Challenge als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/auth/Login.vue](../../resources/js/pages/auth/Login.vue); [resources/js/pages/auth/VerifyEmail.vue](../../resources/js/pages/auth/VerifyEmail.vue); [resources/js/pages/auth/TwoFactorChallenge.vue](../../resources/js/pages/auth/TwoFactorChallenge.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Die drei Ansichten vollständig neu erstellen.
|
|
||||||
2. Bestehende Fortify-Routen und Sessionflüsse verwenden.
|
|
||||||
3. Mobile Tastatur, Autofill und Recovery-Codes berücksichtigen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Normale und zweistufige Anmeldung funktionieren.
|
|
||||||
- [ ] Fehler erhalten passende Eingaben.
|
|
||||||
- [ ] Bestehende Autorisierungsregeln bleiben wirksam.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Auth/AuthenticationTest.php](../../tests/Feature/Auth/AuthenticationTest.php), [tests/Feature/Auth/TwoFactorChallengeTest.php](../../tests/Feature/Auth/TwoFactorChallengeTest.php), [tests/Feature/Auth/EmailVerificationTest.php](../../tests/Feature/Auth/EmailVerificationTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 7: Passwort- und Bestätigungsseiten
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 6](epic-1-issue-6.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Passwort- und Bestätigungsseiten als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/auth/ForgotPassword.vue](../../resources/js/pages/auth/ForgotPassword.vue); [resources/js/pages/auth/ResetPassword.vue](../../resources/js/pages/auth/ResetPassword.vue); [resources/js/pages/auth/ConfirmPassword.vue](../../resources/js/pages/auth/ConfirmPassword.vue); [resources/js/pages/auth/ForcePasswordChange.vue](../../resources/js/pages/auth/ForcePasswordChange.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Passwort-Reset, sensible Aktionsbestätigung und erzwungenen Passwortwechsel neu gestalten.
|
|
||||||
2. Bestehende Fortify-Verträge erhalten.
|
|
||||||
3. Zustände abgelaufener oder ungültiger Links berücksichtigen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Alle vier Flüsse funktionieren mit bestehenden Endpunkten.
|
|
||||||
- [ ] Sensible Werte werden nicht in Entwürfen gespeichert.
|
|
||||||
- [ ] Erfolg und Fehler sind klar.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Auth/PasswordResetTest.php](../../tests/Feature/Auth/PasswordResetTest.php), [tests/Feature/Auth/PasswordConfirmationTest.php](../../tests/Feature/Auth/PasswordConfirmationTest.php), [tests/Feature/Settings/SecurityTest.php](../../tests/Feature/Settings/SecurityTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 8: Projektübersicht und Projekteinstieg
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 4](epic-1-issue-4.md), [Issue 5](epic-1-issue-5.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Projektübersicht und Projekteinstieg als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Index.vue](../../resources/js/pages/projects/Index.vue); [resources/js/pages/projects/Show.vue](../../resources/js/pages/projects/Show.vue). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Projektübersicht und Projekteinstieg neu aufbauen.
|
|
||||||
2. Erlaubte Aktionen und direkte Wege zu Board, Backlog und Einstellungen anbieten.
|
|
||||||
3. Vorhandene Resources nutzen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Nur sichtbare Projekte erscheinen.
|
|
||||||
- [ ] Leerzustand und eingeschränkte Rechte sind abgedeckt.
|
|
||||||
- [ ] Keine Mockup-Funktionen ohne Backend.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# UI Rework 9: Projektanlage, Duplikation und Vorlagenauswahl
|
|
||||||
|
|
||||||
Epic: [Epic 1](epic-1.md)
|
|
||||||
|
|
||||||
Abhängigkeiten: [Issue 8](epic-1-issue-8.md)
|
|
||||||
|
|
||||||
## Ziel und Grenzen
|
|
||||||
|
|
||||||
Projektanlage, Duplikation und Vorlagenauswahl als abgegrenzten Teil des vollständigen Frontend-Neubaus umsetzen. Grundlage: Vue 3/Inertia 2 mit Laravel, Tauri 2 für Apps, gemeinsames Repository, servergeladene Oberfläche, HTTPS, Hell/Dunkel/System und englische Texte über Übersetzungsschlüssel. Bestehende Fachlogik bleibt maßgeblich; Resources und Routen nur soweit für diese Oberfläche erforderlich anpassen. Keine zusätzlichen Produktmodule.
|
|
||||||
|
|
||||||
## Einstieg
|
|
||||||
|
|
||||||
Gezielt prüfen: [resources/js/pages/projects/Create.vue](../../resources/js/pages/projects/Create.vue); [app/Http/Controllers/ProjectController.php](../../app/Http/Controllers/ProjectController.php). Die Pfade sind Einstiegspunkte, keine Erlaubnis zu fachlichen Umbauten. [AGENTS.md](../../AGENTS.md) und die für den tatsächlich geänderten Bereich geltenden Skills beachten. Vor Codeänderungen die zur installierten Version passende Dokumentation prüfen.
|
|
||||||
|
|
||||||
Design: [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015)
|
|
||||||
|
|
||||||
## Implementierungsweg
|
|
||||||
|
|
||||||
1. Neuen Erstellablauf für leeres Projekt, Duplikat und Vorlage bauen.
|
|
||||||
2. Bedingte Felder und Kopieroptionen aus dem Bestand übernehmen.
|
|
||||||
3. Backend-Verträge nur bei Darstellungsbedarf anpassen.
|
|
||||||
|
|
||||||
## Akzeptanzkriterien
|
|
||||||
|
|
||||||
- [ ] Alle drei vorhandenen Erstellwege bleiben korrekt.
|
|
||||||
- [ ] Validierung ist verständlich.
|
|
||||||
- [ ] Operative Ticketdaten werden weiterhin nicht dupliziert.
|
|
||||||
|
|
||||||
## Prüfung
|
|
||||||
|
|
||||||
Vorhandene gezielte Abdeckung: [tests/Feature/Projects/ProjectControllerTest.php](../../tests/Feature/Projects/ProjectControllerTest.php), [tests/Feature/Projects/ProjectTemplateControllerTest.php](../../tests/Feature/Projects/ProjectTemplateControllerTest.php). Nur bei Berührung des jeweiligen Verhaltens aktualisieren bzw. ausführen.
|
|
||||||
|
|
||||||
Gezielte Verhaltenstests für die Änderung; betroffene PHPUnit-Featuretests bei Controller-/Resource-/Route-Anpassungen. Oberfläche mit Tastatur und bei mobiler Breite in beiden Themes prüfen. Projektvorgaben für Lint/Build und bei Vue/TypeScript abschließend `npm run types:check` erfüllen. Keine ausschließlich optischen Momentaufnahmen als Ersatz für Verhaltenstests.
|
|
||||||
|
|
||||||
## Grenze der Agentensitzung
|
|
||||||
|
|
||||||
Nur dieses Ticket und bereits erfüllte Abhängigkeiten bearbeiten. Ein zusammenhängendes Ergebnis mit passenden Tests liefern; keine Nachbartickets mitziehen. Die Aufteilung zielt auf eine begrenzte Sitzung mit Reserve für Prüfung, garantiert aber kein festes Zeit- oder Tokenbudget. Wenn unerwartete Voraussetzungen den Umfang wesentlich vergrößern, rechtzeitig den Stand, betroffene Dateien, bereits ausgeführte Prüfungen und den konkret nötigen Folgeschritt festhalten.
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# Epic 1: KanbanGit UI Rework und Tauri-Apps
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
Die vorhandene, aus dem Laravel-Starterkit entstandene Oberfläche vollständig durch eine neue Vue/Inertia-Oberfläche im Stil von [Fluid Emerald Kanban](https://stitch.withgoogle.com/projects/9754281177402648015) ersetzen. Alle bestehenden Produktfunktionen erhalten ein neues, auf Desktop und Mobilgeräte abgestimmtes Erscheinungsbild.
|
|
||||||
|
|
||||||
Eine gemeinsame servergeladene Oberfläche versorgt Browser sowie Tauri-Apps für macOS, iOS/iPadOS und Android. Backend und Clients bleiben im selben Repository. Selbst gehostete Installationen werden über eine frei wählbare HTTPS-Adresse angebunden; jeweils eine Installation ist aktiv.
|
|
||||||
|
|
||||||
Zum Zielumfang gehören Hell/Dunkel/System, eine Lokalisierungsstruktur mit zunächst englischen Texten und bessere Frontend-Bedienung einschließlich Drag-and-drop für bestehende Statuswechsel. Anpassungen an Resources, Inertia-Daten und erforderlichen Routen sind zulässig.
|
|
||||||
|
|
||||||
Ausgeschlossen sind neue fachliche Backend-Funktionen, zusätzliche Mockup-Module, Offline-Datensynchronisierung, persistierte freie Kartenreihenfolge und Hintergrund-Push. macOS und Android werden als signierte Git-Release-Downloads vorbereitet; iOS/iPadOS wird zunächst selbst über Xcode gebaut. Stores folgen später.
|
|
||||||
|
|
||||||
## Abschluss
|
|
||||||
|
|
||||||
Die neue Oberfläche deckt die bestehenden Funktionen ab, funktioniert auf allen vereinbarten Plattformen und hat das alte Starterkit-Frontend vollständig abgelöst. Die Umsetzung ist in eigenständige, begrenzte Agentensitzungen aufgeteilt.
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,92 @@
|
|||||||
|
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION AND CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
+87
-122
@@ -1,56 +1,98 @@
|
|||||||
@import 'tailwindcss';
|
@import 'tailwindcss';
|
||||||
|
|
||||||
@import 'tw-animate-css';
|
@import 'tw-animate-css';
|
||||||
|
@import './theme.css';
|
||||||
|
|
||||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||||
@source '../../storage/framework/views/*.php';
|
@source '../../storage/framework/views/*.php';
|
||||||
|
@source not '../../bootstrap/ssr';
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/inter/InterVariable.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/inter/InterVariable-Italic.woff2') format('woff2');
|
||||||
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--font-sans:
|
--font-sans: Inter, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
|
||||||
Instrument Sans, ui-sans-serif, system-ui, sans-serif,
|
|
||||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
|
||||||
'Noto Color Emoji';
|
|
||||||
|
|
||||||
--radius-lg: var(--radius);
|
--radius-sm: 0.125rem;
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: var(--radius);
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-lg: 0.5rem;
|
||||||
|
--radius-xl: 0.75rem;
|
||||||
|
|
||||||
|
--spacing-page: 1.5rem;
|
||||||
|
--spacing-column: 1rem;
|
||||||
|
--spacing-card: 0.75rem;
|
||||||
|
--spacing-stack: 0.5rem;
|
||||||
|
--spacing-control: 2.75rem;
|
||||||
|
|
||||||
|
--text-headline: 1.5rem;
|
||||||
|
--text-headline--line-height: 2rem;
|
||||||
|
--text-headline--font-weight: 600;
|
||||||
|
--text-headline--letter-spacing: -0.02em;
|
||||||
|
--text-headline-mobile: 1.25rem;
|
||||||
|
--text-headline-mobile--line-height: 1.75rem;
|
||||||
|
--text-headline-mobile--font-weight: 600;
|
||||||
|
--text-headline-mobile--letter-spacing: -0.02em;
|
||||||
|
--text-title: 1.125rem;
|
||||||
|
--text-title--line-height: 1.5rem;
|
||||||
|
--text-title--font-weight: 600;
|
||||||
|
--text-title--letter-spacing: -0.01em;
|
||||||
|
--text-body: 0.875rem;
|
||||||
|
--text-body--line-height: 1.25rem;
|
||||||
|
--text-caption: 0.8125rem;
|
||||||
|
--text-caption--line-height: 1.125rem;
|
||||||
|
--text-label: 0.6875rem;
|
||||||
|
--text-label--line-height: 1rem;
|
||||||
|
--text-label--font-weight: 700;
|
||||||
|
--text-label--letter-spacing: 0.05em;
|
||||||
|
|
||||||
|
--shadow-card-hover: 0 4px 12px rgb(0 0 0 / 0.08);
|
||||||
|
|
||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
|
|
||||||
--color-card: var(--card);
|
--color-card: var(--card);
|
||||||
--color-card-foreground: var(--card-foreground);
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
|
||||||
--color-popover: var(--popover);
|
--color-popover: var(--popover);
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
|
||||||
--color-primary: var(--primary);
|
--color-primary: var(--primary);
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
|
||||||
--color-secondary: var(--secondary);
|
--color-secondary: var(--secondary);
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
|
||||||
--color-muted: var(--muted);
|
--color-muted: var(--muted);
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
|
||||||
--color-accent: var(--accent);
|
--color-accent: var(--accent);
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
|
||||||
--color-destructive: var(--destructive);
|
--color-destructive: var(--destructive);
|
||||||
--color-destructive-foreground: var(--destructive-foreground);
|
--color-destructive-foreground: var(--destructive-foreground);
|
||||||
|
|
||||||
--color-border: var(--border);
|
--color-border: var(--border);
|
||||||
--color-input: var(--input);
|
--color-input: var(--input);
|
||||||
--color-ring: var(--ring);
|
--color-ring: var(--ring);
|
||||||
|
--color-brand: var(--brand);
|
||||||
|
--color-brand-foreground: var(--brand-foreground);
|
||||||
|
--color-success: var(--success);
|
||||||
|
--color-success-foreground: var(--success-foreground);
|
||||||
|
--color-warning: var(--warning);
|
||||||
|
--color-warning-foreground: var(--warning-foreground);
|
||||||
|
--color-info: var(--info);
|
||||||
|
--color-info-foreground: var(--info-foreground);
|
||||||
--color-chart-1: var(--chart-1);
|
--color-chart-1: var(--chart-1);
|
||||||
--color-chart-2: var(--chart-2);
|
--color-chart-2: var(--chart-2);
|
||||||
--color-chart-3: var(--chart-3);
|
--color-chart-3: var(--chart-3);
|
||||||
--color-chart-4: var(--chart-4);
|
--color-chart-4: var(--chart-4);
|
||||||
--color-chart-5: var(--chart-5);
|
--color-chart-5: var(--chart-5);
|
||||||
|
|
||||||
--color-sidebar: var(--sidebar-background);
|
--color-sidebar: var(--sidebar-background);
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
@@ -61,112 +103,35 @@
|
|||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
The default border color has changed to `currentColor` in Tailwind CSS v4,
|
|
||||||
so we've added these compatibility styles to make sure everything still
|
|
||||||
looks the same as it did with Tailwind CSS v3.
|
|
||||||
|
|
||||||
If we ever want to remove these styles, we need to add an explicit border
|
|
||||||
color utility to any element that depends on these defaults.
|
|
||||||
*/
|
|
||||||
@layer base {
|
|
||||||
*,
|
|
||||||
::after,
|
|
||||||
::before,
|
|
||||||
::backdrop,
|
|
||||||
::file-selector-button {
|
|
||||||
border-color: var(--color-gray-200, currentColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer utilities {
|
|
||||||
body,
|
|
||||||
html {
|
|
||||||
--font-sans:
|
|
||||||
'Instrument Sans', ui-sans-serif, system-ui, sans-serif,
|
|
||||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
|
||||||
'Noto Color Emoji';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: hsl(0 0% 100%);
|
|
||||||
--foreground: hsl(0 0% 3.9%);
|
|
||||||
--card: hsl(0 0% 100%);
|
|
||||||
--card-foreground: hsl(0 0% 3.9%);
|
|
||||||
--popover: hsl(0 0% 100%);
|
|
||||||
--popover-foreground: hsl(0 0% 3.9%);
|
|
||||||
--primary: hsl(0 0% 9%);
|
|
||||||
--primary-foreground: hsl(0 0% 98%);
|
|
||||||
--secondary: hsl(0 0% 92.1%);
|
|
||||||
--secondary-foreground: hsl(0 0% 9%);
|
|
||||||
--muted: hsl(0 0% 96.1%);
|
|
||||||
--muted-foreground: hsl(0 0% 45.1%);
|
|
||||||
--accent: hsl(0 0% 96.1%);
|
|
||||||
--accent-foreground: hsl(0 0% 9%);
|
|
||||||
--destructive: hsl(0 84.2% 60.2%);
|
|
||||||
--destructive-foreground: hsl(0 0% 98%);
|
|
||||||
--border: hsl(0 0% 92.8%);
|
|
||||||
--input: hsl(0 0% 89.8%);
|
|
||||||
--ring: hsl(0 0% 3.9%);
|
|
||||||
--chart-1: hsl(12 76% 61%);
|
|
||||||
--chart-2: hsl(173 58% 39%);
|
|
||||||
--chart-3: hsl(197 37% 24%);
|
|
||||||
--chart-4: hsl(43 74% 66%);
|
|
||||||
--chart-5: hsl(27 87% 67%);
|
|
||||||
--radius: 0.5rem;
|
|
||||||
--sidebar-background: hsl(0 0% 98%);
|
|
||||||
--sidebar-foreground: hsl(240 5.3% 26.1%);
|
|
||||||
--sidebar-primary: hsl(0 0% 10%);
|
|
||||||
--sidebar-primary-foreground: hsl(0 0% 98%);
|
|
||||||
--sidebar-accent: hsl(0 0% 94%);
|
|
||||||
--sidebar-accent-foreground: hsl(0 0% 30%);
|
|
||||||
--sidebar-border: hsl(0 0% 91%);
|
|
||||||
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
|
||||||
--sidebar: hsl(0 0% 98%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: hsl(0 0% 3.9%);
|
|
||||||
--foreground: hsl(0 0% 98%);
|
|
||||||
--card: hsl(0 0% 3.9%);
|
|
||||||
--card-foreground: hsl(0 0% 98%);
|
|
||||||
--popover: hsl(0 0% 3.9%);
|
|
||||||
--popover-foreground: hsl(0 0% 98%);
|
|
||||||
--primary: hsl(0 0% 98%);
|
|
||||||
--primary-foreground: hsl(0 0% 9%);
|
|
||||||
--secondary: hsl(0 0% 14.9%);
|
|
||||||
--secondary-foreground: hsl(0 0% 98%);
|
|
||||||
--muted: hsl(0 0% 16.08%);
|
|
||||||
--muted-foreground: hsl(0 0% 63.9%);
|
|
||||||
--accent: hsl(0 0% 14.9%);
|
|
||||||
--accent-foreground: hsl(0 0% 98%);
|
|
||||||
--destructive: hsl(0 84% 60%);
|
|
||||||
--destructive-foreground: hsl(0 0% 98%);
|
|
||||||
--border: hsl(0 0% 14.9%);
|
|
||||||
--input: hsl(0 0% 14.9%);
|
|
||||||
--ring: hsl(0 0% 83.1%);
|
|
||||||
--chart-1: hsl(220 70% 50%);
|
|
||||||
--chart-2: hsl(160 60% 45%);
|
|
||||||
--chart-3: hsl(30 80% 55%);
|
|
||||||
--chart-4: hsl(280 65% 60%);
|
|
||||||
--chart-5: hsl(340 75% 55%);
|
|
||||||
--sidebar-background: hsl(0 0% 7%);
|
|
||||||
--sidebar-foreground: hsl(0 0% 95.9%);
|
|
||||||
--sidebar-primary: hsl(360, 100%, 100%);
|
|
||||||
--sidebar-primary-foreground: hsl(0 0% 100%);
|
|
||||||
--sidebar-accent: hsl(0 0% 15.9%);
|
|
||||||
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
|
|
||||||
--sidebar-border: hsl(0 0% 15.9%);
|
|
||||||
--sidebar-ring: hsl(217.2 91.2% 59.8%);
|
|
||||||
--sidebar: hsl(240 5.9% 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background font-sans text-body text-foreground antialiased;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--ring);
|
||||||
|
outline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: var(--brand);
|
||||||
|
color: var(--brand-foreground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preserve completion events while removing visible nonessential motion. */
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
scroll-behavior: auto !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/* Fluid Emerald / Kinetic Kanban: shared by the first paint and the app bundle. */
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
--radius: 0.25rem;
|
||||||
|
--background: #f7f9fb;
|
||||||
|
--foreground: #191c1e;
|
||||||
|
--card: #ffffff;
|
||||||
|
--card-foreground: #191c1e;
|
||||||
|
--popover: #ffffff;
|
||||||
|
--popover-foreground: #191c1e;
|
||||||
|
--primary: #006e2f;
|
||||||
|
--primary-foreground: #ffffff;
|
||||||
|
--secondary: #d0e1fb;
|
||||||
|
--secondary-foreground: #38485d;
|
||||||
|
--muted: #f2f4f6;
|
||||||
|
--muted-foreground: #505f76;
|
||||||
|
--accent: #dcfce7;
|
||||||
|
--accent-foreground: #005321;
|
||||||
|
--destructive: #ba1a1a;
|
||||||
|
--destructive-foreground: #ffffff;
|
||||||
|
--border: #e2e8f0;
|
||||||
|
--input: #6d7b6c;
|
||||||
|
--ring: #006e2f;
|
||||||
|
--brand: #22c55e;
|
||||||
|
--brand-foreground: #002109;
|
||||||
|
--success: #dcfce7;
|
||||||
|
--success-foreground: #005321;
|
||||||
|
--warning: #fef3c7;
|
||||||
|
--warning-foreground: #78350f;
|
||||||
|
--info: #dbeafe;
|
||||||
|
--info-foreground: #1e40af;
|
||||||
|
--chart-1: #006e2f;
|
||||||
|
--chart-2: #505f76;
|
||||||
|
--chart-3: #9e4036;
|
||||||
|
--chart-4: #92400e;
|
||||||
|
--chart-5: #1d4ed8;
|
||||||
|
--sidebar-background: #f2f4f6;
|
||||||
|
--sidebar-foreground: #191c1e;
|
||||||
|
--sidebar-primary: #006e2f;
|
||||||
|
--sidebar-primary-foreground: #ffffff;
|
||||||
|
--sidebar-accent: #dcfce7;
|
||||||
|
--sidebar-accent-foreground: #005321;
|
||||||
|
--sidebar-border: #e2e8f0;
|
||||||
|
--sidebar-ring: #006e2f;
|
||||||
|
--sidebar: #f2f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
--background: #0f172a;
|
||||||
|
--foreground: #f1f5f9;
|
||||||
|
--card: #1e293b;
|
||||||
|
--card-foreground: #f1f5f9;
|
||||||
|
--popover: #1e293b;
|
||||||
|
--popover-foreground: #f1f5f9;
|
||||||
|
--primary: #4ae176;
|
||||||
|
--primary-foreground: #002109;
|
||||||
|
--secondary: #334155;
|
||||||
|
--secondary-foreground: #e2e8f0;
|
||||||
|
--muted: #1e293b;
|
||||||
|
--muted-foreground: #b7c8e1;
|
||||||
|
--accent: #173b2b;
|
||||||
|
--accent-foreground: #6bff8f;
|
||||||
|
--destructive: #fca5a5;
|
||||||
|
--destructive-foreground: #450a0a;
|
||||||
|
--border: #334155;
|
||||||
|
--input: #8293aa;
|
||||||
|
--ring: #4ae176;
|
||||||
|
--brand: #22c55e;
|
||||||
|
--brand-foreground: #002109;
|
||||||
|
--success: #173b2b;
|
||||||
|
--success-foreground: #6bff8f;
|
||||||
|
--warning: #422006;
|
||||||
|
--warning-foreground: #fde68a;
|
||||||
|
--info: #172554;
|
||||||
|
--info-foreground: #bfdbfe;
|
||||||
|
--chart-1: #4ae176;
|
||||||
|
--chart-2: #b7c8e1;
|
||||||
|
--chart-3: #ffb4a9;
|
||||||
|
--chart-4: #fbbf24;
|
||||||
|
--chart-5: #60a5fa;
|
||||||
|
--sidebar-background: #111c30;
|
||||||
|
--sidebar-foreground: #f1f5f9;
|
||||||
|
--sidebar-primary: #4ae176;
|
||||||
|
--sidebar-primary-foreground: #002109;
|
||||||
|
--sidebar-accent: #173b2b;
|
||||||
|
--sidebar-accent-foreground: #6bff8f;
|
||||||
|
--sidebar-border: #334155;
|
||||||
|
--sidebar-ring: #4ae176;
|
||||||
|
--sidebar: #111c30;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
+5
-5
@@ -3,7 +3,8 @@ import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
|||||||
import type { DefineComponent } from 'vue';
|
import type { DefineComponent } from 'vue';
|
||||||
import { createApp, h } from 'vue';
|
import { createApp, h } from 'vue';
|
||||||
import '../css/app.css';
|
import '../css/app.css';
|
||||||
import { initializeTheme } from '@/composables/useAppearance';
|
import { createAppearance } from '@/composables/useAppearance';
|
||||||
|
import { createLocalization } from '@/lib/localization';
|
||||||
|
|
||||||
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
|
||||||
|
|
||||||
@@ -17,12 +18,11 @@ createInertiaApp({
|
|||||||
setup({ el, App, props, plugin }) {
|
setup({ el, App, props, plugin }) {
|
||||||
createApp({ render: () => h(App, props) })
|
createApp({ render: () => h(App, props) })
|
||||||
.use(plugin)
|
.use(plugin)
|
||||||
|
.use(createLocalization(props.initialPage.props.localization))
|
||||||
|
.use(createAppearance(props.initialPage.props.appearance))
|
||||||
.mount(el);
|
.mount(el);
|
||||||
},
|
},
|
||||||
progress: {
|
progress: {
|
||||||
color: '#4B5563',
|
color: 'var(--primary)',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// This will set light / dark mode on page load...
|
|
||||||
initializeTheme();
|
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
import { AlertCircle } from 'lucide-vue-next';
|
import { AlertCircle } from 'lucide-vue-next';
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
errors: string[];
|
errors: string[];
|
||||||
title?: string;
|
title?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = defineProps<Props>();
|
||||||
title: 'Something went wrong.',
|
const { t } = useLocalization();
|
||||||
});
|
|
||||||
|
|
||||||
const uniqueErrors = computed(() => Array.from(new Set(props.errors)));
|
const uniqueErrors = computed(() => Array.from(new Set(props.errors)));
|
||||||
</script>
|
</script>
|
||||||
@@ -18,7 +18,7 @@ const uniqueErrors = computed(() => Array.from(new Set(props.errors)));
|
|||||||
<template>
|
<template>
|
||||||
<Alert variant="destructive">
|
<Alert variant="destructive">
|
||||||
<AlertCircle class="size-4" />
|
<AlertCircle class="size-4" />
|
||||||
<AlertTitle>{{ title }}</AlertTitle>
|
<AlertTitle>{{ title ?? t('common.error') }}</AlertTitle>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<ul class="list-inside list-disc text-sm">
|
<ul class="list-inside list-disc text-sm">
|
||||||
<li v-for="(error, index) in uniqueErrors" :key="index">
|
<li v-for="(error, index) in uniqueErrors" :key="index">
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AppLogoIcon from '@/components/AppLogoIcon.vue';
|
import { Columns3 } from 'lucide-vue-next';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<span class="flex min-w-0 items-center gap-3">
|
||||||
class="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-md"
|
<span
|
||||||
>
|
class="bg-brand text-brand-foreground flex size-8 shrink-0 items-center justify-center rounded-md"
|
||||||
<AppLogoIcon class="size-5 fill-current text-white dark:text-black" />
|
|
||||||
</div>
|
|
||||||
<div class="ml-1 grid flex-1 text-left text-sm">
|
|
||||||
<span class="mb-0.5 truncate font-semibold leading-tight"
|
|
||||||
>Laravel Starter Kit</span
|
|
||||||
>
|
>
|
||||||
</div>
|
<Columns3 class="size-5" aria-hidden="true" />
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="text-title truncate font-semibold group-data-[collapsible=icon]:hidden"
|
||||||
|
>KanbanGit</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Link, usePage } from '@inertiajs/vue3';
|
import { Link, router } from '@inertiajs/vue3';
|
||||||
import {
|
import { Settings2, X } from 'lucide-vue-next';
|
||||||
BookOpen,
|
import { computed, onMounted, onUnmounted } from 'vue';
|
||||||
Folder,
|
|
||||||
FolderGit2,
|
|
||||||
LayoutGrid,
|
|
||||||
Layers3,
|
|
||||||
Shield,
|
|
||||||
Users,
|
|
||||||
} from 'lucide-vue-next';
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import AppLogo from '@/components/AppLogo.vue';
|
import AppLogo from '@/components/AppLogo.vue';
|
||||||
import NavFooter from '@/components/NavFooter.vue';
|
|
||||||
import NavMain from '@/components/NavMain.vue';
|
import NavMain from '@/components/NavMain.vue';
|
||||||
import NavUser from '@/components/NavUser.vue';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@@ -22,93 +13,98 @@ import {
|
|||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
|
useSidebar,
|
||||||
} from '@/components/ui/sidebar';
|
} from '@/components/ui/sidebar';
|
||||||
|
import { useAppNavigation } from '@/composables/useAppNavigation';
|
||||||
|
import { useCurrentUrl } from '@/composables/useCurrentUrl';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
import { dashboard } from '@/routes';
|
import { dashboard } from '@/routes';
|
||||||
import { index as adminUsers } from '@/routes/admin/users';
|
import { edit as profile } from '@/routes/profile';
|
||||||
import { index as projectTemplatesIndex } from '@/routes/project-templates';
|
import { show } from '@/routes/projects';
|
||||||
import { index as projectsIndex } from '@/routes/projects';
|
|
||||||
import { index as teamsIndex } from '@/routes/teams';
|
|
||||||
import type { NavItem } from '@/types';
|
|
||||||
|
|
||||||
const page = usePage();
|
const { t } = useLocalization();
|
||||||
|
const { currentUrl } = useCurrentUrl();
|
||||||
const mainNavItems = computed<NavItem[]>(() => {
|
const { project, workspaceItems, projectItems, projectSettingsItems } =
|
||||||
const items: NavItem[] = [
|
useAppNavigation();
|
||||||
{
|
const { isMobile, setOpenMobile } = useSidebar();
|
||||||
title: 'Dashboard',
|
const accountItems = computed(() => [
|
||||||
href: dashboard(),
|
{
|
||||||
icon: LayoutGrid,
|
title: t('navigation.settings'),
|
||||||
},
|
href: profile(),
|
||||||
{
|
icon: Settings2,
|
||||||
title: 'Projects',
|
isActive: currentUrl.value.startsWith('/settings/'),
|
||||||
href: projectsIndex(),
|
},
|
||||||
icon: Folder,
|
]);
|
||||||
},
|
let removeNavigationListener: (() => void) | undefined;
|
||||||
];
|
onMounted(() => {
|
||||||
|
removeNavigationListener = router.on('navigate', () =>
|
||||||
if (page.props.auth.permissions.projectCreate) {
|
setOpenMobile(false),
|
||||||
items.push({
|
);
|
||||||
title: 'Project templates',
|
|
||||||
href: projectTemplatesIndex(),
|
|
||||||
icon: Layers3,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page.props.auth.permissions.globalAdmin) {
|
|
||||||
items.push({
|
|
||||||
title: 'Users',
|
|
||||||
href: adminUsers(),
|
|
||||||
icon: Shield,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page.props.auth.permissions.canManageTeams) {
|
|
||||||
items.push({
|
|
||||||
title: 'Teams',
|
|
||||||
href: teamsIndex(),
|
|
||||||
icon: Users,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
});
|
});
|
||||||
|
onUnmounted(() => removeNavigationListener?.());
|
||||||
const footerNavItems: NavItem[] = [
|
|
||||||
{
|
|
||||||
title: 'Repository',
|
|
||||||
href: 'https://github.com/laravel/vue-starter-kit',
|
|
||||||
icon: FolderGit2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Documentation',
|
|
||||||
href: 'https://laravel.com/docs/starter-kits#vue',
|
|
||||||
icon: BookOpen,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Sidebar collapsible="icon" variant="inset">
|
<Sidebar id="app-navigation" collapsible="icon" variant="sidebar">
|
||||||
<SidebarHeader>
|
<SidebarHeader class="border-sidebar-border gap-4 border-b p-3">
|
||||||
<SidebarMenu>
|
<div class="flex items-center gap-2">
|
||||||
<SidebarMenuItem>
|
<SidebarMenu class="min-w-0">
|
||||||
<SidebarMenuButton size="lg" as-child>
|
<SidebarMenuItem>
|
||||||
<Link :href="dashboard()">
|
<SidebarMenuButton
|
||||||
<AppLogo />
|
size="lg"
|
||||||
</Link>
|
as-child
|
||||||
</SidebarMenuButton>
|
tooltip="KanbanGit"
|
||||||
</SidebarMenuItem>
|
>
|
||||||
</SidebarMenu>
|
<Link :href="dashboard()" aria-label="KanbanGit"
|
||||||
|
><AppLogo
|
||||||
|
/></Link>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
</SidebarMenu>
|
||||||
|
<Button
|
||||||
|
v-if="isMobile"
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
:aria-label="t('navigation.close')"
|
||||||
|
@click="setOpenMobile(false)"
|
||||||
|
>
|
||||||
|
<X aria-hidden="true" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Link
|
||||||
|
v-if="project"
|
||||||
|
:href="show(project.id)"
|
||||||
|
class="bg-sidebar-accent flex min-w-0 items-center gap-3 rounded-md p-3 group-data-[collapsible=icon]:hidden"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="text-label bg-brand/15 text-primary shrink-0 rounded-md px-2 py-1"
|
||||||
|
>{{ project.key }}</span
|
||||||
|
>
|
||||||
|
<span class="truncate font-semibold" :title="project.name">{{
|
||||||
|
project.name
|
||||||
|
}}</span>
|
||||||
|
</Link>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<NavMain :items="mainNavItems" />
|
<nav :aria-label="t('navigation.main')">
|
||||||
|
<NavMain
|
||||||
|
:items="workspaceItems"
|
||||||
|
:label="t('navigation.workspace')"
|
||||||
|
/>
|
||||||
|
<NavMain
|
||||||
|
v-if="projectItems.length"
|
||||||
|
:items="projectItems"
|
||||||
|
:label="t('navigation.project')"
|
||||||
|
/>
|
||||||
|
<NavMain
|
||||||
|
v-if="projectSettingsItems.length"
|
||||||
|
:items="projectSettingsItems"
|
||||||
|
:label="t('navigation.projectSettings')"
|
||||||
|
/>
|
||||||
|
</nav>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
<SidebarFooter class="border-sidebar-border border-t p-0">
|
||||||
<SidebarFooter>
|
<NavMain :items="accountItems" :label="t('navigation.account')" />
|
||||||
<NavFooter :items="footerNavItems" />
|
|
||||||
<NavUser />
|
|
||||||
</SidebarFooter>
|
</SidebarFooter>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
<slot />
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,27 +1,145 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { Link, usePage } from '@inertiajs/vue3';
|
||||||
|
import { Monitor, Moon, Plus, Sun } from 'lucide-vue-next';
|
||||||
|
import { computed } from 'vue';
|
||||||
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
import Breadcrumbs from '@/components/Breadcrumbs.vue';
|
||||||
|
import UserMenuContent from '@/components/UserMenuContent.vue';
|
||||||
|
import { UserAvatar } from '@/components/ui/avatar';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
|
import { useAppNavigation } from '@/composables/useAppNavigation';
|
||||||
|
import {
|
||||||
|
normalizeAppearance,
|
||||||
|
useAppearance,
|
||||||
|
} from '@/composables/useAppearance';
|
||||||
|
import { useCurrentUrl } from '@/composables/useCurrentUrl';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
import { create as createProject } from '@/routes/projects';
|
||||||
|
import { index as board } from '@/routes/projects/board';
|
||||||
|
import { create as createTicket } from '@/routes/projects/tickets';
|
||||||
import type { BreadcrumbItem } from '@/types';
|
import type { BreadcrumbItem } from '@/types';
|
||||||
|
|
||||||
withDefaults(
|
withDefaults(defineProps<{ breadcrumbs?: BreadcrumbItem[] }>(), {
|
||||||
defineProps<{
|
breadcrumbs: () => [],
|
||||||
breadcrumbs?: BreadcrumbItem[];
|
});
|
||||||
}>(),
|
const page = usePage();
|
||||||
{
|
const { project } = useAppNavigation();
|
||||||
breadcrumbs: () => [],
|
const { isCurrentUrl } = useCurrentUrl();
|
||||||
},
|
const { t } = useLocalization();
|
||||||
|
const { appearance, updateAppearance } = useAppearance();
|
||||||
|
const themeIcon = computed(
|
||||||
|
() => ({ light: Sun, dark: Moon, system: Monitor })[appearance.value],
|
||||||
);
|
);
|
||||||
|
const createAction = computed(() => {
|
||||||
|
if (project.value?.canCreateTickets) {
|
||||||
|
return {
|
||||||
|
title: t('navigation.createTicket'),
|
||||||
|
href: createTicket(project.value.id, {
|
||||||
|
query: {
|
||||||
|
source: isCurrentUrl(board(project.value.id))
|
||||||
|
? 'board'
|
||||||
|
: 'backlog',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!project.value && page.props.auth.permissions.projectCreate) {
|
||||||
|
return { title: t('navigation.createProject'), href: createProject() };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header
|
<header
|
||||||
class="border-sidebar-border/70 group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 flex h-16 shrink-0 items-center gap-2 border-b px-6 transition-[width,height] ease-linear md:px-4"
|
class="bg-background md:px-page sticky top-0 z-20 flex min-h-16 min-w-0 shrink-0 items-center justify-between gap-2 border-b px-3 md:gap-4"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||||
<SidebarTrigger class="-ml-1" />
|
<SidebarTrigger class="shrink-0" />
|
||||||
<template v-if="breadcrumbs && breadcrumbs.length > 0">
|
<Breadcrumbs v-if="breadcrumbs.length" :breadcrumbs="breadcrumbs" />
|
||||||
<Breadcrumbs :breadcrumbs="breadcrumbs" />
|
<span v-else class="truncate font-semibold">KanbanGit</span>
|
||||||
</template>
|
</div>
|
||||||
|
<div class="flex shrink-0 items-center gap-1 sm:gap-2">
|
||||||
|
<Button
|
||||||
|
v-if="createAction"
|
||||||
|
as-child
|
||||||
|
:aria-label="createAction.title"
|
||||||
|
class="px-3"
|
||||||
|
>
|
||||||
|
<Link :href="createAction.href"
|
||||||
|
><Plus aria-hidden="true" /><span
|
||||||
|
class="hidden sm:inline"
|
||||||
|
>{{ createAction.title }}</span
|
||||||
|
></Link
|
||||||
|
>
|
||||||
|
</Button>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
:aria-label="t('appearance.label')"
|
||||||
|
><component :is="themeIcon" aria-hidden="true"
|
||||||
|
/></Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuLabel>{{
|
||||||
|
t('appearance.label')
|
||||||
|
}}</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuRadioGroup
|
||||||
|
:model-value="appearance"
|
||||||
|
@update:model-value="
|
||||||
|
(value) =>
|
||||||
|
updateAppearance(normalizeAppearance(value))
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem value="light"
|
||||||
|
><Sun aria-hidden="true" />{{
|
||||||
|
t('appearance.light')
|
||||||
|
}}</DropdownMenuRadioItem
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem value="dark"
|
||||||
|
><Moon aria-hidden="true" />{{
|
||||||
|
t('appearance.dark')
|
||||||
|
}}</DropdownMenuRadioItem
|
||||||
|
>
|
||||||
|
<DropdownMenuRadioItem value="system"
|
||||||
|
><Monitor aria-hidden="true" />{{
|
||||||
|
t('appearance.system')
|
||||||
|
}}</DropdownMenuRadioItem
|
||||||
|
>
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
:aria-label="
|
||||||
|
t('navigation.userMenu', {
|
||||||
|
name: page.props.auth.user.name,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
data-test="sidebar-menu-button"
|
||||||
|
>
|
||||||
|
<UserAvatar :user="page.props.auth.user" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" class="min-w-60">
|
||||||
|
<UserMenuContent :user="page.props.auth.user" />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Monitor, Moon, Sun } from 'lucide-vue-next';
|
import { Monitor, Moon, Sun } from 'lucide-vue-next';
|
||||||
import { useAppearance } from '@/composables/useAppearance';
|
import { useAppearance } from '@/composables/useAppearance';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
|
||||||
const { appearance, updateAppearance } = useAppearance();
|
const { appearance, updateAppearance } = useAppearance();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ value: 'light', Icon: Sun, label: 'Light' },
|
{ value: 'light', Icon: Sun, label: 'appearance.light' },
|
||||||
{ value: 'dark', Icon: Moon, label: 'Dark' },
|
{ value: 'dark', Icon: Moon, label: 'appearance.dark' },
|
||||||
{ value: 'system', Icon: Monitor, label: 'System' },
|
{ value: 'system', Icon: Monitor, label: 'appearance.system' },
|
||||||
] as const;
|
] as const;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="inline-flex gap-1 rounded-lg bg-neutral-100 p-1 dark:bg-neutral-800"
|
role="group"
|
||||||
|
:aria-label="t('appearance.label')"
|
||||||
|
class="border-border bg-muted inline-flex max-w-full gap-1 rounded-lg border p-1"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-for="{ value, Icon, label } in tabs"
|
v-for="{ value, Icon, label } in tabs"
|
||||||
:key="value"
|
:key="value"
|
||||||
|
type="button"
|
||||||
|
:aria-pressed="appearance === value"
|
||||||
@click="updateAppearance(value)"
|
@click="updateAppearance(value)"
|
||||||
:class="[
|
:class="[
|
||||||
'flex items-center rounded-md px-3.5 py-1.5 transition-colors',
|
'min-h-control text-body focus-visible:outline-ring flex min-w-0 items-center justify-center gap-1.5 rounded-md px-3 font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||||
appearance === value
|
appearance === value
|
||||||
? 'shadow-xs bg-white dark:bg-neutral-700 dark:text-neutral-100'
|
? 'bg-primary text-primary-foreground'
|
||||||
: 'text-neutral-500 hover:bg-neutral-200/60 hover:text-black dark:text-neutral-400 dark:hover:bg-neutral-700/60',
|
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<component :is="Icon" class="-ml-1 h-4 w-4" />
|
<component :is="Icon" class="size-4 shrink-0" aria-hidden="true" />
|
||||||
<span class="ml-1.5 text-sm">{{ label }}</span>
|
<span>{{ t(label) }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import FormFeedback from '@/components/FormFeedback.vue';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
import type { FormFeedbackState } from '@/lib/form-feedback';
|
||||||
|
import { login } from '@/routes/index';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
state: FormFeedbackState;
|
||||||
|
responseStatus?: number | null;
|
||||||
|
loadingTitle?: string;
|
||||||
|
successTitle?: string;
|
||||||
|
}>();
|
||||||
|
defineEmits<{ retry: [] }>();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
const expired = computed(
|
||||||
|
() => props.state === 'server' && props.responseStatus === 419,
|
||||||
|
);
|
||||||
|
const limited = computed(
|
||||||
|
() => props.state === 'server' && props.responseStatus === 429,
|
||||||
|
);
|
||||||
|
const title = computed(() => {
|
||||||
|
if (expired.value) {
|
||||||
|
return t('auth.feedback.expiredTitle');
|
||||||
|
}
|
||||||
|
if (limited.value) {
|
||||||
|
return t('auth.feedback.limitedTitle');
|
||||||
|
}
|
||||||
|
if (props.state === 'loading') {
|
||||||
|
return props.loadingTitle ?? t('auth.feedback.pending');
|
||||||
|
}
|
||||||
|
if (props.state === 'success') {
|
||||||
|
return props.successTitle ?? t('auth.feedback.completed');
|
||||||
|
}
|
||||||
|
if (props.state === 'validation') {
|
||||||
|
return t('auth.feedback.validationTitle');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
const description = computed(() => {
|
||||||
|
if (expired.value) {
|
||||||
|
return t('auth.feedback.expiredDescription');
|
||||||
|
}
|
||||||
|
if (limited.value) {
|
||||||
|
return t('auth.feedback.limitedDescription');
|
||||||
|
}
|
||||||
|
if (props.state === 'validation') {
|
||||||
|
return t('auth.feedback.validationDescription');
|
||||||
|
}
|
||||||
|
if (props.state === 'network') {
|
||||||
|
return t('auth.feedback.networkDescription');
|
||||||
|
}
|
||||||
|
if (props.state === 'server') {
|
||||||
|
return t('auth.feedback.serverDescription');
|
||||||
|
}
|
||||||
|
if (props.state === 'cancelled') {
|
||||||
|
return t('auth.feedback.cancelledDescription');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="gap-card grid" v-show="state !== 'idle'">
|
||||||
|
<FormFeedback
|
||||||
|
:state="state"
|
||||||
|
:title="title"
|
||||||
|
:description="description"
|
||||||
|
:retryable="!expired"
|
||||||
|
@retry="$emit('retry')"
|
||||||
|
/>
|
||||||
|
<Button v-if="expired" as-child variant="outline">
|
||||||
|
<!-- A fresh document renews the session/CSRF state and drops in-memory secrets. -->
|
||||||
|
<a :href="login.url()">{{ t('auth.backToLogin') }}</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -9,29 +9,48 @@ import {
|
|||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from '@/components/ui/breadcrumb';
|
} from '@/components/ui/breadcrumb';
|
||||||
import type { BreadcrumbItem as BreadcrumbItemType } from '@/types';
|
import type { BreadcrumbItem as BreadcrumbItemType } from '@/types';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
breadcrumbs: BreadcrumbItemType[];
|
breadcrumbs: BreadcrumbItemType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
const { t } = useLocalization();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Breadcrumb>
|
<Breadcrumb class="min-w-0" :aria-label="t('navigation.breadcrumbs')">
|
||||||
<BreadcrumbList>
|
<BreadcrumbList class="flex-nowrap">
|
||||||
<template v-for="(item, index) in breadcrumbs" :key="index">
|
<template v-for="(item, index) in breadcrumbs" :key="index">
|
||||||
<BreadcrumbItem>
|
<BreadcrumbItem
|
||||||
|
:class="[
|
||||||
|
'min-w-0',
|
||||||
|
index !== breadcrumbs.length - 1
|
||||||
|
? 'hidden md:inline-flex'
|
||||||
|
: '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
<template v-if="index === breadcrumbs.length - 1">
|
<template v-if="index === breadcrumbs.length - 1">
|
||||||
<BreadcrumbPage>{{ item.title }}</BreadcrumbPage>
|
<BreadcrumbPage class="truncate" :title="item.title">{{
|
||||||
|
item.title
|
||||||
|
}}</BreadcrumbPage>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<BreadcrumbLink as-child>
|
<BreadcrumbLink as-child>
|
||||||
<Link :href="item.href">{{ item.title }}</Link>
|
<Link
|
||||||
|
:href="item.href"
|
||||||
|
class="max-w-40 truncate"
|
||||||
|
:title="item.title"
|
||||||
|
>{{ item.title }}</Link
|
||||||
|
>
|
||||||
</BreadcrumbLink>
|
</BreadcrumbLink>
|
||||||
</template>
|
</template>
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
<BreadcrumbSeparator v-if="index !== breadcrumbs.length - 1" />
|
<BreadcrumbSeparator
|
||||||
|
v-if="index !== breadcrumbs.length - 1"
|
||||||
|
class="hidden md:block"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
</BreadcrumbList>
|
</BreadcrumbList>
|
||||||
</Breadcrumb>
|
</Breadcrumb>
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { CircleAlert, CircleCheck, Inbox, LoaderCircle } from 'lucide-vue-next';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
state: 'empty' | 'loading' | 'error' | 'success';
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
retryable?: boolean;
|
||||||
|
}>(),
|
||||||
|
{ retryable: false },
|
||||||
|
);
|
||||||
|
defineEmits<{ retry: [] }>();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
const icons = {
|
||||||
|
empty: Inbox,
|
||||||
|
loading: LoaderCircle,
|
||||||
|
error: CircleAlert,
|
||||||
|
success: CircleCheck,
|
||||||
|
};
|
||||||
|
const defaultTitles = {
|
||||||
|
empty: 'feedback.empty',
|
||||||
|
loading: 'common.loading',
|
||||||
|
error: 'common.error',
|
||||||
|
success: 'feedback.saved',
|
||||||
|
} as const;
|
||||||
|
const heading = computed(() => props.title ?? t(defaultTitles[props.state]));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
:role="state === 'error' ? 'alert' : 'status'"
|
||||||
|
:aria-busy="state === 'loading' || undefined"
|
||||||
|
:data-feedback="state"
|
||||||
|
class="gap-card bg-card p-card text-body text-card-foreground flex min-w-0 items-start rounded-md border"
|
||||||
|
:class="{
|
||||||
|
'border-destructive/40': state === 'error',
|
||||||
|
'border-success/40': state === 'success',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="icons[state]"
|
||||||
|
aria-hidden="true"
|
||||||
|
class="mt-0.5 size-5 shrink-0"
|
||||||
|
:class="{
|
||||||
|
'text-primary animate-spin motion-reduce:animate-none':
|
||||||
|
state === 'loading',
|
||||||
|
'text-destructive': state === 'error',
|
||||||
|
'text-success-foreground': state === 'success',
|
||||||
|
'text-muted-foreground': state === 'empty',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<div class="gap-stack grid min-w-0 flex-1 break-words">
|
||||||
|
<p class="font-semibold">{{ heading }}</p>
|
||||||
|
<p v-if="description" class="text-muted-foreground">
|
||||||
|
{{ description }}
|
||||||
|
</p>
|
||||||
|
<div v-if="$slots.default"><slot /></div>
|
||||||
|
<Button
|
||||||
|
v-if="retryable && state === 'error'"
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
class="justify-self-start"
|
||||||
|
@click="$emit('retry')"
|
||||||
|
>{{ t('common.retry') }}</Button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, nextTick, useTemplateRef, watch } from 'vue';
|
||||||
|
import FeedbackState from '@/components/FeedbackState.vue';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
import type { FormFeedbackState } from '@/lib/form-feedback';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
state: FormFeedbackState;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
retryable?: boolean;
|
||||||
|
}>(),
|
||||||
|
{ retryable: true },
|
||||||
|
);
|
||||||
|
defineEmits<{ retry: [] }>();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
const feedback = useTemplateRef<HTMLDivElement>('feedback');
|
||||||
|
watch(
|
||||||
|
() => props.state,
|
||||||
|
async (state) => {
|
||||||
|
if (['validation', 'network', 'server', 'cancelled'].includes(state)) {
|
||||||
|
await nextTick();
|
||||||
|
if (props.state === state) {
|
||||||
|
feedback.value?.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const titles = {
|
||||||
|
idle: 'feedback.saved',
|
||||||
|
loading: 'forms.saving',
|
||||||
|
success: 'feedback.saved',
|
||||||
|
validation: 'forms.validationTitle',
|
||||||
|
network: 'forms.networkTitle',
|
||||||
|
server: 'forms.serverTitle',
|
||||||
|
cancelled: 'forms.cancelledTitle',
|
||||||
|
} as const;
|
||||||
|
const descriptions = {
|
||||||
|
validation: 'forms.validationDescription',
|
||||||
|
network: 'forms.networkDescription',
|
||||||
|
server: 'forms.serverDescription',
|
||||||
|
cancelled: 'forms.cancelledDescription',
|
||||||
|
} as const;
|
||||||
|
const description = computed(() =>
|
||||||
|
props.state in descriptions
|
||||||
|
? t(descriptions[props.state as keyof typeof descriptions])
|
||||||
|
: undefined,
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
ref="feedback"
|
||||||
|
v-show="state !== 'idle'"
|
||||||
|
:data-form-state="state"
|
||||||
|
tabindex="-1"
|
||||||
|
class="focus-visible:ring-ring/35 rounded-md outline-none focus-visible:ring-2"
|
||||||
|
>
|
||||||
|
<FeedbackState
|
||||||
|
v-if="state !== 'idle'"
|
||||||
|
:state="
|
||||||
|
state === 'loading' || state === 'success' ? state : 'error'
|
||||||
|
"
|
||||||
|
:title="props.title ?? t(titles[state])"
|
||||||
|
:description="props.description ?? description"
|
||||||
|
:retryable="
|
||||||
|
props.retryable !== false &&
|
||||||
|
(state === 'network' ||
|
||||||
|
state === 'server' ||
|
||||||
|
state === 'cancelled')
|
||||||
|
"
|
||||||
|
@retry="$emit('retry')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -5,8 +5,8 @@ defineProps<{
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-show="message">
|
<div v-show="message" role="alert">
|
||||||
<p class="text-sm text-red-600 dark:text-red-500">
|
<p class="text-body text-destructive">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,23 +12,46 @@ import type { NavItem } from '@/types';
|
|||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
items: NavItem[];
|
items: NavItem[];
|
||||||
|
label: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { isCurrentUrl } = useCurrentUrl();
|
const { isCurrentUrl } = useCurrentUrl();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SidebarGroup class="px-2 py-0">
|
<SidebarGroup class="px-3 py-2">
|
||||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
<SidebarGroupLabel class="text-label text-muted-foreground uppercase">{{
|
||||||
<SidebarMenu>
|
label
|
||||||
|
}}</SidebarGroupLabel>
|
||||||
|
<SidebarMenu role="list" :aria-label="label">
|
||||||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
as-child
|
as-child
|
||||||
:is-active="isCurrentUrl(item.href)"
|
:is-active="
|
||||||
|
item.isActive ??
|
||||||
|
isCurrentUrl(
|
||||||
|
item.href,
|
||||||
|
undefined,
|
||||||
|
item.match === 'section',
|
||||||
|
)
|
||||||
|
"
|
||||||
:tooltip="item.title"
|
:tooltip="item.title"
|
||||||
|
class="min-h-control data-[active=true]:border-brand border-l-2 border-transparent px-3 data-[active=true]:font-semibold group-data-[collapsible=icon]:min-h-8"
|
||||||
>
|
>
|
||||||
<Link :href="item.href">
|
<Link
|
||||||
<component :is="item.icon" />
|
:href="item.href"
|
||||||
|
:aria-current="
|
||||||
|
(item.isActive ??
|
||||||
|
isCurrentUrl(
|
||||||
|
item.href,
|
||||||
|
undefined,
|
||||||
|
item.match === 'section',
|
||||||
|
))
|
||||||
|
? 'page'
|
||||||
|
: undefined
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<component :is="item.icon" aria-hidden="true" />
|
||||||
<span>{{ item.title }}</span>
|
<span>{{ item.title }}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { Eye, EyeOff } from 'lucide-vue-next';
|
import { Eye, EyeOff } from 'lucide-vue-next';
|
||||||
import { ref, useTemplateRef } from 'vue';
|
import { ref, useTemplateRef } from 'vue';
|
||||||
import type { HTMLAttributes } from 'vue';
|
import type { HTMLAttributes } from 'vue';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
@@ -9,12 +10,14 @@ defineOptions({ inheritAttrs: false });
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
class?: HTMLAttributes['class'];
|
class?: HTMLAttributes['class'];
|
||||||
|
disabled?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const model = defineModel<string | number | null>();
|
const model = defineModel<string | number | null>();
|
||||||
|
|
||||||
const showPassword = ref(false);
|
const showPassword = ref(false);
|
||||||
const inputRef = useTemplateRef('inputRef');
|
const inputRef = useTemplateRef('inputRef');
|
||||||
|
const { t } = useLocalization();
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
$el: inputRef,
|
$el: inputRef,
|
||||||
@@ -28,7 +31,8 @@ defineExpose({
|
|||||||
ref="inputRef"
|
ref="inputRef"
|
||||||
v-model="model"
|
v-model="model"
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
:class="cn('pr-10', props.class)"
|
:class="cn('pr-12', props.class)"
|
||||||
|
:disabled="disabled"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
@@ -36,11 +40,14 @@ defineExpose({
|
|||||||
@click="showPassword = !showPassword"
|
@click="showPassword = !showPassword"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'text-muted-foreground hover:text-foreground focus-visible:ring-ring absolute inset-y-0 right-0 flex items-center rounded-r-md px-3 focus-visible:outline-none focus-visible:ring-[3px]',
|
'text-muted-foreground hover:text-foreground focus-visible:ring-ring w-control absolute inset-y-0 right-0 flex items-center justify-center rounded-r-md focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
:aria-label="showPassword ? 'Hide password' : 'Show password'"
|
:aria-label="
|
||||||
:tabindex="-1"
|
t(showPassword ? 'auth.password.hide' : 'auth.password.show')
|
||||||
|
"
|
||||||
|
:aria-pressed="showPassword"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
<EyeOff v-if="showPassword" class="size-4" />
|
<EyeOff v-if="showPassword" class="size-4" />
|
||||||
<Eye v-else class="size-4" />
|
<Eye v-else class="size-4" />
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
import { ChevronDown, Settings2 } from 'lucide-vue-next';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { useAppNavigation } from '@/composables/useAppNavigation';
|
||||||
|
import { useCurrentUrl } from '@/composables/useCurrentUrl';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
|
||||||
|
const { projectItems, projectSettingsItems } = useAppNavigation();
|
||||||
|
const { isCurrentUrl } = useCurrentUrl();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
const settingsActive = computed(() =>
|
||||||
|
projectSettingsItems.value.some((item) => isCurrentUrl(item.href)),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="projectItems.length"
|
||||||
|
class="bg-card md:px-page flex min-w-0 items-center gap-2 border-b px-4"
|
||||||
|
>
|
||||||
|
<nav
|
||||||
|
:aria-label="t('navigation.project')"
|
||||||
|
class="flex min-w-0 flex-1 gap-4 overflow-x-auto"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
v-for="item in projectItems"
|
||||||
|
:key="item.title"
|
||||||
|
:href="item.href"
|
||||||
|
:aria-current="isCurrentUrl(item.href) ? 'page' : undefined"
|
||||||
|
:class="[
|
||||||
|
'min-h-control text-body flex shrink-0 items-center gap-2 border-b-2 px-1 py-3 font-medium outline-offset-[-3px]',
|
||||||
|
isCurrentUrl(item.href)
|
||||||
|
? 'border-brand text-primary'
|
||||||
|
: 'text-muted-foreground hover:border-border hover:text-foreground border-transparent',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="item.icon"
|
||||||
|
class="size-4"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>{{ item.title }}
|
||||||
|
</Link>
|
||||||
|
</nav>
|
||||||
|
<DropdownMenu v-if="projectSettingsItems.length">
|
||||||
|
<DropdownMenuTrigger as-child>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
:aria-label="t('navigation.projectSettings')"
|
||||||
|
:class="{
|
||||||
|
'bg-accent text-accent-foreground': settingsActive,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<Settings2 aria-hidden="true" /><span
|
||||||
|
class="hidden sm:inline"
|
||||||
|
>{{ t('navigation.projectSettings') }}</span
|
||||||
|
><ChevronDown class="hidden sm:block" aria-hidden="true" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem
|
||||||
|
v-for="item in projectSettingsItems"
|
||||||
|
:key="item.title"
|
||||||
|
as-child
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
:href="item.href"
|
||||||
|
:aria-current="
|
||||||
|
isCurrentUrl(item.href) ? 'page' : undefined
|
||||||
|
"
|
||||||
|
:class="{ 'bg-accent': isCurrentUrl(item.href) }"
|
||||||
|
>
|
||||||
|
<component :is="item.icon" aria-hidden="true" />{{
|
||||||
|
item.title
|
||||||
|
}}
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -1,33 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue';
|
|
||||||
import { useInitials } from '@/composables/useInitials';
|
|
||||||
import type { User } from '@/types';
|
|
||||||
import UserAvatar from '@/components/ui/avatar/UserAvatar.vue';
|
import UserAvatar from '@/components/ui/avatar/UserAvatar.vue';
|
||||||
|
import type { User } from '@/types';
|
||||||
|
|
||||||
type Props = {
|
withDefaults(defineProps<{ user: User; showEmail?: boolean }>(), {
|
||||||
user: User;
|
|
||||||
showEmail?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
showEmail: false,
|
showEmail: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { getInitials } = useInitials();
|
|
||||||
|
|
||||||
// Compute whether we should show the avatar image
|
|
||||||
const showAvatar = computed(
|
|
||||||
() => props.user.avatar && props.user.avatar !== '',
|
|
||||||
);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<UserAvatar />
|
<div class="flex min-w-0 items-center gap-2">
|
||||||
|
<UserAvatar :user="user" />
|
||||||
<div class="grid flex-1 text-left text-sm leading-tight">
|
<div class="grid min-w-0 flex-1 text-left text-sm leading-tight">
|
||||||
<span class="truncate font-medium">{{ user.name }}</span>
|
<span class="truncate font-medium">{{ user.name }}</span>
|
||||||
<span v-if="showEmail" class="text-muted-foreground truncate text-xs">{{
|
<span
|
||||||
user.email
|
v-if="showEmail"
|
||||||
}}</span>
|
class="text-muted-foreground truncate text-xs"
|
||||||
|
>{{ user.email }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import UserInfo from '@/components/UserInfo.vue';
|
|||||||
import { logout } from '@/routes/index';
|
import { logout } from '@/routes/index';
|
||||||
import { edit } from '@/routes/profile/index';
|
import { edit } from '@/routes/profile/index';
|
||||||
import type { User } from '@/types';
|
import type { User } from '@/types';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -21,6 +22,7 @@ const handleLogout = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
defineProps<Props>();
|
defineProps<Props>();
|
||||||
|
const { t } = useLocalization();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -34,7 +36,7 @@ defineProps<Props>();
|
|||||||
<DropdownMenuItem :as-child="true">
|
<DropdownMenuItem :as-child="true">
|
||||||
<Link class="block w-full cursor-pointer" :href="edit()" prefetch>
|
<Link class="block w-full cursor-pointer" :href="edit()" prefetch>
|
||||||
<Settings class="mr-2 h-4 w-4" />
|
<Settings class="mr-2 h-4 w-4" />
|
||||||
Settings
|
{{ t('navigation.settings') }}
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
@@ -48,7 +50,7 @@ defineProps<Props>();
|
|||||||
data-test="logout-button"
|
data-test="logout-button"
|
||||||
>
|
>
|
||||||
<LogOut class="mr-2 h-4 w-4" />
|
<LogOut class="mr-2 h-4 w-4" />
|
||||||
Log out
|
{{ t('navigation.logout') }}
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
import { ArrowUpRight } from 'lucide-vue-next';
|
||||||
|
import TicketMetadata from '@/components/tickets/TicketMetadata.vue';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
import { show as showTicket } from '@/routes/projects/tickets';
|
||||||
|
import type {
|
||||||
|
TicketPresentationEmits,
|
||||||
|
TicketPresentationProps,
|
||||||
|
} from '@/types/ticket-presentation';
|
||||||
|
|
||||||
|
withDefaults(defineProps<TicketPresentationProps>(), {
|
||||||
|
showDescription: true,
|
||||||
|
showStatus: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<TicketPresentationEmits>();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<article
|
||||||
|
:class="[
|
||||||
|
'bg-card text-card-foreground min-w-0 rounded-md border shadow-sm',
|
||||||
|
ticket.is_blocked ? 'border-warning' : 'border-border',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
:href="showTicket({ project: projectId, ticket: ticket.id })"
|
||||||
|
:aria-label="
|
||||||
|
t('tickets.open', {
|
||||||
|
key: ticket.ticket_key,
|
||||||
|
title: ticket.title,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
class="focus-visible:ring-ring/35 gap-card min-h-control hover:bg-accent/35 group flex min-w-0 flex-col rounded-t-md p-4 transition-[background-color,box-shadow] focus-visible:outline-none focus-visible:ring-2"
|
||||||
|
@click="emit('open', ticket.id)"
|
||||||
|
>
|
||||||
|
<div class="flex min-w-0 items-start justify-between gap-3">
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
class="wrap-anywhere max-w-full whitespace-normal"
|
||||||
|
>
|
||||||
|
{{ ticket.ticket_key }}
|
||||||
|
</Badge>
|
||||||
|
<ArrowUpRight
|
||||||
|
class="text-muted-foreground group-hover:text-primary size-4 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3
|
||||||
|
class="text-body group-hover:text-primary wrap-anywhere min-w-0 font-semibold"
|
||||||
|
>
|
||||||
|
{{ ticket.title }}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<TicketMetadata :ticket="ticket" :show-status="showStatus" />
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="showDescription && ticket.description"
|
||||||
|
class="text-caption text-muted-foreground wrap-anywhere line-clamp-3"
|
||||||
|
>
|
||||||
|
{{ ticket.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="ticket.is_blocked && ticket.blocked_comment"
|
||||||
|
class="text-caption text-warning-foreground bg-warning wrap-anywhere rounded-sm px-3 py-2"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t('tickets.blockerComment', {
|
||||||
|
comment: ticket.blocked_comment,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div v-if="$slots.actions" class="border-t p-4">
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { Link } from '@inertiajs/vue3';
|
||||||
|
import { ArrowUpRight } from 'lucide-vue-next';
|
||||||
|
import TicketMetadata from '@/components/tickets/TicketMetadata.vue';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
import { show as showTicket } from '@/routes/projects/tickets';
|
||||||
|
import type {
|
||||||
|
TicketPresentationEmits,
|
||||||
|
TicketPresentationProps,
|
||||||
|
} from '@/types/ticket-presentation';
|
||||||
|
|
||||||
|
withDefaults(defineProps<TicketPresentationProps>(), {
|
||||||
|
showDescription: true,
|
||||||
|
showStatus: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits<TicketPresentationEmits>();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<li
|
||||||
|
:class="[
|
||||||
|
'bg-card text-card-foreground min-w-0 rounded-md border',
|
||||||
|
ticket.is_blocked ? 'border-warning' : 'border-border',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<div class="flex min-w-0 flex-col lg:flex-row lg:items-stretch">
|
||||||
|
<Link
|
||||||
|
:href="showTicket({ project: projectId, ticket: ticket.id })"
|
||||||
|
:aria-label="
|
||||||
|
t('tickets.open', {
|
||||||
|
key: ticket.ticket_key,
|
||||||
|
title: ticket.title,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
class="focus-visible:ring-ring/35 gap-card min-h-control hover:bg-accent/35 group flex min-w-0 flex-1 flex-col rounded-t-md p-4 transition-[background-color,box-shadow] focus-visible:outline-none focus-visible:ring-2 lg:rounded-l-md lg:rounded-tr-none"
|
||||||
|
@click="emit('open', ticket.id)"
|
||||||
|
>
|
||||||
|
<div class="flex min-w-0 items-start gap-3">
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
class="wrap-anywhere max-w-full whitespace-normal"
|
||||||
|
>
|
||||||
|
{{ ticket.ticket_key }}
|
||||||
|
</Badge>
|
||||||
|
<h3
|
||||||
|
class="text-body group-hover:text-primary wrap-anywhere min-w-0 flex-1 font-semibold"
|
||||||
|
>
|
||||||
|
{{ ticket.title }}
|
||||||
|
</h3>
|
||||||
|
<ArrowUpRight
|
||||||
|
class="text-muted-foreground group-hover:text-primary size-4 shrink-0"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<TicketMetadata :ticket="ticket" :show-status="showStatus" />
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="showDescription && ticket.description"
|
||||||
|
class="text-caption text-muted-foreground wrap-anywhere line-clamp-2"
|
||||||
|
>
|
||||||
|
{{ ticket.description }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="ticket.is_blocked && ticket.blocked_comment"
|
||||||
|
class="text-caption text-warning-foreground bg-warning wrap-anywhere rounded-sm px-3 py-2"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
t('tickets.blockerComment', {
|
||||||
|
comment: ticket.blocked_comment,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</p>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="$slots.actions"
|
||||||
|
class="border-t p-4 lg:flex lg:w-80 lg:items-center lg:border-l lg:border-t-0"
|
||||||
|
>
|
||||||
|
<slot name="actions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { CircleUserRound, OctagonAlert } from 'lucide-vue-next';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { UserAvatar } from '@/components/ui/avatar';
|
||||||
|
import { useLocalization } from '@/composables/useLocalization';
|
||||||
|
import { ticketStatusLabel } from '@/lib/tickets';
|
||||||
|
import type { TicketModel } from '@/types/models/ticket-model';
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
showStatus?: boolean;
|
||||||
|
ticket: TicketModel;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
showStatus: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const { t } = useLocalization();
|
||||||
|
const statusLabel = computed(() =>
|
||||||
|
ticketStatusLabel(props.ticket.ticket_status, t('tickets.noStatus')),
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="gap-stack flex min-w-0 flex-wrap items-center">
|
||||||
|
<Badge
|
||||||
|
v-if="ticket.is_blocked"
|
||||||
|
variant="warning"
|
||||||
|
:aria-label="t('tickets.blocked')"
|
||||||
|
>
|
||||||
|
<OctagonAlert aria-hidden="true" />
|
||||||
|
{{ t('tickets.blocked') }}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
:aria-label="
|
||||||
|
t('tickets.typeLabel', { name: ticket.ticket_type.name })
|
||||||
|
"
|
||||||
|
:data-ticket-type-id="ticket.ticket_type.id"
|
||||||
|
>
|
||||||
|
{{ ticket.ticket_type.name }}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
:aria-label="
|
||||||
|
t('tickets.priorityLabel', {
|
||||||
|
name: ticket.ticket_priority.name,
|
||||||
|
})
|
||||||
|
"
|
||||||
|
:data-ticket-priority-id="ticket.ticket_priority.id"
|
||||||
|
>
|
||||||
|
{{ ticket.ticket_priority.name }}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<Badge
|
||||||
|
v-if="showStatus"
|
||||||
|
variant="outline"
|
||||||
|
:aria-label="t('tickets.statusLabel', { name: statusLabel })"
|
||||||
|
:data-ticket-status-id="ticket.ticket_status?.id"
|
||||||
|
>
|
||||||
|
{{ statusLabel }}
|
||||||
|
</Badge>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="ticket.assignee"
|
||||||
|
class="text-caption text-muted-foreground flex min-w-0 items-center gap-1.5"
|
||||||
|
:aria-label="
|
||||||
|
t('tickets.assignedTo', { name: ticket.assignee.name })
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<UserAvatar
|
||||||
|
:user="ticket.assignee"
|
||||||
|
size="sm"
|
||||||
|
:label="ticket.assignee.name"
|
||||||
|
/>
|
||||||
|
<span class="max-w-40 truncate">{{ ticket.assignee.name }}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="text-caption text-muted-foreground flex items-center gap-1.5"
|
||||||
|
>
|
||||||
|
<CircleUserRound class="size-4" aria-hidden="true" />
|
||||||
|
{{ t('tickets.unassigned') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as TicketCard } from './TicketCard.vue';
|
||||||
|
export { default as TicketListItem } from './TicketListItem.vue';
|
||||||
@@ -1,18 +1,35 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { AvatarRootProps } from "reka-ui";
|
||||||
import { AvatarRoot } from "reka-ui"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { cn } from "@/lib/utils"
|
import type { AvatarVariants } from ".";
|
||||||
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
|
import { AvatarRoot, useForwardProps } from "reka-ui";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { avatarVariants } from ".";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = withDefaults(
|
||||||
class?: HTMLAttributes["class"]
|
defineProps<
|
||||||
}>()
|
AvatarRootProps & {
|
||||||
|
class?: HTMLAttributes["class"];
|
||||||
|
size?: AvatarVariants["size"];
|
||||||
|
shape?: AvatarVariants["shape"];
|
||||||
|
}
|
||||||
|
>(),
|
||||||
|
{ size: "default", shape: "circle" },
|
||||||
|
);
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class", "size", "shape");
|
||||||
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AvatarRoot
|
<AvatarRoot
|
||||||
data-slot="avatar"
|
data-slot="avatar"
|
||||||
:class="cn('relative flex size-8 shrink-0 overflow-hidden rounded-full', props.class)"
|
:data-size="size"
|
||||||
>
|
:data-shape="shape"
|
||||||
<slot />
|
v-bind="forwardedProps"
|
||||||
</AvatarRoot>
|
:class="cn(avatarVariants({ size, shape }), props.class)"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AvatarRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AvatarFallbackProps } from "reka-ui"
|
import type { AvatarFallbackProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { AvatarFallback } from "reka-ui"
|
import { AvatarFallback } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<AvatarFallbackProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<
|
||||||
|
AvatarFallbackProps & { class?: HTMLAttributes["class"] }
|
||||||
|
>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AvatarFallback
|
<AvatarFallback
|
||||||
data-slot="avatar-fallback"
|
data-slot="avatar-fallback"
|
||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
:class="cn('bg-muted flex size-full items-center justify-center rounded-full', props.class)"
|
:class="
|
||||||
>
|
cn(
|
||||||
<slot />
|
'bg-accent text-accent-foreground flex size-full items-center justify-center font-semibold',
|
||||||
</AvatarFallback>
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</AvatarFallback>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,41 +1,39 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getInitials } from "@/composables/useInitials";
|
import type { AvatarVariants } from "@/components/ui/avatar";
|
||||||
import {
|
import type { UserModel } from "@/types/models/user-model";
|
||||||
Avatar,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/components/ui/avatar/index";
|
|
||||||
import { usePage } from "@inertiajs/vue3";
|
import { usePage } from "@inertiajs/vue3";
|
||||||
import { computed, PropType } from "vue";
|
import { computed } from "vue";
|
||||||
import { UserModel } from "@/types/models/user-model";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { getInitials } from "@/composables/useInitials";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = withDefaults(
|
||||||
user: {
|
defineProps<{
|
||||||
type: Object as PropType<UserModel>,
|
user?: UserModel;
|
||||||
|
rounded?: boolean;
|
||||||
|
size?: AvatarVariants["size"];
|
||||||
|
label?: string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
rounded: true,
|
||||||
|
size: "default",
|
||||||
},
|
},
|
||||||
rounded: {
|
);
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const page = usePage();
|
const page = usePage();
|
||||||
const auth = computed(() => page.props.auth);
|
const user = computed(() => props.user ?? page.props.auth.user);
|
||||||
|
const accessibleLabel = computed(() => props.label ?? user.value.name);
|
||||||
const user = computed(() => props.user ?? auth.value.user)
|
const initials = computed(() => getInitials(user.value.name));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Avatar class="size-8 overflow-hidden" :class="rounded ? 'rounded-full' : 'rounded-lg'">
|
<Avatar :size="size" :shape="rounded ? 'circle' : 'rounded'">
|
||||||
<AvatarImage
|
<AvatarImage
|
||||||
v-if="user.avatar"
|
v-if="user.avatar"
|
||||||
:src="user.avatar"
|
:src="user.avatar"
|
||||||
:alt="user.name"
|
:alt="accessibleLabel"
|
||||||
/>
|
/>
|
||||||
<AvatarFallback
|
<AvatarFallback :aria-label="accessibleLabel">
|
||||||
class="rounded-lg bg-neutral-200 font-semibold text-black dark:bg-neutral-700 dark:text-white"
|
{{ initials }}
|
||||||
>
|
|
||||||
{{ getInitials(auth.user?.name) }}
|
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,3 +1,31 @@
|
|||||||
export { default as Avatar } from "./Avatar.vue"
|
import type { VariantProps } from "class-variance-authority";
|
||||||
export { default as AvatarFallback } from "./AvatarFallback.vue"
|
import { cva } from "class-variance-authority";
|
||||||
export { default as AvatarImage } from "./AvatarImage.vue"
|
|
||||||
|
export { default as Avatar } from "./Avatar.vue";
|
||||||
|
export { default as AvatarFallback } from "./AvatarFallback.vue";
|
||||||
|
export { default as AvatarImage } from "./AvatarImage.vue";
|
||||||
|
export { default as UserAvatar } from "./UserAvatar.vue";
|
||||||
|
|
||||||
|
export const avatarVariants = cva(
|
||||||
|
"border-border bg-muted relative flex shrink-0 overflow-hidden border",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
sm: "size-6 text-label",
|
||||||
|
default: "size-8 text-caption",
|
||||||
|
lg: "size-10 text-body",
|
||||||
|
xl: "size-16 text-title",
|
||||||
|
},
|
||||||
|
shape: {
|
||||||
|
circle: "rounded-full",
|
||||||
|
rounded: "rounded-lg",
|
||||||
|
square: "rounded-sm",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: "default",
|
||||||
|
shape: "circle",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
export type AvatarVariants = VariantProps<typeof avatarVariants>;
|
||||||
|
|||||||
@@ -1,26 +1,28 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { PrimitiveProps } from "reka-ui"
|
import type { PrimitiveProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import type { BadgeVariants } from "."
|
import type { BadgeVariants } from ".";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { Primitive } from "reka-ui"
|
import { Primitive } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { badgeVariants } from "."
|
import { badgeVariants } from ".";
|
||||||
|
|
||||||
const props = defineProps<PrimitiveProps & {
|
const props = defineProps<
|
||||||
variant?: BadgeVariants["variant"]
|
PrimitiveProps & {
|
||||||
class?: HTMLAttributes["class"]
|
variant?: BadgeVariants["variant"];
|
||||||
}>()
|
class?: HTMLAttributes["class"];
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class", "variant");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Primitive
|
<Primitive
|
||||||
data-slot="badge"
|
data-slot="badge"
|
||||||
:class="cn(badgeVariants({ variant }), props.class)"
|
:class="cn(badgeVariants({ variant }), props.class)"
|
||||||
v-bind="delegatedProps"
|
v-bind="delegatedProps"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</Primitive>
|
</Primitive>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,26 +1,31 @@
|
|||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority";
|
||||||
import { cva } from "class-variance-authority"
|
import { cva } from "class-variance-authority";
|
||||||
|
|
||||||
export { default as Badge } from "./Badge.vue"
|
export { default as Badge } from "./Badge.vue";
|
||||||
|
|
||||||
export const badgeVariants = cva(
|
export const badgeVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
"inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border px-2.5 py-1 text-label whitespace-nowrap transition-[background-color,border-color,color,box-shadow] outline-none [&>svg]:pointer-events-none [&>svg]:size-3 [&>svg]:shrink-0 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
||||||
secondary:
|
secondary:
|
||||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/80",
|
||||||
destructive:
|
destructive:
|
||||||
"border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"border-transparent bg-destructive text-destructive-foreground [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/35",
|
||||||
outline:
|
outline:
|
||||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
"border-border bg-card text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
||||||
},
|
success:
|
||||||
|
"border-transparent bg-success text-success-foreground [a&]:hover:brightness-95",
|
||||||
|
warning:
|
||||||
|
"border-transparent bg-warning text-warning-foreground [a&]:hover:brightness-95",
|
||||||
|
info: "border-transparent bg-info text-info-foreground [a&]:hover:brightness-95",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
);
|
||||||
variant: "default",
|
export type BadgeVariants = VariantProps<typeof badgeVariants>;
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
|
||||||
|
|||||||
@@ -1,29 +1,47 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { PrimitiveProps } from "reka-ui"
|
import type { PrimitiveProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import type { ButtonVariants } from "."
|
import type { ButtonVariants } from ".";
|
||||||
import { Primitive } from "reka-ui"
|
import { Primitive } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { computed } from "vue";
|
||||||
import { buttonVariants } from "."
|
import { Spinner } from "@/components/ui/spinner";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { buttonVariants } from ".";
|
||||||
|
|
||||||
interface Props extends PrimitiveProps {
|
interface Props extends PrimitiveProps {
|
||||||
variant?: ButtonVariants["variant"]
|
variant?: ButtonVariants["variant"];
|
||||||
size?: ButtonVariants["size"]
|
size?: ButtonVariants["size"];
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes["class"];
|
||||||
|
disabled?: boolean;
|
||||||
|
loading?: boolean;
|
||||||
|
loadingLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
as: "button",
|
as: "button",
|
||||||
})
|
disabled: false,
|
||||||
|
loading: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isDisabled = computed(() => props.disabled || props.loading);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Primitive
|
<Primitive
|
||||||
data-slot="button"
|
data-slot="button"
|
||||||
:as="as"
|
:data-loading="loading ? '' : undefined"
|
||||||
:as-child="asChild"
|
:as="as"
|
||||||
:class="cn(buttonVariants({ variant, size }), props.class)"
|
:as-child="asChild"
|
||||||
>
|
:disabled="isDisabled"
|
||||||
<slot />
|
:aria-disabled="isDisabled ? 'true' : undefined"
|
||||||
</Primitive>
|
:aria-busy="loading ? 'true' : undefined"
|
||||||
|
:class="cn(buttonVariants({ variant, size }), props.class)"
|
||||||
|
>
|
||||||
|
<Spinner
|
||||||
|
v-if="loading"
|
||||||
|
data-slot="button-spinner"
|
||||||
|
:label="loadingLabel"
|
||||||
|
/>
|
||||||
|
<slot />
|
||||||
|
</Primitive>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,38 +1,37 @@
|
|||||||
import type { VariantProps } from "class-variance-authority"
|
import type { VariantProps } from "class-variance-authority";
|
||||||
import { cva } from "class-variance-authority"
|
import { cva } from "class-variance-authority";
|
||||||
|
|
||||||
export { default as Button } from "./Button.vue"
|
export { default as Button } from "./Button.vue";
|
||||||
|
|
||||||
export const buttonVariants = cva(
|
export const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex shrink-0 items-center justify-center gap-2 whitespace-nowrap rounded-md text-body font-semibold transition-[background-color,border-color,color,box-shadow] outline-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[loading]:cursor-wait data-[loading]:opacity-80 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35 focus-visible:ring-offset-2 focus-visible:ring-offset-background [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default:
|
||||||
"bg-primary text-primary-foreground hover:bg-primary/90",
|
"border border-transparent bg-brand text-brand-foreground shadow-xs hover:brightness-95",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"border border-transparent bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/35",
|
||||||
outline:
|
outline:
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
"border border-input bg-card text-foreground shadow-xs hover:border-primary/45 hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"border border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost:
|
ghost: "border border-transparent hover:bg-accent hover:text-accent-foreground",
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
link: "min-h-0 border-0 px-0 text-primary underline-offset-4 shadow-none hover:underline",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
},
|
||||||
},
|
size: {
|
||||||
size: {
|
default: "min-h-control px-4 py-2 has-[>svg]:px-3",
|
||||||
"default": "h-9 px-4 py-2 has-[>svg]:px-3",
|
sm: "min-h-9 gap-1.5 px-3 py-1.5 has-[>svg]:px-2.5",
|
||||||
"sm": "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
lg: "min-h-12 px-6 py-3 has-[>svg]:px-4",
|
||||||
"lg": "h-10 rounded-md px-6 has-[>svg]:px-4",
|
icon: "size-control",
|
||||||
"icon": "size-9",
|
"icon-sm": "size-9",
|
||||||
"icon-sm": "size-8",
|
"icon-lg": "size-12",
|
||||||
"icon-lg": "size-10",
|
},
|
||||||
},
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
);
|
||||||
variant: "default",
|
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
|
||||||
|
|||||||
@@ -1,35 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { CheckboxRootEmits, CheckboxRootProps } from "reka-ui"
|
import type { CheckboxRootEmits, CheckboxRootProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { Check } from "lucide-vue-next"
|
import { Check } from "lucide-vue-next";
|
||||||
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "reka-ui"
|
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<CheckboxRootProps & { class?: HTMLAttributes["class"] }>()
|
const props = withDefaults(
|
||||||
const emits = defineEmits<CheckboxRootEmits>()
|
defineProps<
|
||||||
|
CheckboxRootProps & {
|
||||||
|
class?: HTMLAttributes["class"];
|
||||||
|
invalid?: boolean;
|
||||||
|
}
|
||||||
|
>(),
|
||||||
|
{ invalid: false },
|
||||||
|
);
|
||||||
|
const emits = defineEmits<CheckboxRootEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class", "invalid");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<CheckboxRoot
|
<CheckboxRoot
|
||||||
v-slot="slotProps"
|
v-slot="slotProps"
|
||||||
data-slot="checkbox"
|
data-slot="checkbox"
|
||||||
v-bind="forwarded"
|
:data-invalid="invalid ? '' : undefined"
|
||||||
:class="
|
:aria-invalid="invalid ? 'true' : undefined"
|
||||||
cn('peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
v-bind="forwarded"
|
||||||
props.class)"
|
:class="
|
||||||
>
|
cn(
|
||||||
<CheckboxIndicator
|
'peer border-input bg-card size-5 shrink-0 rounded-md border shadow-xs transition-[background-color,border-color,box-shadow] outline-none hover:border-primary/55 data-[state=checked]:border-brand data-[state=checked]:bg-brand data-[state=checked]:text-brand-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/25',
|
||||||
data-slot="checkbox-indicator"
|
props.class,
|
||||||
class="grid place-content-center text-current transition-none"
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<slot v-bind="slotProps">
|
<CheckboxIndicator
|
||||||
<Check class="size-3.5" />
|
data-slot="checkbox-indicator"
|
||||||
</slot>
|
class="grid place-content-center text-current transition-none"
|
||||||
</CheckboxIndicator>
|
>
|
||||||
</CheckboxRoot>
|
<slot v-bind="slotProps">
|
||||||
|
<Check class="size-4" stroke-width="2.5" />
|
||||||
|
</slot>
|
||||||
|
</CheckboxIndicator>
|
||||||
|
</CheckboxRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,53 +1,67 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
import type { DialogContentEmits, DialogContentProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { X } from "lucide-vue-next"
|
import { X } from "lucide-vue-next";
|
||||||
import {
|
import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import DialogOverlay from "./DialogOverlay.vue"
|
import DialogOverlay from "./DialogOverlay.vue";
|
||||||
|
import { useDialogFocus } from "@/composables/useDialogFocus";
|
||||||
|
import { useLocalization } from "@/composables/useLocalization";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DialogContentProps & { class?: HTMLAttributes["class"], showCloseButton?: boolean }>(), {
|
const props = withDefaults(
|
||||||
showCloseButton: true,
|
defineProps<
|
||||||
})
|
DialogContentProps & {
|
||||||
const emits = defineEmits<DialogContentEmits>()
|
class?: HTMLAttributes["class"];
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
}
|
||||||
|
>(),
|
||||||
|
{
|
||||||
|
showCloseButton: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const emits = defineEmits<DialogContentEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class", "showCloseButton");
|
||||||
|
const { restoreFocus } = useDialogFocus();
|
||||||
|
const { t } = useLocalization();
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay />
|
<DialogOverlay />
|
||||||
<DialogContent
|
<DialogContent
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
v-bind="{ ...$attrs, ...forwarded }"
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
:class="
|
@close-auto-focus="restoreFocus"
|
||||||
cn(
|
:class="
|
||||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
cn(
|
||||||
props.class,
|
'bg-card text-card-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid max-h-[calc(100dvh-2rem)] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-column overflow-y-auto overscroll-contain rounded-md border p-page shadow-lg duration-200 motion-reduce:animate-none sm:max-w-lg',
|
||||||
)"
|
props.class,
|
||||||
>
|
)
|
||||||
<slot />
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
|
||||||
<DialogClose
|
<DialogClose
|
||||||
v-if="showCloseButton"
|
v-if="showCloseButton"
|
||||||
data-slot="dialog-close"
|
data-slot="dialog-close"
|
||||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
class="ring-offset-background focus-visible:ring-ring absolute top-1 right-1 flex size-control items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||||
>
|
>
|
||||||
<X />
|
<X />
|
||||||
<span class="sr-only">Close</span>
|
<span class="sr-only">{{ t("common.close") }}</span>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes["class"];
|
||||||
}>()
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
data-slot="dialog-header"
|
data-slot="dialog-header"
|
||||||
:class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)"
|
:class="cn('flex flex-col gap-stack pr-6 text-left', props.class)"
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,59 +1,71 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
import type { DialogContentEmits, DialogContentProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { X } from "lucide-vue-next"
|
import { X } from "lucide-vue-next";
|
||||||
import {
|
import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogOverlay,
|
DialogOverlay,
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useDialogFocus } from "@/composables/useDialogFocus";
|
||||||
|
import { useLocalization } from "@/composables/useLocalization";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<
|
||||||
const emits = defineEmits<DialogContentEmits>()
|
DialogContentProps & { class?: HTMLAttributes["class"] }
|
||||||
|
>();
|
||||||
|
const emits = defineEmits<DialogContentEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
const { restoreFocus } = useDialogFocus();
|
||||||
|
const { t } = useLocalization();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<DialogOverlay
|
<DialogOverlay
|
||||||
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||||
>
|
|
||||||
<DialogContent
|
|
||||||
:class="
|
|
||||||
cn(
|
|
||||||
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
|
|
||||||
props.class,
|
|
||||||
)
|
|
||||||
"
|
|
||||||
v-bind="{ ...$attrs, ...forwarded }"
|
|
||||||
@pointer-down-outside="(event) => {
|
|
||||||
const originalEvent = event.detail.originalEvent;
|
|
||||||
const target = originalEvent.target as HTMLElement;
|
|
||||||
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
|
|
||||||
<DialogClose
|
|
||||||
class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary"
|
|
||||||
>
|
>
|
||||||
<X class="w-4 h-4" />
|
<DialogContent
|
||||||
<span class="sr-only">Close</span>
|
:class="
|
||||||
</DialogClose>
|
cn(
|
||||||
</DialogContent>
|
'relative z-50 my-4 grid w-full max-w-[calc(100%-2rem)] gap-column rounded-md border bg-card p-page text-card-foreground shadow-lg duration-200 sm:max-w-lg',
|
||||||
</DialogOverlay>
|
props.class,
|
||||||
</DialogPortal>
|
)
|
||||||
|
"
|
||||||
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
|
@close-auto-focus="restoreFocus"
|
||||||
|
@pointer-down-outside="
|
||||||
|
(event) => {
|
||||||
|
const originalEvent = event.detail.originalEvent;
|
||||||
|
const target = originalEvent.target as HTMLElement;
|
||||||
|
if (
|
||||||
|
originalEvent.offsetX > target.clientWidth ||
|
||||||
|
originalEvent.offsetY > target.clientHeight
|
||||||
|
) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
<DialogClose
|
||||||
|
class="absolute top-1 right-1 flex size-control items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-2 focus-visible:outline-ring"
|
||||||
|
>
|
||||||
|
<X class="w-4 h-4" />
|
||||||
|
<span class="sr-only">{{ t("common.close") }}</span>
|
||||||
|
</DialogClose>
|
||||||
|
</DialogContent>
|
||||||
|
</DialogOverlay>
|
||||||
|
</DialogPortal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,39 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownMenuCheckboxItemEmits, DropdownMenuCheckboxItemProps } from "reka-ui"
|
import type {
|
||||||
import type { HTMLAttributes } from "vue"
|
DropdownMenuCheckboxItemEmits,
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
DropdownMenuCheckboxItemProps,
|
||||||
import { Check } from "lucide-vue-next"
|
} from "reka-ui";
|
||||||
|
import type { HTMLAttributes } from "vue";
|
||||||
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
|
import { Check } from "lucide-vue-next";
|
||||||
import {
|
import {
|
||||||
DropdownMenuCheckboxItem,
|
DropdownMenuCheckboxItem,
|
||||||
DropdownMenuItemIndicator,
|
DropdownMenuItemIndicator,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<
|
||||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>()
|
DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }
|
||||||
|
>();
|
||||||
|
const emits = defineEmits<DropdownMenuCheckboxItemEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuCheckboxItem
|
<DropdownMenuCheckboxItem
|
||||||
data-slot="dropdown-menu-checkbox-item"
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class=" cn(
|
:class="
|
||||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
cn(
|
||||||
props.class,
|
'focus:bg-accent focus:text-accent-foreground relative flex min-h-control cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
)"
|
props.class,
|
||||||
>
|
)
|
||||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
"
|
||||||
<DropdownMenuItemIndicator>
|
>
|
||||||
<slot name="indicator-icon">
|
<span
|
||||||
<Check class="size-4" />
|
class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"
|
||||||
</slot>
|
>
|
||||||
</DropdownMenuItemIndicator>
|
<DropdownMenuItemIndicator>
|
||||||
</span>
|
<slot name="indicator-icon">
|
||||||
<slot />
|
<Check class="size-4" />
|
||||||
</DropdownMenuCheckboxItem>
|
</slot>
|
||||||
|
</DropdownMenuItemIndicator>
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,39 +1,49 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownMenuContentEmits, DropdownMenuContentProps } from "reka-ui"
|
import type {
|
||||||
import type { HTMLAttributes } from "vue"
|
DropdownMenuContentEmits,
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
DropdownMenuContentProps,
|
||||||
|
} from "reka-ui";
|
||||||
|
import type { HTMLAttributes } from "vue";
|
||||||
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import {
|
import {
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuPortal,
|
DropdownMenuPortal,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
|
defineProps<
|
||||||
{
|
DropdownMenuContentProps & { class?: HTMLAttributes["class"] }
|
||||||
sideOffset: 4,
|
>(),
|
||||||
},
|
{
|
||||||
)
|
sideOffset: 4,
|
||||||
const emits = defineEmits<DropdownMenuContentEmits>()
|
},
|
||||||
|
);
|
||||||
|
const emits = defineEmits<DropdownMenuContentEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuPortal>
|
<DropdownMenuPortal>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
data-slot="dropdown-menu-content"
|
data-slot="dropdown-menu-content"
|
||||||
v-bind="{ ...$attrs, ...forwarded }"
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
:class="cn('bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', props.class)"
|
:class="
|
||||||
>
|
cn(
|
||||||
<slot />
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] max-w-[calc(100vw-2rem)] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
||||||
</DropdownMenuContent>
|
props.class,
|
||||||
</DropdownMenuPortal>
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenuPortal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,31 +1,41 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownMenuItemProps } from "reka-ui"
|
import type { DropdownMenuItemProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { DropdownMenuItem, useForwardProps } from "reka-ui"
|
import { DropdownMenuItem, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = withDefaults(defineProps<DropdownMenuItemProps & {
|
const props = withDefaults(
|
||||||
class?: HTMLAttributes["class"]
|
defineProps<
|
||||||
inset?: boolean
|
DropdownMenuItemProps & {
|
||||||
variant?: "default" | "destructive"
|
class?: HTMLAttributes["class"];
|
||||||
}>(), {
|
inset?: boolean;
|
||||||
variant: "default",
|
variant?: "default" | "destructive";
|
||||||
})
|
}
|
||||||
|
>(),
|
||||||
|
{
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "inset", "variant", "class")
|
const delegatedProps = reactiveOmit(props, "inset", "variant", "class");
|
||||||
|
|
||||||
const forwardedProps = useForwardProps(delegatedProps)
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
data-slot="dropdown-menu-item"
|
data-slot="dropdown-menu-item"
|
||||||
:data-inset="inset ? '' : undefined"
|
:data-inset="inset ? '' : undefined"
|
||||||
:data-variant="variant"
|
:data-variant="variant"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="cn('focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4', props.class)"
|
:class="
|
||||||
>
|
cn(
|
||||||
<slot />
|
'focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex min-h-control cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
</DropdownMenuItem>
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuItem>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,40 +1,49 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownMenuRadioItemEmits, DropdownMenuRadioItemProps } from "reka-ui"
|
import type {
|
||||||
import type { HTMLAttributes } from "vue"
|
DropdownMenuRadioItemEmits,
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
DropdownMenuRadioItemProps,
|
||||||
import { Circle } from "lucide-vue-next"
|
} from "reka-ui";
|
||||||
|
import type { HTMLAttributes } from "vue";
|
||||||
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
|
import { Circle } from "lucide-vue-next";
|
||||||
import {
|
import {
|
||||||
DropdownMenuItemIndicator,
|
DropdownMenuItemIndicator,
|
||||||
DropdownMenuRadioItem,
|
DropdownMenuRadioItem,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<
|
||||||
|
DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }
|
||||||
|
>();
|
||||||
|
|
||||||
const emits = defineEmits<DropdownMenuRadioItemEmits>()
|
const emits = defineEmits<DropdownMenuRadioItemEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuRadioItem
|
<DropdownMenuRadioItem
|
||||||
data-slot="dropdown-menu-radio-item"
|
data-slot="dropdown-menu-radio-item"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="cn(
|
:class="
|
||||||
'focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
cn(
|
||||||
props.class,
|
'focus:bg-accent focus:text-accent-foreground relative flex min-h-control cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
||||||
)"
|
props.class,
|
||||||
>
|
)
|
||||||
<span class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
"
|
||||||
<DropdownMenuItemIndicator>
|
>
|
||||||
<slot name="indicator-icon">
|
<span
|
||||||
<Circle class="size-2 fill-current" />
|
class="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"
|
||||||
</slot>
|
>
|
||||||
</DropdownMenuItemIndicator>
|
<DropdownMenuItemIndicator>
|
||||||
</span>
|
<slot name="indicator-icon">
|
||||||
<slot />
|
<Circle class="size-2 fill-current" />
|
||||||
</DropdownMenuRadioItem>
|
</slot>
|
||||||
|
</DropdownMenuItemIndicator>
|
||||||
|
</span>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,27 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownMenuSubContentEmits, DropdownMenuSubContentProps } from "reka-ui"
|
import type {
|
||||||
import type { HTMLAttributes } from "vue"
|
DropdownMenuSubContentEmits,
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
DropdownMenuSubContentProps,
|
||||||
import {
|
} from "reka-ui";
|
||||||
DropdownMenuSubContent,
|
import type { HTMLAttributes } from "vue";
|
||||||
useForwardPropsEmits,
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
} from "reka-ui"
|
import { DropdownMenuSubContent, useForwardPropsEmits } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<
|
||||||
const emits = defineEmits<DropdownMenuSubContentEmits>()
|
DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }
|
||||||
|
>();
|
||||||
|
const emits = defineEmits<DropdownMenuSubContentEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuSubContent
|
<DropdownMenuSubContent
|
||||||
data-slot="dropdown-menu-sub-content"
|
data-slot="dropdown-menu-sub-content"
|
||||||
v-bind="forwarded"
|
v-bind="forwarded"
|
||||||
:class="cn('bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--reka-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg', props.class)"
|
:class="
|
||||||
>
|
cn(
|
||||||
<slot />
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--reka-dropdown-menu-content-available-height) min-w-[8rem] max-w-[calc(100vw-2rem)] origin-(--reka-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-lg',
|
||||||
</DropdownMenuSubContent>
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</DropdownMenuSubContent>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,30 +1,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DropdownMenuSubTriggerProps } from "reka-ui"
|
import type { DropdownMenuSubTriggerProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronRight } from "lucide-vue-next"
|
import { ChevronRight } from "lucide-vue-next";
|
||||||
import {
|
import { DropdownMenuSubTrigger, useForwardProps } from "reka-ui";
|
||||||
DropdownMenuSubTrigger,
|
import { cn } from "@/lib/utils";
|
||||||
useForwardProps,
|
|
||||||
} from "reka-ui"
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"], inset?: boolean }>()
|
const props = defineProps<
|
||||||
|
DropdownMenuSubTriggerProps & {
|
||||||
|
class?: HTMLAttributes["class"];
|
||||||
|
inset?: boolean;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class", "inset")
|
const delegatedProps = reactiveOmit(props, "class", "inset");
|
||||||
const forwardedProps = useForwardProps(delegatedProps)
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DropdownMenuSubTrigger
|
<DropdownMenuSubTrigger
|
||||||
data-slot="dropdown-menu-sub-trigger"
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="cn(
|
:class="
|
||||||
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
|
cn(
|
||||||
props.class,
|
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex min-h-control cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
|
||||||
)"
|
props.class,
|
||||||
>
|
)
|
||||||
<slot />
|
"
|
||||||
<ChevronRight class="ml-auto size-4" />
|
>
|
||||||
</DropdownMenuSubTrigger>
|
<slot />
|
||||||
|
<ChevronRight class="ml-auto size-4" />
|
||||||
|
</DropdownMenuSubTrigger>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,33 +1,38 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { useVModel } from "@vueuse/core"
|
import { useVModel } from "@vueuse/core";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
defaultValue?: string | number
|
defaultValue?: string | number;
|
||||||
modelValue?: string | number | null
|
modelValue?: string | number | null;
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes["class"];
|
||||||
}>()
|
invalid?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
(e: "update:modelValue", payload: string | number): void
|
(e: "update:modelValue", payload: string | number): void;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const modelValue = useVModel(props, "modelValue", emits, {
|
const modelValue = useVModel(props, "modelValue", emits, {
|
||||||
passive: true,
|
passive: true,
|
||||||
defaultValue: props.defaultValue,
|
defaultValue: props.defaultValue,
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<input
|
<input
|
||||||
v-model="modelValue"
|
v-model="modelValue"
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
:class="cn(
|
:data-invalid="invalid ? '' : undefined"
|
||||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
:aria-invalid="invalid ? 'true' : undefined"
|
||||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
:class="
|
||||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
cn(
|
||||||
props.class,
|
'border-input bg-card placeholder:text-muted-foreground selection:bg-brand selection:text-brand-foreground min-h-control w-full min-w-0 rounded-md border px-3 py-2 text-base shadow-xs transition-[border-color,box-shadow] outline-none file:mr-3 file:inline-flex file:min-h-8 file:border-0 file:bg-transparent file:text-body file:font-semibold disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 md:text-body',
|
||||||
)"
|
'hover:border-primary/45 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35',
|
||||||
>
|
'aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/25',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,51 +1,60 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SelectContentEmits, SelectContentProps } from "reka-ui"
|
import type { SelectContentEmits, SelectContentProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import {
|
import {
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectPortal,
|
SelectPortal,
|
||||||
SelectViewport,
|
SelectViewport,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { SelectScrollDownButton, SelectScrollUpButton } from "."
|
import { SelectScrollDownButton, SelectScrollUpButton } from ".";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<SelectContentProps & { class?: HTMLAttributes["class"] }>(),
|
defineProps<SelectContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||||
{
|
{
|
||||||
position: "popper",
|
position: "popper",
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
const emits = defineEmits<SelectContentEmits>()
|
const emits = defineEmits<SelectContentEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SelectPortal>
|
<SelectPortal>
|
||||||
<SelectContent
|
<SelectContent
|
||||||
data-slot="select-content"
|
data-slot="select-content"
|
||||||
v-bind="{ ...$attrs, ...forwarded }"
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
:class="cn(
|
:class="
|
||||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--reka-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
cn(
|
||||||
position === 'popper'
|
'bg-popover text-popover-foreground border-border data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--reka-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-lg',
|
||||||
&& 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
position === 'popper' &&
|
||||||
props.class,
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
)
|
props.class,
|
||||||
"
|
)
|
||||||
>
|
"
|
||||||
<SelectScrollUpButton />
|
>
|
||||||
<SelectViewport :class="cn('p-1', position === 'popper' && 'h-[var(--reka-select-trigger-height)] w-full min-w-[var(--reka-select-trigger-width)] scroll-my-1')">
|
<SelectScrollUpButton />
|
||||||
<slot />
|
<SelectViewport
|
||||||
</SelectViewport>
|
:class="
|
||||||
<SelectScrollDownButton />
|
cn(
|
||||||
</SelectContent>
|
'p-1',
|
||||||
</SelectPortal>
|
position === 'popper' &&
|
||||||
|
'h-[var(--reka-select-trigger-height)] w-full min-w-[var(--reka-select-trigger-width)] scroll-my-1',
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</SelectViewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectContent>
|
||||||
|
</SelectPortal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,44 +1,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SelectItemProps } from "reka-ui"
|
import type { SelectItemProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { Check } from "lucide-vue-next"
|
import { Check } from "lucide-vue-next";
|
||||||
import {
|
import {
|
||||||
SelectItem,
|
SelectItem,
|
||||||
SelectItemIndicator,
|
SelectItemIndicator,
|
||||||
SelectItemText,
|
SelectItemText,
|
||||||
useForwardProps,
|
useForwardProps,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = defineProps<SelectItemProps & { class?: HTMLAttributes["class"] }>()
|
const props = defineProps<
|
||||||
|
SelectItemProps & { class?: HTMLAttributes["class"] }
|
||||||
|
>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class")
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
const forwardedProps = useForwardProps(delegatedProps)
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SelectItem
|
<SelectItem
|
||||||
data-slot="select-item"
|
data-slot="select-item"
|
||||||
v-bind="forwardedProps"
|
v-bind="forwardedProps"
|
||||||
:class="
|
:class="
|
||||||
cn(
|
cn(
|
||||||
'focus:bg-accent focus:text-accent-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',
|
'data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground relative flex min-h-9 w-full cursor-default items-center gap-2 rounded-sm py-2 pr-8 pl-2 text-body outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2',
|
||||||
props.class,
|
props.class,
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span class="absolute right-2 flex size-3.5 items-center justify-center">
|
<span
|
||||||
<SelectItemIndicator>
|
class="absolute right-2 flex size-3.5 items-center justify-center"
|
||||||
<slot name="indicator-icon">
|
>
|
||||||
<Check class="size-4" />
|
<SelectItemIndicator>
|
||||||
</slot>
|
<slot name="indicator-icon">
|
||||||
</SelectItemIndicator>
|
<Check class="size-4" />
|
||||||
</span>
|
</slot>
|
||||||
|
</SelectItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
<SelectItemText>
|
<SelectItemText>
|
||||||
<slot />
|
<slot />
|
||||||
</SelectItemText>
|
</SelectItemText>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,33 +1,45 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { SelectTriggerProps } from "reka-ui"
|
import type { SelectTriggerProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { ChevronDown } from "lucide-vue-next"
|
import { ChevronDown } from "lucide-vue-next";
|
||||||
import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui"
|
import { SelectIcon, SelectTrigger, useForwardProps } from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<SelectTriggerProps & { class?: HTMLAttributes["class"], size?: "sm" | "default" }>(),
|
defineProps<
|
||||||
{ size: "default" },
|
SelectTriggerProps & {
|
||||||
)
|
class?: HTMLAttributes["class"];
|
||||||
|
size?: "sm" | "default";
|
||||||
|
invalid?: boolean;
|
||||||
|
}
|
||||||
|
>(),
|
||||||
|
{ size: "default", invalid: false },
|
||||||
|
);
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class", "size")
|
const delegatedProps = reactiveOmit(props, "class", "size", "invalid");
|
||||||
const forwardedProps = useForwardProps(delegatedProps)
|
const forwardedProps = useForwardProps(delegatedProps);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SelectTrigger
|
<SelectTrigger
|
||||||
data-slot="select-trigger"
|
data-slot="select-trigger"
|
||||||
:data-size="size"
|
:data-size="size"
|
||||||
v-bind="forwardedProps"
|
:data-invalid="invalid ? '' : undefined"
|
||||||
:class="cn(
|
:aria-invalid="invalid ? 'true' : undefined"
|
||||||
'border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*=\'text-\'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4',
|
v-bind="forwardedProps"
|
||||||
props.class,
|
:class="
|
||||||
)"
|
cn(
|
||||||
>
|
'group/select-trigger border-input bg-card data-[placeholder]:text-muted-foreground flex w-fit items-center justify-between gap-2 rounded-md border px-3 py-2 text-body whitespace-nowrap shadow-xs transition-[border-color,box-shadow] outline-none hover:border-primary/45 focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35 disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-60 data-[state=open]:border-primary data-[size=default]:min-h-control data-[size=sm]:min-h-9 aria-invalid:border-destructive aria-invalid:ring-2 aria-invalid:ring-destructive/25 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-4 [&_svg:not([class*=\'text-\'])]:text-muted-foreground',
|
||||||
<slot />
|
props.class,
|
||||||
<SelectIcon as-child>
|
)
|
||||||
<ChevronDown class="size-4 opacity-50" />
|
"
|
||||||
</SelectIcon>
|
>
|
||||||
</SelectTrigger>
|
<slot />
|
||||||
|
<SelectIcon as-child>
|
||||||
|
<ChevronDown
|
||||||
|
class="size-4 transition-transform duration-150 group-data-[state=open]/select-trigger:rotate-180"
|
||||||
|
/>
|
||||||
|
</SelectIcon>
|
||||||
|
</SelectTrigger>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,62 +1,70 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { DialogContentEmits, DialogContentProps } from "reka-ui"
|
import type { DialogContentEmits, DialogContentProps } from "reka-ui";
|
||||||
import type { HTMLAttributes } from "vue"
|
import type { HTMLAttributes } from "vue";
|
||||||
import { reactiveOmit } from "@vueuse/core"
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
import { X } from "lucide-vue-next"
|
import { X } from "lucide-vue-next";
|
||||||
import {
|
import {
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogPortal,
|
DialogPortal,
|
||||||
useForwardPropsEmits,
|
useForwardPropsEmits,
|
||||||
} from "reka-ui"
|
} from "reka-ui";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import SheetOverlay from "./SheetOverlay.vue"
|
import SheetOverlay from "./SheetOverlay.vue";
|
||||||
|
import { useDialogFocus } from "@/composables/useDialogFocus";
|
||||||
|
import { useLocalization } from "@/composables/useLocalization";
|
||||||
|
|
||||||
interface SheetContentProps extends DialogContentProps {
|
interface SheetContentProps extends DialogContentProps {
|
||||||
class?: HTMLAttributes["class"]
|
class?: HTMLAttributes["class"];
|
||||||
side?: "top" | "right" | "bottom" | "left"
|
side?: "top" | "right" | "bottom" | "left";
|
||||||
}
|
}
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const props = withDefaults(defineProps<SheetContentProps>(), {
|
const props = withDefaults(defineProps<SheetContentProps>(), {
|
||||||
side: "right",
|
side: "right",
|
||||||
})
|
});
|
||||||
const emits = defineEmits<DialogContentEmits>()
|
const emits = defineEmits<DialogContentEmits>();
|
||||||
|
|
||||||
const delegatedProps = reactiveOmit(props, "class", "side")
|
const delegatedProps = reactiveOmit(props, "class", "side");
|
||||||
|
|
||||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
const { restoreFocus } = useDialogFocus();
|
||||||
|
const { t } = useLocalization();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<DialogPortal>
|
<DialogPortal>
|
||||||
<SheetOverlay />
|
<SheetOverlay />
|
||||||
<DialogContent
|
<DialogContent
|
||||||
data-slot="sheet-content"
|
data-slot="sheet-content"
|
||||||
:class="cn(
|
:class="
|
||||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
cn(
|
||||||
side === 'right'
|
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
|
||||||
&& 'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
side === 'right' &&
|
||||||
side === 'left'
|
'data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm',
|
||||||
&& 'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
side === 'left' &&
|
||||||
side === 'top'
|
'data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm',
|
||||||
&& 'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
|
side === 'top' &&
|
||||||
side === 'bottom'
|
'data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top inset-x-0 top-0 h-auto border-b',
|
||||||
&& 'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
|
side === 'bottom' &&
|
||||||
props.class)"
|
'data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom inset-x-0 bottom-0 h-auto border-t',
|
||||||
v-bind="{ ...$attrs, ...forwarded }"
|
props.class,
|
||||||
>
|
)
|
||||||
<slot />
|
"
|
||||||
|
v-bind="{ ...$attrs, ...forwarded }"
|
||||||
|
@close-auto-focus="restoreFocus"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
|
||||||
<DialogClose
|
<DialogClose
|
||||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none"
|
class="ring-offset-background focus-visible:ring-ring absolute top-1 right-1 flex size-control items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none"
|
||||||
>
|
>
|
||||||
<X class="size-4" />
|
<X class="size-4" />
|
||||||
<span class="sr-only">Close</span>
|
<span class="sr-only">{{ t("common.close") }}</span>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</DialogPortal>
|
</DialogPortal>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user