feat: complete Epic 1 — team management & permission system
- Story 1.1: Permission enum, config, AuthorizesPermissions & HasWorkspaceScope traits, member→worker migration - Story 1.2: Team page with member list, invitation system with queued email - Story 1.3: Role assignment (Manager/Worker) and member removal with activity logging - Story 1.4: Owner-only permission toggle matrix for Managers (manage team, view logs, configure portal) - Story 1.5: Role-based access enforcement — Workers see only assigned declarations/clients, sidebar scoping - Story 1.6: Workspace switcher dropdown for multi-workspace users with session-based switching - 83 new/modified files, 182 tests passing with zero regressions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { router } from '@inertiajs/vue3';
|
||||
import {
|
||||
ChevronFirst,
|
||||
ChevronLast,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
} from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -10,7 +16,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { ChevronFirst, ChevronLast, ChevronLeft, ChevronRight } from 'lucide-vue-next';
|
||||
|
||||
interface PaginationData {
|
||||
current_page: number;
|
||||
@@ -33,7 +38,9 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const canGoPrevious = computed(() => props.pagination.current_page > 1);
|
||||
const canGoNext = computed(() => props.pagination.current_page < props.pagination.last_page);
|
||||
const canGoNext = computed(
|
||||
() => props.pagination.current_page < props.pagination.last_page,
|
||||
);
|
||||
|
||||
const handlePerPageChange = (value: unknown): void => {
|
||||
const str = value != null ? String(value) : null;
|
||||
@@ -43,10 +50,14 @@ const handlePerPageChange = (value: unknown): void => {
|
||||
url.searchParams.set('per_page', perPage.toString());
|
||||
url.searchParams.set('page', '1');
|
||||
|
||||
router.get(url.pathname + url.search, {}, {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
});
|
||||
router.get(
|
||||
url.pathname + url.search,
|
||||
{},
|
||||
{
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const goToPage = (page: number): void => {
|
||||
@@ -57,27 +68,34 @@ const goToPage = (page: number): void => {
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.set('page', page.toString());
|
||||
|
||||
router.get(url.pathname + url.search, {}, {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
});
|
||||
router.get(
|
||||
url.pathname + url.search,
|
||||
{},
|
||||
{
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
},
|
||||
);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-4 px-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="text-muted-foreground text-sm">
|
||||
<div
|
||||
class="flex flex-col gap-4 px-4 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div class="text-sm text-muted-foreground">
|
||||
<span v-if="selectedCount > 0">
|
||||
{{ selectedCount }} sur {{ pagination.total }} ligne(s) sélectionnée(s).
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ pagination.total }} ligne(s) au total
|
||||
{{ selectedCount }} sur {{ pagination.total }} ligne(s)
|
||||
sélectionnée(s).
|
||||
</span>
|
||||
<span v-else> {{ pagination.total }} ligne(s) au total </span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:gap-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-muted-foreground hidden text-sm sm:inline">Lignes par page</span>
|
||||
<span class="hidden text-sm text-muted-foreground sm:inline"
|
||||
>Lignes par page</span
|
||||
>
|
||||
<Select
|
||||
:model-value="pagination.per_page.toString()"
|
||||
@update:model-value="handlePerPageChange"
|
||||
@@ -97,8 +115,9 @@ const goToPage = (page: number): void => {
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="text-muted-foreground hidden text-sm md:inline">
|
||||
Page {{ pagination.current_page }} sur {{ pagination.last_page }}
|
||||
<div class="hidden text-sm text-muted-foreground md:inline">
|
||||
Page {{ pagination.current_page }} sur
|
||||
{{ pagination.last_page }}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
@@ -118,7 +137,7 @@ const goToPage = (page: number): void => {
|
||||
>
|
||||
<ChevronLeft class="size-4" />
|
||||
</Button>
|
||||
<div class="text-muted-foreground mx-2 text-sm md:hidden">
|
||||
<div class="mx-2 text-sm text-muted-foreground md:hidden">
|
||||
{{ pagination.current_page }}/{{ pagination.last_page }}
|
||||
</div>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user