Add PriorityAlertsPanel component to the dashboard, update DashboardController with alert logic, and apply misc UI fixes across sidebar, forms, and pages. Includes epic-1 retrospective and sprint status update. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
172 lines
5.7 KiB
Vue
172 lines
5.7 KiB
Vue
<script setup lang="ts">
|
|
/* eslint-disable vue/no-mutating-props -- Inertia useForm objects are reactive and designed to be mutated via props */
|
|
import type { Form } from '@inertiajs/vue3';
|
|
import InputError from '@/components/InputError.vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Spinner } from '@/components/ui/spinner';
|
|
|
|
export type WorkspaceFormData = {
|
|
name: string;
|
|
slug: string;
|
|
user_ids: number[];
|
|
user_roles: Record<number, string>;
|
|
};
|
|
|
|
export type WorkspaceFormUser = {
|
|
id: number;
|
|
name: string;
|
|
email: string;
|
|
};
|
|
|
|
type Props = {
|
|
form: Form<WorkspaceFormData>;
|
|
users: WorkspaceFormUser[];
|
|
workspaceUserRoles: Record<string, string>;
|
|
submitLabel?: string;
|
|
};
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
submitLabel: 'Save',
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
submit: [];
|
|
}>();
|
|
|
|
const defaultRole = 'worker';
|
|
|
|
function onUserToggle(userId: number, checked: boolean) {
|
|
if (checked) {
|
|
const ids = props.form.user_ids ?? [];
|
|
if (!ids.includes(userId)) {
|
|
props.form.user_ids = [...ids, userId];
|
|
}
|
|
const roles = props.form.user_roles ?? {};
|
|
props.form.user_roles = {
|
|
...roles,
|
|
[userId]: roles[userId] ?? defaultRole,
|
|
};
|
|
} else {
|
|
props.form.user_ids = (props.form.user_ids ?? []).filter(
|
|
(id) => id !== userId,
|
|
);
|
|
const roles = { ...(props.form.user_roles ?? {}) };
|
|
delete roles[userId];
|
|
props.form.user_roles = roles;
|
|
}
|
|
}
|
|
|
|
function onRoleChange(userId: number, role: string) {
|
|
const roles = props.form.user_roles ?? {};
|
|
props.form.user_roles = { ...roles, [userId]: role };
|
|
}
|
|
|
|
function isUserSelected(userId: number): boolean {
|
|
return (props.form.user_ids ?? []).includes(userId);
|
|
}
|
|
|
|
function getUserRole(userId: number): string {
|
|
return props.form.user_roles?.[userId] ?? defaultRole;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<form @submit.prevent="emit('submit')" class="flex flex-col space-y-6">
|
|
<div class="grid gap-2">
|
|
<Label for="name">Name</Label>
|
|
<Input
|
|
id="name"
|
|
v-model="form.name"
|
|
type="text"
|
|
required
|
|
placeholder="Cabinet Comptable XYZ"
|
|
aria-invalid="!!form.errors.name"
|
|
/>
|
|
<InputError :message="form.errors.name" />
|
|
</div>
|
|
|
|
<div class="grid gap-2">
|
|
<Label for="slug">Slug</Label>
|
|
<Input
|
|
id="slug"
|
|
v-model="form.slug"
|
|
type="text"
|
|
placeholder="cabinet-comptable-xyz (optional)"
|
|
aria-invalid="!!form.errors.slug"
|
|
/>
|
|
<p class="text-xs text-muted-foreground">
|
|
Leave empty to auto-generate from the name.
|
|
</p>
|
|
<InputError :message="form.errors.slug" />
|
|
</div>
|
|
|
|
<div v-if="users?.length" class="grid gap-2">
|
|
<Label>Users</Label>
|
|
<p class="text-xs text-muted-foreground">
|
|
Select users to add to this workspace.
|
|
</p>
|
|
<div
|
|
class="max-h-48 space-y-2 overflow-y-auto rounded-md border border-sidebar-border/70 p-3 dark:border-sidebar-border"
|
|
>
|
|
<div
|
|
v-for="user in users"
|
|
:key="user.id"
|
|
class="flex items-center gap-3 rounded px-2 py-1.5 hover:bg-muted/50"
|
|
>
|
|
<input
|
|
type="checkbox"
|
|
:value="user.id"
|
|
:checked="isUserSelected(user.id)"
|
|
class="size-4 shrink-0 rounded-[4px] border border-input focus-visible:ring-2 focus-visible:ring-ring"
|
|
@change="
|
|
onUserToggle(
|
|
user.id,
|
|
($event.target as HTMLInputElement).checked,
|
|
)
|
|
"
|
|
/>
|
|
<div class="flex min-w-0 flex-1 flex-col">
|
|
<span class="text-sm font-medium">{{ user.name }}</span>
|
|
<span class="text-xs text-muted-foreground">{{
|
|
user.email
|
|
}}</span>
|
|
</div>
|
|
<select
|
|
:value="getUserRole(user.id)"
|
|
:disabled="!isUserSelected(user.id)"
|
|
class="h-8 shrink-0 rounded-md border border-input bg-background px-2 text-sm disabled:opacity-50"
|
|
@change="
|
|
onRoleChange(
|
|
user.id,
|
|
($event.target as HTMLSelectElement).value,
|
|
)
|
|
"
|
|
>
|
|
<option
|
|
v-for="(label, value) in workspaceUserRoles"
|
|
:key="value"
|
|
:value="value"
|
|
>
|
|
{{ label }}
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<InputError :message="form.errors.user_ids" />
|
|
</div>
|
|
|
|
<div class="flex items-center gap-4">
|
|
<Button
|
|
type="submit"
|
|
:disabled="form.processing"
|
|
data-test="workspace-form-submit"
|
|
>
|
|
<Spinner v-if="form.processing" />
|
|
{{ submitLabel }}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</template>
|