feat: Ajouter des fonctionnalités de tri et de filtrage des heures, incluant la sélection d'utilisateurs et l'ajout de timestamps

This commit is contained in:
2025-11-20 18:31:33 +01:00
parent b90c2e518a
commit e825651c57
9 changed files with 511 additions and 23 deletions

View File

@@ -41,4 +41,4 @@ prisma/dev.db
INITIAL_SETUP.md
API_DOCS.md
INDICATIONS_POUR_LLMS.md
scripts/seed-test.js

View File

@@ -28,6 +28,14 @@ import {
DialogHeader,
DialogTitle,
} from '../../components/ui/dialog';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../../components/ui/select';
import { Checkbox } from '../../components/ui/checkbox';
import { toast } from 'sonner';
import { DatePicker } from '../../components/ui/date-picker';
import { format } from 'date-fns';
@@ -40,6 +48,7 @@ interface Hour {
reason: string;
status: string;
userId: string;
createdAt?: string;
user: { email: string; firstName?: string; lastName?: string; role: string };
validatedBy?: { firstName?: string; lastName?: string; email: string };
}
@@ -95,6 +104,9 @@ export default function AdminPage() {
const [users, setUsers] = useState<User[]>([]);
const [confirmPasswordChange, setConfirmPasswordChange] = useState(false);
const [selectedUserIds, setSelectedUserIds] = useState<string[]>([]);
const [sortBy, setSortBy] = useState<'date' | 'createdAt'>('date');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [showPendingFirst, setShowPendingFirst] = useState(false);
const { refetchSettings } = useSettings();
useEffect(() => {
@@ -363,9 +375,27 @@ export default function AdminPage() {
const isSuperAdmin = session?.user?.role === 'SUPER_ADMIN';
const sortedHours = hours.sort(
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
);
const sortedHours = [...hours].sort((a, b) => {
if (showPendingFirst) {
if (a.status === 'PENDING' && b.status !== 'PENDING') return -1;
if (a.status !== 'PENDING' && b.status === 'PENDING') return 1;
}
let dateA, dateB;
if (sortBy === 'createdAt') {
dateA = a.createdAt ? new Date(a.createdAt).getTime() : 0;
dateB = b.createdAt ? new Date(b.createdAt).getTime() : 0;
} else {
dateA = new Date(a.date).getTime();
dateB = new Date(b.date).getTime();
}
if (sortOrder === 'asc') {
return dateA - dateB;
} else {
return dateB - dateA;
}
});
const displayedHours = showAll ? sortedHours : sortedHours.slice(0, 10);
const formatHours = (minutes: number) => {
@@ -395,12 +425,11 @@ export default function AdminPage() {
<div className="h-40 overflow-y-auto border rounded p-2 space-y-2 bg-white dark:bg-stone-900">
{users.map((user) => (
<div key={user.id} className="flex items-center space-x-2">
<input
type="checkbox"
<Checkbox
id={`user-${user.id}`}
checked={selectedUserIds.includes(user.id)}
onChange={(e) => {
if (e.target.checked) {
onCheckedChange={(checked) => {
if (checked) {
setSelectedUserIds([...selectedUserIds, user.id]);
} else {
setSelectedUserIds(
@@ -408,7 +437,6 @@ export default function AdminPage() {
);
}
}}
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary"
/>
<Label
htmlFor={`user-${user.id}`}
@@ -467,6 +495,59 @@ export default function AdminPage() {
<CardTitle>Gestion des heures</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-4 mb-4 items-end">
<div className="w-[200px]">
<Label htmlFor="sortBy" className="mb-2 block">
Trier par
</Label>
<Select
value={sortBy}
onValueChange={(value) =>
setSortBy(value as 'date' | 'createdAt')
}
>
<SelectTrigger id="sortBy">
<SelectValue placeholder="Trier par" />
</SelectTrigger>
<SelectContent>
<SelectItem value="date">Date effective</SelectItem>
<SelectItem value="createdAt">Date d'ajout</SelectItem>
</SelectContent>
</Select>
</div>
<div className="w-[200px]">
<Label htmlFor="sortOrder" className="mb-2 block">
Ordre
</Label>
<Select
value={sortOrder}
onValueChange={(value) => setSortOrder(value as 'asc' | 'desc')}
>
<SelectTrigger id="sortOrder">
<SelectValue placeholder="Ordre" />
</SelectTrigger>
<SelectContent>
<SelectItem value="desc">Récent d'abord</SelectItem>
<SelectItem value="asc">Ancien d'abord</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2 pb-2">
<Checkbox
id="showPendingFirst"
checked={showPendingFirst}
onCheckedChange={(checked) =>
setShowPendingFirst(checked as boolean)
}
/>
<Label
htmlFor="showPendingFirst"
className="cursor-pointer font-normal"
>
Afficher les "En attente" en premier
</Label>
</div>
</div>
<Table>
<TableHeader>
<TableRow>
@@ -687,16 +768,18 @@ export default function AdminPage() {
/>
</div>
<div>
<Label htmlFor="newRole">Rôle</Label>
<select
id="newRole"
value={newRole}
onChange={(e) => setNewRole(e.target.value)}
className="w-full p-2 border rounded bg-white text-black dark:bg-stone-800 dark:text-white dark:border-stone-600"
>
<option value="MEMBER">Membre</option>
<option value="ADMIN">Bureau</option>
</select>
<Label htmlFor="newRole" className="mb-2 block">
Rôle
</Label>
<Select value={newRole} onValueChange={setNewRole}>
<SelectTrigger id="newRole">
<SelectValue placeholder="Rôle" />
</SelectTrigger>
<SelectContent>
<SelectItem value="MEMBER">Membre</SelectItem>
<SelectItem value="ADMIN">Bureau</SelectItem>
</SelectContent>
</Select>
</div>
<Button type="submit">Créer</Button>
</form>
@@ -742,11 +825,10 @@ export default function AdminPage() {
<DialogDescription>
Êtes-vous sûr de vouloir supprimer cet utilisateur ?
<div className="flex items-center space-x-2 mt-2">
<input
type="checkbox"
<Checkbox
id="force"
checked={forceDelete}
onChange={(e) => setForceDelete(e.target.checked)}
onCheckedChange={(checked) => setForceDelete(checked as boolean)}
/>
<Label htmlFor="force">
Forcer la suppression même si l'utilisateur a des heures

View File

@@ -24,6 +24,7 @@ export async function GET() {
reason: true,
status: true,
userId: true,
createdAt: true,
user: { select: { email: true, firstName: true, lastName: true } },
validatedBy: {
select: { firstName: true, lastName: true, email: true },
@@ -39,6 +40,7 @@ export async function GET() {
reason: true,
status: true,
userId: true,
createdAt: true,
user: {
select: { email: true, firstName: true, lastName: true, role: true },
},

View File

@@ -0,0 +1,32 @@
"use client"
import * as React from "react"
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
import { CheckIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary 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",
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
)
}
export { Checkbox }

187
components/ui/select.tsx Normal file
View File

@@ -0,0 +1,187 @@
"use client"
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { cn } from "@/lib/utils"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"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",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
align = "center",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
className={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 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={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",
className
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View File

@@ -10,14 +10,17 @@
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"test:e2e": "cypress run",
"ci:run": "pnpm build && npx start-server-and-test start http://localhost:3000 test:e2e"
"ci:run": "pnpm build && npx start-server-and-test start http://localhost:3000 test:e2e",
"db:migrate": "prisma migrate deploy"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@prisma/client": "6.17.1",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.3",
"@types/bcrypt": "^6.0.0",
"bcrypt": "^6.0.0",

159
pnpm-lock.yaml generated
View File

@@ -18,6 +18,9 @@ importers:
'@prisma/client':
specifier: 6.17.1
version: 6.17.1(prisma@6.17.1(typescript@5.9.3))(typescript@5.9.3)
'@radix-ui/react-checkbox':
specifier: ^1.3.3
version: 1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-dialog':
specifier: ^1.1.15
version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
@@ -27,6 +30,9 @@ importers:
'@radix-ui/react-popover':
specifier: ^1.1.15
version: 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-select':
specifier: ^2.2.6
version: 2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-slot':
specifier: ^1.2.3
version: 1.2.3(@types/react@19.2.2)(react@19.2.0)
@@ -414,6 +420,9 @@ packages:
'@prisma/get-platform@6.17.1':
resolution: {integrity: sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==}
'@radix-ui/number@1.1.1':
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
'@radix-ui/primitive@1.1.3':
resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==}
@@ -430,6 +439,32 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-checkbox@1.3.3':
resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==}
peerDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-collection@1.1.7':
resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==}
peerDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.2':
resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==}
peerDependencies:
@@ -461,6 +496,15 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-direction@1.1.1':
resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==}
peerDependencies:
'@types/react': 19.2.2
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dismissable-layer@1.1.11':
resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==}
peerDependencies:
@@ -583,6 +627,19 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-select@2.2.6':
resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==}
peerDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.2.3':
resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==}
peerDependencies:
@@ -637,6 +694,15 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-previous@1.1.1':
resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==}
peerDependencies:
'@types/react': 19.2.2
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-rect@1.1.1':
resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==}
peerDependencies:
@@ -655,6 +721,19 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-visually-hidden@1.2.3':
resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==}
peerDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
@@ -2417,6 +2496,8 @@ snapshots:
dependencies:
'@prisma/debug': 6.17.1
'@radix-ui/number@1.1.1': {}
'@radix-ui/primitive@1.1.3': {}
'@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
@@ -2428,6 +2509,34 @@ snapshots:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2(@types/react@19.2.2)
'@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.2)(react@19.2.0)
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
optionalDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2(@types/react@19.2.2)
'@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0)
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
optionalDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2(@types/react@19.2.2)
'@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)':
dependencies:
react: 19.2.0
@@ -2462,6 +2571,12 @@ snapshots:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2(@types/react@19.2.2)
'@radix-ui/react-direction@1.1.1(@types/react@19.2.2)(react@19.2.0)':
dependencies:
react: 19.2.0
optionalDependencies:
'@types/react': 19.2.2
'@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.3
@@ -2578,6 +2693,35 @@ snapshots:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2(@types/react@19.2.2)
'@radix-ui/react-select@2.2.6(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@radix-ui/number': 1.1.1
'@radix-ui/primitive': 1.1.3
'@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-context': 1.1.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-id': 1.1.1(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
'@radix-ui/react-slot': 1.2.3(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.2)(react@19.2.0)
'@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
aria-hidden: 1.2.6
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
react-remove-scroll: 2.7.1(@types/react@19.2.2)(react@19.2.0)
optionalDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2(@types/react@19.2.2)
'@radix-ui/react-slot@1.2.3(@types/react@19.2.2)(react@19.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0)
@@ -2619,6 +2763,12 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.2
'@radix-ui/react-use-previous@1.1.1(@types/react@19.2.2)(react@19.2.0)':
dependencies:
react: 19.2.0
optionalDependencies:
'@types/react': 19.2.2
'@radix-ui/react-use-rect@1.1.1(@types/react@19.2.2)(react@19.2.0)':
dependencies:
'@radix-ui/rect': 1.1.1
@@ -2633,6 +2783,15 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.2
'@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
optionalDependencies:
'@types/react': 19.2.2
'@types/react-dom': 19.2.2(@types/react@19.2.2)
'@radix-ui/rect@1.1.1': {}
'@standard-schema/spec@1.0.0': {}

View File

@@ -0,0 +1,21 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Hour" (
"id" TEXT NOT NULL PRIMARY KEY,
"date" DATETIME NOT NULL,
"duration" INTEGER NOT NULL,
"reason" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'PENDING',
"validatedById" TEXT,
"userId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Hour_validatedById_fkey" FOREIGN KEY ("validatedById") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Hour_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
INSERT INTO "new_Hour" ("date", "duration", "id", "reason", "status", "userId", "validatedById") SELECT "date", "duration", "id", "reason", "status", "userId", "validatedById" FROM "Hour";
DROP TABLE "Hour";
ALTER TABLE "new_Hour" RENAME TO "Hour";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@@ -33,6 +33,8 @@ model Hour {
validatedById String?
user User @relation("UserHours", fields: [userId], references: [id])
userId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt @default(now())
}
model ClubSettings {