fix: update import paths for components and utilities to relative paths

This commit is contained in:
2025-10-17 22:41:52 +02:00
parent 02643e3702
commit 337b07f3c5
33 changed files with 3050 additions and 70 deletions

44
.dockerignore Normal file
View File

@@ -0,0 +1,44 @@
node_modules
.next
.env*
.git
.gitignore
README.md
.dockerignore
*.log
.DS_Store
.vscode
.idea
coverage
.nyc_output
.cache
dist
build
*.tgz
*.tar.gz
.DS_Store
Thumbs.db
*.swp
*.swo
*~
.npm
.yarn-integrity
.pnpm-debug.log*
.npmrc
.yarnrc
.pnpmrc
.prettierrc
.eslintrc
.eslintignore
.prettierignore
.stylelintrc
postcss.config.js
tailwind.config.js
next.config.js
next-env.d.ts
tsconfig.json
prisma/dev.db
INITIAL_SETUP.md
API_DOCS.md
INDICATIONS_POUR_LLMS.md

21
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/.github/workflow" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/" # Location of Dockerfile
schedule:
interval: "weekly"

43
.github/workflows/docker-build.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: Build and Push Docker Image
on:
push:
branches: [ main ]
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare Docker tags
id: prep
run: |
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]')
echo "owner=$OWNER" >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
ghcr.io/${{ steps.prep.outputs.owner }}/site-comptage-heure:latest
ghcr.io/${{ steps.prep.outputs.owner }}/site-comptage-heure:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max

5
.gitignore vendored
View File

@@ -44,13 +44,10 @@ next-env.d.ts
.idea/*
.idea
INDICATIONS_POUR_LLMS.md
public/uploads
public/uploads/*
prisma/dev.db
pnpm-lock.yaml
.vscode/
.vscode/*
.vscode
.vscode

View File

@@ -1,10 +1,13 @@
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY pnpm-lock.yaml ./
RUN npm install -g pnpm
RUN pnpm install --frozen-lockfile
COPY . .
ENV NEXTAUTH_SECRET=your_super_secret_key_here_change_in_production
RUN npx prisma generate
RUN npm run build
RUN npx prisma db push
RUN pnpm run build
EXPOSE 3000
CMD ["npm", "start"]
CMD ["pnpm", "start"]

80
INDICATIONS_POUR_LLMS.md Normal file
View File

@@ -0,0 +1,80 @@
# Indications pour les LLMs - Projet Site de Comptage d'Heures
## Vue d'ensemble du projet
Ce projet est une application web Next.js pour le suivi des heures travaillées dans un club ou une organisation. Elle permet aux utilisateurs de saisir leurs heures, aux administrateurs de valider ou rejeter les demandes, et de gérer les utilisateurs et les paramètres du club.
## Technologies utilisées
- **Framework**: Next.js 15 avec App Router
- **Base de données**: SQLite avec Prisma ORM
- **Authentification**: NextAuth.js
- **UI**: Radix UI avec Tailwind CSS
- **Langage**: TypeScript
- **Gestionnaire de paquets**: pnpm
- **Déploiement**: Docker (optionnel)
## Structure du projet
- `app/`: Pages et API routes Next.js
- `components/`: Composants réutilisables
- `lib/`: Utilitaires, configuration Prisma et auth
- `prisma/`: Schéma de base de données et migrations
- `public/`: Assets statiques
- `types/`: Types TypeScript personnalisés
## Modèles de données
- **User**: Utilisateurs avec email, mot de passe, rôle, prénom, nom
- **Hour**: Entrées d'heures avec date, durée, raison, statut
- **ClubSettings**: Paramètres du club (nom, logo)
## Rôles utilisateurs
- **MEMBER**: Peut saisir et voir ses propres heures
- **ADMIN**: Peut valider/rejeter les heures de tous, gérer les paramètres
- **SUPER_ADMIN**: Peut créer des comptes admin/membre, supprimer des utilisateurs
## Commandes importantes
- `pnpm dev`: Lancer le serveur de développement
- `pnpm build`: Construire pour la production
- `pnpm start`: Lancer en production
- `npx prisma db push`: Appliquer les changements de schéma à la DB
- `npx prisma generate`: Régénérer le client Prisma
- `npx prisma studio`: Interface graphique pour la DB
## Points d'attention pour les LLMs
- Utiliser pnpm pour toutes les commandes npm
- Le projet utilise SQLite, donc pas de migrations Prisma traditionnelles ; utiliser `db push`
- Les heures sont stockées en minutes (entier)
- L'authentification utilise NextAuth avec des sessions
- Les fichiers uploadés vont dans `public/uploads/`
- Le premier compte créé est SUPER_ADMIN
- Vérifier les erreurs Prisma après changements de schéma
## API Routes
- `/api/auth/[...nextauth]`: Authentification
- `/api/hours`: CRUD des heures
- `/api/settings`: Gestion des paramètres club
- `/api/upload`: Upload de fichiers
- `/api/export`: Export CSV/Excel
- `/api/users/[id]`: Gestion utilisateurs
## Composants UI
Utilise shadcn/ui basé sur Radix UI pour une cohérence.
## Sécurité
- Mots de passe hashés avec bcrypt
- Sessions sécurisées
- Rôles et permissions strictes
## Développement
- Utiliser TypeScript strictement
- Respecter les conventions de nommage
- Tester les changements dans le navigateur et via les API

View File

@@ -33,17 +33,20 @@ Une application web moderne pour la gestion des heures travaillées dans un club
## Installation
1. **Cloner le repository**
```bash
git clone https://github.com/breizhhardware/site-comptage-heure.git
cd site-comptage-heure
```
2. **Installer les dépendances**
```bash
pnpm install
```
3. **Configuration de la base de données**
```bash
# Appliquer le schéma Prisma
npx prisma db push
@@ -53,8 +56,9 @@ Une application web moderne pour la gestion des heures travaillées dans un club
```
4. **Variables d'environnement**
Créer un fichier `.env.local` à la racine :
```env
NEXTAUTH_SECRET=votre-secret-très-long-et-sécurisé
NEXTAUTH_URL=http://localhost:3000
@@ -68,6 +72,7 @@ Une application web moderne pour la gestion des heures travaillées dans un club
## Utilisation
### Démarrage en développement
```bash
pnpm dev
```
@@ -75,6 +80,7 @@ pnpm dev
Ouvrir [http://localhost:3000](http://localhost:3000)
### Première connexion
- Utilisez les identifiants du Super Administrateur créé
- Configurez le nom et le logo du club dans l'admin
- Créez des comptes pour les administrateurs et membres
@@ -126,6 +132,7 @@ Ouvrir [http://localhost:3000](http://localhost:3000)
### Avec Docker
1. Build l'image :
```bash
docker build -t comptage-heures .
```
@@ -134,6 +141,15 @@ Ouvrir [http://localhost:3000](http://localhost:3000)
```bash
docker run -p 3000:3000 comptage-heures
```
3. Créer un Super Administrateur à l'intérieur du container :
```bash
docker exec -it <container_id> sh
```
Puis exécuter (pensez à modifier le nom d'utilisateur et le mot de passe si nécessaire) :
```bash
node scripts/create-super-admin.js
```
## Contribution

View File

@@ -3,9 +3,9 @@
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '../../components/ui/button';
import { Input } from '../../components/ui/input';
import { Label } from '../../components/ui/label';
import {
Table,
TableBody,
@@ -13,8 +13,8 @@ import {
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
} from '../../components/ui/table';
import { Card, CardContent, CardHeader, CardTitle } from '../../components/ui/card';
import {
Dialog,
DialogContent,
@@ -22,9 +22,9 @@ import {
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
} from '../../components/ui/dialog';
import { toast } from 'sonner';
import { DatePicker } from '@/components/ui/date-picker';
import { DatePicker } from '../../components/ui/date-picker';
import { format } from 'date-fns';
interface Hour {
@@ -94,7 +94,7 @@ export default function AdminPage() {
}
};
const handleValidate = async (id: number, status: string) => {
const handleValidate = async (id: string, status: string) => {
await fetch(`/api/hours/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
@@ -179,7 +179,7 @@ export default function AdminPage() {
}
};
const handleDelete = async (id: number) => {
const handleDelete = async (id: string) => {
await fetch(`/api/hours/${id}`, {
method: 'DELETE',
});
@@ -330,14 +330,14 @@ export default function AdminPage() {
<Button
onClick={() => handleValidate(hour.id, 'VALIDATED')}
className="mr-2"
disabled={hour.userId === session.user.id}
disabled={hour.userId === session?.user?.id}
>
Valider
</Button>
<Button
onClick={() => handleDelete(hour.id)}
variant="destructive"
disabled={hour.userId === session.user.id}
disabled={hour.userId === session?.user?.id}
>
Supprimer
</Button>

View File

@@ -1,5 +1,5 @@
import NextAuth from 'next-auth';
import { authOptions } from '@/lib/auth';
import { authOptions } from '../../../../lib/auth'
const handler = NextAuth(authOptions);

View File

@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { authOptions } from '../../../../lib/auth';
import { prisma } from '../../../../lib/prisma';
import bcrypt from 'bcryptjs';
import { v4 as uuidv4 } from 'uuid';

View File

@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { authOptions } from '../../../lib/auth';
import { prisma } from '../../../lib/prisma';
import * as csvWriter from 'csv-writer';
import ExcelJS from 'exceljs';

View File

@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { authOptions } from '../../../../lib/auth';
import { prisma } from '../../../../lib/prisma';
export async function PUT(
request: NextRequest,

View File

@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { authOptions } from '../../../lib/auth';
import { prisma } from '../../../lib/prisma';
import { v4 as uuidv4 } from 'uuid';
export async function GET() {

View File

@@ -1,7 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { authOptions } from '../../../lib/auth';
import { prisma } from '../../../lib/prisma';
export async function GET() {
const settings = await prisma.clubSettings.findFirst();

View File

@@ -1,21 +1,21 @@
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { authOptions } from '../../../../lib/auth';
import { prisma } from '../../../../lib/prisma';
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } },
{ params }: { params: Promise<{ id: string }> },
) {
const session = await getServerSession(authOptions);
if (!session || session.user.role !== 'SUPER_ADMIN') {
return NextResponse.json({ error: 'Accès refusé' }, { status: 403 });
}
const userId = params.id;
const { id } = await params;
const user = await prisma.user.findUnique({
where: { id: userId },
where: { id },
include: { hours: true },
});
@@ -34,7 +34,7 @@ export async function DELETE(
}
await prisma.user.delete({
where: { id: userId },
where: { id },
});
return NextResponse.json({ message: 'Utilisateur supprimé' });

View File

@@ -3,9 +3,9 @@
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '../../components/ui/button';
import { Input } from '../../components/ui/input';
import { Label } from '../../components/ui/label';
import {
Table,
TableBody,
@@ -13,8 +13,8 @@ import {
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
} from '../../components/ui/table';
import { Card, CardContent, CardHeader, CardTitle } from '../../components/ui/card';
interface Hour {
id: number;

View File

@@ -1,9 +1,9 @@
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import { Providers } from '@/components/providers';
import Header from '@/components/Header';
import { Toaster } from '@/components/ui/sonner';
import { Providers } from '../components/providers';
import Header from '../components/Header';
import { Toaster } from '../components/ui/sonner';
const geistSans = Geist({
variable: '--font-geist-sans',

View File

@@ -3,10 +3,10 @@
import { useState, useEffect } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '../../components/ui/button';
import { Input } from '../../components/ui/input';
import { Label } from '../../components/ui/label';
import { Card, CardContent, CardHeader, CardTitle } from '../../components/ui/card';
export default function LoginPage() {
const [email, setEmail] = useState('');

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
import { cn } from '../../lib/utils';
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all cursor-pointer 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",

View File

@@ -8,8 +8,8 @@ import {
} from 'lucide-react';
import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
import { cn } from '@/lib/utils';
import { Button, buttonVariants } from '@/components/ui/button';
import { cn } from '../../lib/utils';
import { Button, buttonVariants } from './button';
function Calendar({
className,

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import { cn } from '../../lib/utils';
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (

View File

@@ -4,14 +4,14 @@ import * as React from 'react';
import { format } from 'date-fns';
import { Calendar as CalendarIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Calendar } from '@/components/ui/calendar';
import { cn } from '../../lib/utils';
import { Button } from './button';
import { Calendar } from './calendar';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/components/ui/popover';
} from './popover';
interface DatePickerProps {
date: Date | undefined;

View File

@@ -4,7 +4,7 @@ import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { XIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
import { cn } from '../../lib/utils';
function Dialog({
...props

View File

@@ -13,8 +13,8 @@ import {
type FieldValues,
} from 'react-hook-form';
import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';
import { cn } from '../../lib/utils';
import { Label } from './label';
const Form = FormProvider;

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import { cn } from '../../lib/utils';
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (

View File

@@ -3,7 +3,7 @@
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cn } from '@/lib/utils';
import { cn } from '../../lib/utils';
function Label({
className,

View File

@@ -3,7 +3,7 @@
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/lib/utils';
import { cn } from '../../lib/utils';
function Popover({
...props

View File

@@ -2,7 +2,7 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
import { cn } from '../../lib/utils';
function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (

View File

@@ -1,9 +1,10 @@
version: '3.8'
services:
app:
build: .
ports:
- '3000:3000'
volumes:
- ./data:/app/prisma/data # Pour la DB SQLite
- ./data:/app/prisma/data
environment:
- NEXTAUTH_SECRET=your_secret_key

View File

@@ -1,7 +1,11 @@
import type { NextConfig } from 'next';
import path from 'path';
const nextConfig: NextConfig = {
/* config options here */
webpack: (config) => {
config.resolve.alias['@'] = path.resolve(__dirname);
return config;
},
};
export default nextConfig;

View File

@@ -1,10 +1,10 @@
{
"name": "my-app",
"version": "0.1.0",
"name": "site-comptage-heure",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"build": "next build",
"start": "next start",
"format": "prettier --write ."
},

2769
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
const { PrismaClient } = require('@prisma/client');
const bcrypt = require('bcryptjs');
const { v4: uuidv4 } = require('uuid');
const prisma = new PrismaClient();
@@ -12,6 +13,7 @@ async function main() {
email: 'test@test.fr',
password: hashedPassword,
role: 'SUPER_ADMIN',
id: uuidv4(), // Ajout d'un identifiant unique
},
});
console.log('Super admin created');