Ajout de la prise en charge de PostgreSQL, configuration de Prisma, et création d'une API pour gérer les calendriers par défaut et le partage.
This commit is contained in:
parent
c967b58981
commit
9f0b49a0cc
6
.gitignore
vendored
6
.gitignore
vendored
@ -16,4 +16,8 @@ copy.sh
|
|||||||
start.sh
|
start.sh
|
||||||
|
|
||||||
# Playbook développement
|
# Playbook développement
|
||||||
ansible/playbooks/dev
|
ansible/playbooks/dev
|
||||||
|
|
||||||
|
# Migrations Prisma
|
||||||
|
#TODO: Supprimer en prod
|
||||||
|
front/prisma/migrations
|
||||||
@ -103,6 +103,15 @@
|
|||||||
retries: 3
|
retries: 3
|
||||||
delay: 5
|
delay: 5
|
||||||
|
|
||||||
|
#TODO: Supprimer en prod
|
||||||
|
- name: Supprimer tous les conteneurs
|
||||||
|
command: "docker compose down -v"
|
||||||
|
register: rm_status
|
||||||
|
until: rm_status is success
|
||||||
|
retries: 3
|
||||||
|
delay: 5
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: Lancer le service Traefik
|
- name: Lancer le service Traefik
|
||||||
command: "docker compose up -d --build --remove-orphans {{ traefik_service_name }}"
|
command: "docker compose up -d --build --remove-orphans {{ traefik_service_name }}"
|
||||||
args:
|
args:
|
||||||
|
|||||||
33
ansible/playbooks/6_postgresql.yml
Normal file
33
ansible/playbooks/6_postgresql.yml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
- name: Installer et configurer PostgreSQL
|
||||||
|
hosts: servers
|
||||||
|
become: true
|
||||||
|
gather_facts: true
|
||||||
|
|
||||||
|
vars:
|
||||||
|
git_repo: "https://gite.slm-lab.net/Chabdeltsang/Neah-Enkun.git"
|
||||||
|
git_dest: "/opt/Neah-Enkun"
|
||||||
|
git_branch: "master"
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: Cloner le dépôt Git
|
||||||
|
git:
|
||||||
|
repo: "{{ git_repo }}"
|
||||||
|
dest: "{{ git_dest }}"
|
||||||
|
version: "{{ git_branch }}"
|
||||||
|
update: true
|
||||||
|
force: true
|
||||||
|
register: git_status
|
||||||
|
until: git_status is success
|
||||||
|
retries: 3
|
||||||
|
delay: 5
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Lancer le service PostgreSQL
|
||||||
|
command: "docker compose up -d --build --remove-orphans postgresql"
|
||||||
|
args:
|
||||||
|
chdir: "{{ git_dest }}"
|
||||||
|
register: postgresql_launch
|
||||||
|
until: postgresql_launch is success
|
||||||
|
retries: 3
|
||||||
|
delay: 5
|
||||||
@ -17,6 +17,7 @@ PLAYBOOKS=(
|
|||||||
"playbooks/dev/3_1_keycloak_dev.yml"
|
"playbooks/dev/3_1_keycloak_dev.yml"
|
||||||
"playbooks/4_mysql.yml"
|
"playbooks/4_mysql.yml"
|
||||||
"playbooks/5_nextcloud.yml"
|
"playbooks/5_nextcloud.yml"
|
||||||
|
"playbooks/6_postgresql.yml"
|
||||||
"playbooks/0_front.yml"
|
"playbooks/0_front.yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -7,4 +7,6 @@ KEYCLOAK_REALM=master
|
|||||||
KEYCLOAK_ISSUER=http://172.16.32.141:8090/realms/master
|
KEYCLOAK_ISSUER=http://172.16.32.141:8090/realms/master
|
||||||
KEYCLOAK_BASE_URL=http://172.16.32.141:8090
|
KEYCLOAK_BASE_URL=http://172.16.32.141:8090
|
||||||
|
|
||||||
NEXTCLOUD_URL=http://cloud.neah.local
|
NEXTCLOUD_URL=http://cloud.neah.local
|
||||||
|
|
||||||
|
DATABASE_URL="postgresql://enkun:183d9ad665c9257703c2e0703f111d240266a56b33e10df04fb8c565e55e0b94@172.16.32.141:5432/enkun?schema=public"
|
||||||
@ -13,6 +13,10 @@ RUN npm ci
|
|||||||
# Copy the rest of the application
|
# Copy the rest of the application
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Initialize Prisma
|
||||||
|
RUN npx prisma generate
|
||||||
|
RUN npx prisma migrate dev --name init
|
||||||
|
|
||||||
# Build the Next.js application
|
# Build the Next.js application
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|||||||
270
front/app/api/calendars/[id]/events/[eventId]/route.ts
Normal file
270
front/app/api/calendars/[id]/events/[eventId]/route.ts
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the GET request to retrieve a specific event from a calendar.
|
||||||
|
*
|
||||||
|
* @param req - The incoming Next.js request object.
|
||||||
|
* @param params - An object containing the route parameters.
|
||||||
|
* @param params.id - The ID of the calendar.
|
||||||
|
* @param params.eventId - The ID of the event.
|
||||||
|
* @returns A JSON response containing the event data or an error message.
|
||||||
|
*
|
||||||
|
* The function performs the following steps:
|
||||||
|
* 1. Checks if the user is authenticated.
|
||||||
|
* 2. Verifies that the calendar exists and belongs to the authenticated user.
|
||||||
|
* 3. Verifies that the event exists and belongs to the specified calendar.
|
||||||
|
* 4. Returns the event data if all checks pass.
|
||||||
|
*
|
||||||
|
* Possible error responses:
|
||||||
|
* - 401: User is not authenticated.
|
||||||
|
* - 403: User is not authorized to access the calendar.
|
||||||
|
* - 404: Calendar or event not found.
|
||||||
|
* - 500: Server error occurred while retrieving the event.
|
||||||
|
*/
|
||||||
|
export async function GET(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string; eventId: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier que le calendrier appartient à l'utilisateur
|
||||||
|
const calendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!calendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await prisma.event.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.eventId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!event) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Événement non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que l'événement appartient bien au calendrier
|
||||||
|
if (event.calendarId !== params.id) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Événement non trouvé dans ce calendrier" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(event);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération de l'événement:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the PUT request to update an event in a calendar.
|
||||||
|
*
|
||||||
|
* @param req - The incoming request object.
|
||||||
|
* @param params - The route parameters containing the calendar ID and event ID.
|
||||||
|
* @returns A JSON response indicating the result of the update operation.
|
||||||
|
*
|
||||||
|
* The function performs the following steps:
|
||||||
|
* 1. Retrieves the server session to check if the user is authenticated.
|
||||||
|
* 2. Verifies that the calendar belongs to the authenticated user.
|
||||||
|
* 3. Checks if the event exists and belongs to the specified calendar.
|
||||||
|
* 4. Validates the request payload to ensure required fields are present.
|
||||||
|
* 5. Updates the event with the provided data.
|
||||||
|
* 6. Returns the updated event or an appropriate error response.
|
||||||
|
*
|
||||||
|
* Possible error responses:
|
||||||
|
* - 401: User is not authenticated.
|
||||||
|
* - 403: User is not authorized to update the calendar.
|
||||||
|
* - 404: Calendar or event not found.
|
||||||
|
* - 400: Validation error for missing required fields.
|
||||||
|
* - 500: Server error during the update process.
|
||||||
|
*/
|
||||||
|
export async function PUT(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string; eventId: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier que le calendrier appartient à l'utilisateur
|
||||||
|
const calendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!calendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que l'événement existe et appartient au calendrier
|
||||||
|
const existingEvent = await prisma.event.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.eventId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingEvent) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Événement non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingEvent.calendarId !== params.id) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Événement non trouvé dans ce calendrier" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, description, start, end, location, isAllDay } =
|
||||||
|
await req.json();
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!title) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Le titre est requis" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!start || !end) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Les dates de début et de fin sont requises" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedEvent = await prisma.event.update({
|
||||||
|
where: {
|
||||||
|
id: params.eventId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
start: new Date(start),
|
||||||
|
end: new Date(end),
|
||||||
|
location,
|
||||||
|
isAllDay: isAllDay || false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(updatedEvent);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la mise à jour de l'événement:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the DELETE request to remove an event from a calendar.
|
||||||
|
*
|
||||||
|
* @param req - The incoming Next.js request object.
|
||||||
|
* @param params - An object containing the parameters from the request URL.
|
||||||
|
* @param params.id - The ID of the calendar.
|
||||||
|
* @param params.eventId - The ID of the event to be deleted.
|
||||||
|
* @returns A JSON response indicating the result of the deletion operation.
|
||||||
|
*
|
||||||
|
* @throws Will return a 401 status if the user is not authenticated.
|
||||||
|
* @throws Will return a 404 status if the calendar or event is not found.
|
||||||
|
* @throws Will return a 403 status if the user is not authorized to delete the event.
|
||||||
|
* @throws Will return a 500 status if there is a server error during the deletion process.
|
||||||
|
*/
|
||||||
|
export async function DELETE(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string; eventId: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier que le calendrier appartient à l'utilisateur
|
||||||
|
const calendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!calendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérifier que l'événement existe et appartient au calendrier
|
||||||
|
const existingEvent = await prisma.event.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.eventId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingEvent) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Événement non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingEvent.calendarId !== params.id) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Événement non trouvé dans ce calendrier" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.event.delete({
|
||||||
|
where: {
|
||||||
|
id: params.eventId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return new NextResponse(null, { status: 204 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la suppression de l'événement:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
171
front/app/api/calendars/[id]/events/route.ts
Normal file
171
front/app/api/calendars/[id]/events/route.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the GET request to retrieve events for a specific calendar.
|
||||||
|
*
|
||||||
|
* @param req - The incoming request object.
|
||||||
|
* @param params - An object containing the route parameters.
|
||||||
|
* @param params.id - The ID of the calendar.
|
||||||
|
* @returns A JSON response containing the events or an error message.
|
||||||
|
*
|
||||||
|
* The function performs the following steps:
|
||||||
|
* 1. Retrieves the server session to check if the user is authenticated.
|
||||||
|
* 2. Verifies that the calendar exists and belongs to the authenticated user.
|
||||||
|
* 3. Retrieves and filters events based on optional date parameters (`start` and `end`).
|
||||||
|
* 4. Returns the filtered events in ascending order of their start date.
|
||||||
|
*
|
||||||
|
* Possible response statuses:
|
||||||
|
* - 200: Successfully retrieved events.
|
||||||
|
* - 401: User is not authenticated.
|
||||||
|
* - 403: User is not authorized to access the calendar.
|
||||||
|
* - 404: Calendar not found.
|
||||||
|
* - 500: Server error occurred while retrieving events.
|
||||||
|
*/
|
||||||
|
export async function GET(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier que le calendrier appartient à l'utilisateur
|
||||||
|
const calendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!calendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer les paramètres de filtrage de date s'ils existent
|
||||||
|
const { searchParams } = new URL(req.url);
|
||||||
|
const startParam = searchParams.get("start");
|
||||||
|
const endParam = searchParams.get("end");
|
||||||
|
|
||||||
|
let whereClause: any = {
|
||||||
|
calendarId: params.id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startParam && endParam) {
|
||||||
|
whereClause.AND = [
|
||||||
|
{
|
||||||
|
start: {
|
||||||
|
lte: new Date(endParam),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
end: {
|
||||||
|
gte: new Date(startParam),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = await prisma.event.findMany({
|
||||||
|
where: whereClause,
|
||||||
|
orderBy: {
|
||||||
|
start: "asc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(events);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération des événements:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the creation of a new event for a specific calendar.
|
||||||
|
*
|
||||||
|
* @param req - The incoming request object.
|
||||||
|
* @param params - An object containing the route parameters.
|
||||||
|
* @param params.id - The ID of the calendar to which the event will be added.
|
||||||
|
* @returns A JSON response with the created event data or an error message.
|
||||||
|
*
|
||||||
|
* @throws {401} If the user is not authenticated.
|
||||||
|
* @throws {404} If the specified calendar is not found.
|
||||||
|
* @throws {403} If the user is not authorized to add events to the specified calendar.
|
||||||
|
* @throws {400} If the required fields (title, start, end) are missing.
|
||||||
|
* @throws {500} If there is a server error during event creation.
|
||||||
|
*/
|
||||||
|
export async function POST(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const calendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!calendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, description, start, end, location, isAllDay } =
|
||||||
|
await req.json();
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!title) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Le titre est requis" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!start || !end) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Les dates de début et de fin sont requises" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = await prisma.event.create({
|
||||||
|
data: {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
start: new Date(start),
|
||||||
|
end: new Date(end),
|
||||||
|
location,
|
||||||
|
isAllDay: isAllDay || false,
|
||||||
|
calendarId: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(event, { status: 201 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la création de l'événement:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
181
front/app/api/calendars/[id]/route.ts
Normal file
181
front/app/api/calendars/[id]/route.ts
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles GET requests to retrieve a calendar by its ID.
|
||||||
|
*
|
||||||
|
* @param req - The incoming request object.
|
||||||
|
* @param params - An object containing the route parameters.
|
||||||
|
* @param params.id - The ID of the calendar to retrieve.
|
||||||
|
* @returns A JSON response containing the calendar data if found and authorized,
|
||||||
|
* or an error message with the appropriate HTTP status code.
|
||||||
|
*
|
||||||
|
* - 401: If the user is not authenticated.
|
||||||
|
* - 403: If the user is not authorized to access the calendar.
|
||||||
|
* - 404: If the calendar is not found.
|
||||||
|
* - 500: If there is a server error during the retrieval process.
|
||||||
|
*/
|
||||||
|
export async function GET(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const calendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!calendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vérification que l'utilisateur est bien le propriétaire
|
||||||
|
if (calendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(calendar);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération du calendrier:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the PUT request to update a calendar.
|
||||||
|
*
|
||||||
|
* @param req - The incoming request object.
|
||||||
|
* @param params - An object containing the route parameters.
|
||||||
|
* @param params.id - The ID of the calendar to update.
|
||||||
|
* @returns A JSON response with the updated calendar data or an error message.
|
||||||
|
*
|
||||||
|
* @throws {401} If the user is not authenticated.
|
||||||
|
* @throws {404} If the calendar is not found.
|
||||||
|
* @throws {403} If the user is not authorized to update the calendar.
|
||||||
|
* @throws {400} If the calendar name is not provided.
|
||||||
|
* @throws {500} If there is a server error during the update process.
|
||||||
|
*/
|
||||||
|
export async function PUT(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier que le calendrier existe et appartient à l'utilisateur
|
||||||
|
const existingCalendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingCalendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingCalendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { name, color, description } = await req.json();
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!name) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Le nom du calendrier est requis" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedCalendar = await prisma.calendar.update({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
color,
|
||||||
|
description,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(updatedCalendar);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la mise à jour du calendrier:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the DELETE request to remove a calendar by its ID.
|
||||||
|
*
|
||||||
|
* @param req - The incoming Next.js request object.
|
||||||
|
* @param params - An object containing the route parameters.
|
||||||
|
* @param params.id - The ID of the calendar to be deleted.
|
||||||
|
* @returns A JSON response indicating the result of the deletion operation.
|
||||||
|
*
|
||||||
|
* - If the user is not authenticated, returns a 401 status with an error message.
|
||||||
|
* - If the calendar does not exist, returns a 404 status with an error message.
|
||||||
|
* - If the calendar does not belong to the authenticated user, returns a 403 status with an error message.
|
||||||
|
* - If the calendar is successfully deleted, returns a 204 status with no content.
|
||||||
|
* - If an error occurs during the deletion process, returns a 500 status with an error message.
|
||||||
|
*/
|
||||||
|
export async function DELETE(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier que le calendrier existe et appartient à l'utilisateur
|
||||||
|
const existingCalendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingCalendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingCalendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.calendar.delete({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return new NextResponse(null, { status: 204 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la suppression du calendrier:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
53
front/app/api/calendars/[id]/share/route.ts
Normal file
53
front/app/api/calendars/[id]/share/route.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
// Non testé, généré automatiquement par IA
|
||||||
|
export async function POST(
|
||||||
|
req: NextRequest,
|
||||||
|
{ params }: { params: { id: string } }
|
||||||
|
) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier que le calendrier appartient à l'utilisateur
|
||||||
|
const calendar = await prisma.calendar.findUnique({
|
||||||
|
where: {
|
||||||
|
id: params.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!calendar) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Calendrier non trouvé" },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (calendar.userId !== session.user.username) {
|
||||||
|
return NextResponse.json({ error: "Non autorisé" }, { status: 403 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Générer un token de partage
|
||||||
|
const shareToken = crypto.randomBytes(32).toString("hex");
|
||||||
|
|
||||||
|
// Dans une implémentation réelle, on stockerait ce token dans la base de données
|
||||||
|
// avec une date d'expiration et des permissions
|
||||||
|
|
||||||
|
const shareUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/calendars/shared/${shareToken}`;
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
shareUrl,
|
||||||
|
shareToken,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la création du lien de partage:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
57
front/app/api/calendars/default/route.ts
Normal file
57
front/app/api/calendars/default/route.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the creation of a default calendar for an authenticated user.
|
||||||
|
*
|
||||||
|
* This function checks if the user already has a default calendar named "Calendrier principal".
|
||||||
|
* If such a calendar exists, it returns the existing calendar.
|
||||||
|
* Otherwise, it creates a new default calendar for the user.
|
||||||
|
*
|
||||||
|
* @param req - The incoming request object.
|
||||||
|
* @returns A JSON response containing the existing or newly created calendar, or an error message.
|
||||||
|
*
|
||||||
|
* @throws Will return a 401 status if the user is not authenticated.
|
||||||
|
* @throws Will return a 500 status if there is a server error during the calendar creation process.
|
||||||
|
*/
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Vérifier si l'utilisateur a déjà un calendrier par défaut
|
||||||
|
const existingCalendar = await prisma.calendar.findFirst({
|
||||||
|
where: {
|
||||||
|
userId: session.user.username,
|
||||||
|
name: "Calendrier principal",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingCalendar) {
|
||||||
|
return NextResponse.json(existingCalendar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Créer un calendrier par défaut
|
||||||
|
const calendar = await prisma.calendar.create({
|
||||||
|
data: {
|
||||||
|
name: "Calendrier principal",
|
||||||
|
color: "#0082c9",
|
||||||
|
description: "Calendrier principal",
|
||||||
|
userId: session.user.username,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(calendar, { status: 201 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"Erreur lors de la création du calendrier par défaut:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
93
front/app/api/calendars/route.ts
Normal file
93
front/app/api/calendars/route.ts
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the GET request to retrieve calendars for the authenticated user.
|
||||||
|
*
|
||||||
|
* @param {NextRequest} req - The incoming request object.
|
||||||
|
* @returns {Promise<NextResponse>} - A promise that resolves to a JSON response containing the calendars or an error message.
|
||||||
|
*
|
||||||
|
* The function performs the following steps:
|
||||||
|
* 1. Retrieves the server session using `getServerSession`.
|
||||||
|
* 2. Checks if the user is authenticated by verifying the presence of `session.user.username`.
|
||||||
|
* - If not authenticated, returns a 401 response with an error message.
|
||||||
|
* 3. Attempts to fetch the calendars associated with the authenticated user from the database.
|
||||||
|
* - If successful, returns the calendars in a JSON response.
|
||||||
|
* - If an error occurs during the database query, logs the error and returns a 500 response with an error message.
|
||||||
|
*/
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const calendars = await prisma.calendar.findMany({
|
||||||
|
where: {
|
||||||
|
userId: session.user.username,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(calendars);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la récupération des calendriers:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the POST request to create a new calendar.
|
||||||
|
*
|
||||||
|
* @param {NextRequest} req - The incoming request object.
|
||||||
|
* @returns {Promise<NextResponse>} The response object containing the created calendar or an error message.
|
||||||
|
*
|
||||||
|
* @throws {Error} If there is an issue with the request or server.
|
||||||
|
*
|
||||||
|
* The function performs the following steps:
|
||||||
|
* 1. Retrieves the server session using `getServerSession`.
|
||||||
|
* 2. Checks if the user is authenticated by verifying the presence of `session.user.username`.
|
||||||
|
* 3. Parses the request body to extract `name`, `color`, and `description`.
|
||||||
|
* 4. Validates that the `name` field is provided.
|
||||||
|
* 5. Creates a new calendar entry in the database using Prisma.
|
||||||
|
* 6. Returns the created calendar with a 201 status code.
|
||||||
|
* 7. Catches and logs any errors, returning a 500 status code with an error message.
|
||||||
|
*/
|
||||||
|
export async function POST(req: NextRequest) {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user?.username) {
|
||||||
|
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { name, color, description } = await req.json();
|
||||||
|
|
||||||
|
// Validation
|
||||||
|
if (!name) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Le nom du calendrier est requis" },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const calendar = await prisma.calendar.create({
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
color: color || "#0082c9",
|
||||||
|
description,
|
||||||
|
userId: session.user.username,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return NextResponse.json(calendar, { status: 201 });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la création du calendrier:", error);
|
||||||
|
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
54
front/app/calendar/page.tsx
Normal file
54
front/app/calendar/page.tsx
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { getServerSession } from "next-auth/next";
|
||||||
|
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { CalendarClient } from "@/components/calendar/calendar-client";
|
||||||
|
|
||||||
|
export const metadata = {
|
||||||
|
title: "Enkun - Calendrier",
|
||||||
|
description: "Gérez vos rendez-vous et événements",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function CalendarPage() {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
redirect("/api/auth/signin");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Récupérer tous les calendriers de l'utilisateur
|
||||||
|
const userCalendars = await prisma.calendar.findMany({
|
||||||
|
where: {
|
||||||
|
userId: session.user.username || session.user.email,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Si aucun calendrier n'existe, en créer un par défaut
|
||||||
|
let calendars = userCalendars;
|
||||||
|
if (calendars.length === 0) {
|
||||||
|
const defaultCalendar = await prisma.calendar.create({
|
||||||
|
data: {
|
||||||
|
name: "Calendrier principal",
|
||||||
|
color: "#0082c9",
|
||||||
|
description: "Calendrier par défaut",
|
||||||
|
userId: session.user.username || session.user.email,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
calendars = [defaultCalendar];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='container mx-auto py-8'>
|
||||||
|
<div className='mb-6'>
|
||||||
|
<h1 className='text-3xl font-bold'>Calendrier</h1>
|
||||||
|
<p className='text-muted-foreground'>
|
||||||
|
Gérez vos rendez-vous et événements
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<CalendarClient initialCalendars={calendars} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
268
front/components/calendar/calendar-client.tsx
Normal file
268
front/components/calendar/calendar-client.tsx
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect, useRef } from "react";
|
||||||
|
import FullCalendar from "@fullcalendar/react";
|
||||||
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
|
import interactionPlugin from "@fullcalendar/interaction";
|
||||||
|
import frLocale from "@fullcalendar/core/locales/fr";
|
||||||
|
import { Card } from "@/components/ui/card";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import { Loader2, Plus } from "lucide-react";
|
||||||
|
import { useCalendarEvents } from "@/hooks/use-calendar-events";
|
||||||
|
import { EventDialog } from "@/components/calendar/event-dialog";
|
||||||
|
import { Calendar as CalendarType } from "@prisma/client";
|
||||||
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
|
|
||||||
|
interface CalendarClientProps {
|
||||||
|
initialCalendars: CalendarType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CalendarClient({ initialCalendars }: CalendarClientProps) {
|
||||||
|
const [calendars, setCalendars] = useState<CalendarType[]>(initialCalendars);
|
||||||
|
const [selectedCalendarId, setSelectedCalendarId] = useState<string>(
|
||||||
|
initialCalendars[0]?.id || ""
|
||||||
|
);
|
||||||
|
const [view, setView] = useState<
|
||||||
|
"dayGridMonth" | "timeGridWeek" | "timeGridDay"
|
||||||
|
>("dayGridMonth");
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
const [selectedEvent, setSelectedEvent] = useState<any>(null);
|
||||||
|
const [dateRange, setDateRange] = useState({
|
||||||
|
start: new Date(),
|
||||||
|
end: new Date(new Date().setMonth(new Date().getMonth() + 1)),
|
||||||
|
});
|
||||||
|
|
||||||
|
const calendarRef = useRef<any>(null);
|
||||||
|
const { toast } = useToast();
|
||||||
|
|
||||||
|
const {
|
||||||
|
events,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refresh,
|
||||||
|
createEvent,
|
||||||
|
updateEvent,
|
||||||
|
deleteEvent,
|
||||||
|
} = useCalendarEvents(selectedCalendarId, dateRange.start, dateRange.end);
|
||||||
|
|
||||||
|
// Mettre à jour la plage de dates lorsque la vue change
|
||||||
|
const handleDatesSet = (arg: any) => {
|
||||||
|
setDateRange({
|
||||||
|
start: arg.start,
|
||||||
|
end: arg.end,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gérer la sélection d'une plage de dates pour créer un événement
|
||||||
|
const handleDateSelect = (selectInfo: any) => {
|
||||||
|
setSelectedEvent({
|
||||||
|
start: selectInfo.startStr,
|
||||||
|
end: selectInfo.endStr,
|
||||||
|
allDay: selectInfo.allDay,
|
||||||
|
});
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gérer le clic sur un événement existant
|
||||||
|
const handleEventClick = (clickInfo: any) => {
|
||||||
|
setSelectedEvent({
|
||||||
|
id: clickInfo.event.id,
|
||||||
|
title: clickInfo.event.title,
|
||||||
|
description: clickInfo.event.extendedProps.description,
|
||||||
|
start: clickInfo.event.startStr,
|
||||||
|
end: clickInfo.event.endStr,
|
||||||
|
location: clickInfo.event.extendedProps.location,
|
||||||
|
allDay: clickInfo.event.allDay,
|
||||||
|
});
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gérer la création ou mise à jour d'un événement
|
||||||
|
const handleEventSave = async (eventData: any) => {
|
||||||
|
try {
|
||||||
|
if (eventData.id) {
|
||||||
|
await updateEvent(eventData);
|
||||||
|
toast({
|
||||||
|
title: "Événement mis à jour",
|
||||||
|
description: "L'événement a été modifié avec succès.",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createEvent({
|
||||||
|
...eventData,
|
||||||
|
calendarId: selectedCalendarId,
|
||||||
|
});
|
||||||
|
toast({
|
||||||
|
title: "Événement créé",
|
||||||
|
description: "L'événement a été ajouté au calendrier.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la sauvegarde de l'événement:", error);
|
||||||
|
toast({
|
||||||
|
title: "Erreur",
|
||||||
|
description: "Impossible d'enregistrer l'événement.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gérer la suppression d'un événement
|
||||||
|
const handleEventDelete = async (eventId: string) => {
|
||||||
|
try {
|
||||||
|
await deleteEvent(eventId);
|
||||||
|
toast({
|
||||||
|
title: "Événement supprimé",
|
||||||
|
description: "L'événement a été supprimé du calendrier.",
|
||||||
|
});
|
||||||
|
setIsDialogOpen(false);
|
||||||
|
refresh();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la suppression de l'événement:", error);
|
||||||
|
toast({
|
||||||
|
title: "Erreur",
|
||||||
|
description: "Impossible de supprimer l'événement.",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Changer la vue du calendrier
|
||||||
|
const handleViewChange = (
|
||||||
|
newView: "dayGridMonth" | "timeGridWeek" | "timeGridDay"
|
||||||
|
) => {
|
||||||
|
setView(newView);
|
||||||
|
if (calendarRef.current) {
|
||||||
|
const calendarApi = calendarRef.current.getApi();
|
||||||
|
calendarApi.changeView(newView);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Changer le calendrier sélectionné
|
||||||
|
const handleCalendarChange = (calendarId: string) => {
|
||||||
|
setSelectedCalendarId(calendarId);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='space-y-4'>
|
||||||
|
{/* Options et filtres du calendrier */}
|
||||||
|
<div className='flex flex-wrap justify-between items-center gap-4 mb-4'>
|
||||||
|
<div className='flex flex-wrap gap-2'>
|
||||||
|
{calendars.map((calendar) => (
|
||||||
|
<Button
|
||||||
|
key={calendar.id}
|
||||||
|
variant={
|
||||||
|
calendar.id === selectedCalendarId ? "default" : "outline"
|
||||||
|
}
|
||||||
|
onClick={() => handleCalendarChange(calendar.id)}
|
||||||
|
style={{
|
||||||
|
backgroundColor:
|
||||||
|
calendar.id === selectedCalendarId
|
||||||
|
? calendar.color
|
||||||
|
: undefined,
|
||||||
|
borderColor: calendar.color,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{calendar.name}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedEvent(null);
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className='mr-2 h-4 w-4' />
|
||||||
|
Nouvel événement
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Sélecteur de vue */}
|
||||||
|
<Tabs value={view} className='w-full'>
|
||||||
|
<TabsList className='mb-4'>
|
||||||
|
<TabsTrigger
|
||||||
|
value='dayGridMonth'
|
||||||
|
onClick={() => handleViewChange("dayGridMonth")}
|
||||||
|
>
|
||||||
|
Mois
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value='timeGridWeek'
|
||||||
|
onClick={() => handleViewChange("timeGridWeek")}
|
||||||
|
>
|
||||||
|
Semaine
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
value='timeGridDay'
|
||||||
|
onClick={() => handleViewChange("timeGridDay")}
|
||||||
|
>
|
||||||
|
Jour
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
{/* Affichage du calendrier */}
|
||||||
|
<Card className='p-4'>
|
||||||
|
{error && (
|
||||||
|
<div className='p-4 mb-4 text-red-500 bg-red-50 rounded-md'>
|
||||||
|
Erreur: {error.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading && !events.length ? (
|
||||||
|
<div className='h-96 flex items-center justify-center'>
|
||||||
|
<Loader2 className='h-8 w-8 animate-spin text-primary' />
|
||||||
|
<span className='ml-2'>Chargement des événements...</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<FullCalendar
|
||||||
|
ref={calendarRef}
|
||||||
|
plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
|
||||||
|
initialView={view}
|
||||||
|
headerToolbar={{
|
||||||
|
left: "prev,next today",
|
||||||
|
center: "title",
|
||||||
|
right: "",
|
||||||
|
}}
|
||||||
|
events={events.map((event) => ({
|
||||||
|
id: event.id,
|
||||||
|
title: event.title,
|
||||||
|
start: event.start,
|
||||||
|
end: event.end,
|
||||||
|
allDay: event.isAllDay,
|
||||||
|
extendedProps: {
|
||||||
|
description: event.description,
|
||||||
|
location: event.location,
|
||||||
|
},
|
||||||
|
}))}
|
||||||
|
selectable={true}
|
||||||
|
selectMirror={true}
|
||||||
|
dayMaxEvents={true}
|
||||||
|
weekends={true}
|
||||||
|
locale={frLocale}
|
||||||
|
select={handleDateSelect}
|
||||||
|
eventClick={handleEventClick}
|
||||||
|
datesSet={handleDatesSet}
|
||||||
|
height='auto'
|
||||||
|
aspectRatio={1.8}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
{/* Dialogue pour créer/modifier un événement */}
|
||||||
|
{isDialogOpen && (
|
||||||
|
<EventDialog
|
||||||
|
open={isDialogOpen}
|
||||||
|
event={selectedEvent}
|
||||||
|
onClose={() => setIsDialogOpen(false)}
|
||||||
|
onSave={handleEventSave}
|
||||||
|
onDelete={selectedEvent?.id ? handleEventDelete : undefined}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
220
front/components/calendar/event-dialog.tsx
Normal file
220
front/components/calendar/event-dialog.tsx
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogFooter,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
|
import { parseISO, format } from "date-fns";
|
||||||
|
import { fr } from "date-fns/locale";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "../ui/alert-dialog";
|
||||||
|
|
||||||
|
interface EventDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
event?: any;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave: (event: any) => void;
|
||||||
|
onDelete?: (eventId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EventDialog({
|
||||||
|
open,
|
||||||
|
event,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
onDelete,
|
||||||
|
}: EventDialogProps) {
|
||||||
|
const [title, setTitle] = useState(event?.title || "");
|
||||||
|
const [description, setDescription] = useState(event?.description || "");
|
||||||
|
const [location, setLocation] = useState(event?.location || "");
|
||||||
|
const [start, setStart] = useState(event?.start || "");
|
||||||
|
const [end, setEnd] = useState(event?.end || "");
|
||||||
|
const [allDay, setAllDay] = useState(event?.allDay || false);
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
|
||||||
|
// Formater les dates pour l'affichage
|
||||||
|
const formatDate = (dateStr: string) => {
|
||||||
|
if (!dateStr) return "";
|
||||||
|
try {
|
||||||
|
const date = parseISO(dateStr);
|
||||||
|
return format(date, allDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm", {
|
||||||
|
locale: fr,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gérer le changement de l'option "Toute la journée"
|
||||||
|
const handleAllDayChange = (checked: boolean) => {
|
||||||
|
setAllDay(checked);
|
||||||
|
|
||||||
|
// Ajuster les dates si nécessaire
|
||||||
|
if (checked && start) {
|
||||||
|
const startDate = parseISO(start);
|
||||||
|
setStart(format(startDate, "yyyy-MM-dd"));
|
||||||
|
|
||||||
|
if (end) {
|
||||||
|
const endDate = parseISO(end);
|
||||||
|
setEnd(format(endDate, "yyyy-MM-dd"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enregistrer l'événement
|
||||||
|
const handleSave = () => {
|
||||||
|
onSave({
|
||||||
|
id: event?.id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
location,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
isAllDay: allDay,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supprimer l'événement
|
||||||
|
const handleDelete = () => {
|
||||||
|
if (onDelete && event?.id) {
|
||||||
|
onDelete(event.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Dialog open={open} onOpenChange={onClose}>
|
||||||
|
<DialogContent className='sm:max-w-[550px]'>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{event?.id ? "Modifier l'événement" : "Nouvel événement"}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className='grid gap-4 py-4'>
|
||||||
|
<div className='grid gap-2'>
|
||||||
|
<Label htmlFor='title'>Titre *</Label>
|
||||||
|
<Input
|
||||||
|
id='title'
|
||||||
|
value={title}
|
||||||
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
|
placeholder='Ajouter un titre'
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='grid gap-2'>
|
||||||
|
<Label htmlFor='description'>Description</Label>
|
||||||
|
<Textarea
|
||||||
|
id='description'
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
placeholder="Description de l'événement"
|
||||||
|
className='min-h-[100px]'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='grid gap-2'>
|
||||||
|
<Label htmlFor='location'>Lieu</Label>
|
||||||
|
<Input
|
||||||
|
id='location'
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
|
placeholder='Emplacement'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center space-x-2'>
|
||||||
|
<Checkbox
|
||||||
|
id='allDay'
|
||||||
|
checked={allDay}
|
||||||
|
onCheckedChange={handleAllDayChange}
|
||||||
|
/>
|
||||||
|
<Label htmlFor='allDay'>Toute la journée</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-2 gap-4'>
|
||||||
|
<div className='grid gap-2'>
|
||||||
|
<Label htmlFor='start'>Début *</Label>
|
||||||
|
<Input
|
||||||
|
id='start'
|
||||||
|
type={allDay ? "date" : "datetime-local"}
|
||||||
|
value={formatDate(start)}
|
||||||
|
onChange={(e) => setStart(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='grid gap-2'>
|
||||||
|
<Label htmlFor='end'>Fin *</Label>
|
||||||
|
<Input
|
||||||
|
id='end'
|
||||||
|
type={allDay ? "date" : "datetime-local"}
|
||||||
|
value={formatDate(end)}
|
||||||
|
onChange={(e) => setEnd(e.target.value)}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter className='flex justify-between'>
|
||||||
|
<div>
|
||||||
|
{event?.id && onDelete && (
|
||||||
|
<Button
|
||||||
|
variant='destructive'
|
||||||
|
onClick={() => setConfirmDelete(true)}
|
||||||
|
>
|
||||||
|
Supprimer
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='flex space-x-2'>
|
||||||
|
<Button variant='outline' onClick={onClose}>
|
||||||
|
Annuler
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleSave} disabled={!title || !start || !end}>
|
||||||
|
Enregistrer
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Dialogue de confirmation de suppression */}
|
||||||
|
<AlertDialog open={confirmDelete} onOpenChange={setConfirmDelete}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Confirmer la suppression</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Êtes-vous sûr de vouloir supprimer cet événement ? Cette action
|
||||||
|
est irréversible.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Annuler</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={handleDelete}>
|
||||||
|
Supprimer
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -10,7 +10,6 @@ import {
|
|||||||
GitFork,
|
GitFork,
|
||||||
Building2,
|
Building2,
|
||||||
Users,
|
Users,
|
||||||
User,
|
|
||||||
Calendar,
|
Calendar,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@ -27,12 +26,13 @@ const menuItems = [
|
|||||||
title: "Users",
|
title: "Users",
|
||||||
icon: Users,
|
icon: Users,
|
||||||
href: "/users",
|
href: "/users",
|
||||||
|
external: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Calendar",
|
title: "Calendar",
|
||||||
icon: Calendar,
|
icon: Calendar,
|
||||||
href: "http://cloud.neah.local/apps/calendar/dayGridMonth/now",
|
href: "/calendar",
|
||||||
external: true,
|
external: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Flow",
|
title: "Flow",
|
||||||
|
|||||||
106
front/hooks/use-calendar-events.ts
Normal file
106
front/hooks/use-calendar-events.ts
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Event } from "@prisma/client";
|
||||||
|
|
||||||
|
export function useCalendarEvents(calendarId: string, start: Date, end: Date) {
|
||||||
|
const [events, setEvents] = useState<Event[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
|
||||||
|
// Charger les événements
|
||||||
|
const fetchEvents = async () => {
|
||||||
|
if (!calendarId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/calendars/${calendarId}/events?` +
|
||||||
|
`start=${start.toISOString()}&end=${end.toISOString()}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Erreur ${response.status}: ${await response.text()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
setEvents(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erreur lors du chargement des événements:", err);
|
||||||
|
setError(err as Error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Créer un événement
|
||||||
|
const createEvent = async (eventData: any) => {
|
||||||
|
const response = await fetch(`/api/calendars/${calendarId}/events`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(eventData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(errorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mettre à jour un événement
|
||||||
|
const updateEvent = async (eventData: any) => {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/calendars/${calendarId}/events/${eventData.id}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(eventData),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(errorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supprimer un événement
|
||||||
|
const deleteEvent = async (eventId: string) => {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/calendars/${calendarId}/events/${eventId}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(errorText);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Charger les événements quand le calendrier ou les dates changent
|
||||||
|
useEffect(() => {
|
||||||
|
fetchEvents();
|
||||||
|
}, [calendarId, start.toISOString(), end.toISOString()]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
events,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refresh: fetchEvents,
|
||||||
|
createEvent,
|
||||||
|
updateEvent,
|
||||||
|
deleteEvent,
|
||||||
|
};
|
||||||
|
}
|
||||||
10
front/lib/prisma.ts
Normal file
10
front/lib/prisma.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
// front/lib/prisma.ts
|
||||||
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
var prisma: PrismaClient | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prisma = global.prisma || new PrismaClient();
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== "production") global.prisma = prisma;
|
||||||
710
front/package-lock.json
generated
710
front/package-lock.json
generated
@ -8,7 +8,9 @@
|
|||||||
"name": "neah",
|
"name": "neah",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fullcalendar/react": "^6.1.15",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
"@prisma/client": "^6.4.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
||||||
@ -40,8 +42,9 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "1.0.4",
|
"cmdk": "1.0.4",
|
||||||
"date-fns": "3.0.0",
|
"date-fns": "^3.0.0",
|
||||||
"embla-carousel-react": "8.5.1",
|
"embla-carousel-react": "8.5.1",
|
||||||
|
"fullcalendar": "^6.1.15",
|
||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "14.2.24",
|
"next": "14.2.24",
|
||||||
@ -65,6 +68,7 @@
|
|||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
"prisma": "^6.4.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
@ -91,6 +95,431 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"aix"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/android-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/darwin-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/freebsd-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ia32": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-loong64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
|
||||||
|
"cpu": [
|
||||||
|
"loong64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-mips64el": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
|
||||||
|
"cpu": [
|
||||||
|
"mips64el"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-ppc64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-riscv64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
|
||||||
|
"cpu": [
|
||||||
|
"riscv64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-s390x": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/linux-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/netbsd-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"netbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/openbsd-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openbsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/sunos-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"sunos"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-arm64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-ia32": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
|
||||||
|
"cpu": [
|
||||||
|
"ia32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@esbuild/win32-x64": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@floating-ui/core": {
|
"node_modules/@floating-ui/core": {
|
||||||
"version": "1.6.9",
|
"version": "1.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
|
||||||
@ -125,6 +554,87 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz",
|
||||||
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
|
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@fullcalendar/core": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"preact": "~10.12.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/core/node_modules/preact": {
|
||||||
|
"version": "10.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
|
||||||
|
"integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/preact"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/daygrid": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/interaction": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/list": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/multimonth": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-sEZY6jbOYkeF9TwhUldG+UUVv+hiPlGkS8zZEgPR7ypcjhipyA03c5rPjx7N6huOHqh6lCMH59zlohLooQRlaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fullcalendar/daygrid": "~6.1.15"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/react": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-L0b9hybS2J4e7lq6G2CD4nqriyLEqOH1tE8iI6JQjAMTVh5JicOo5Mqw+fhU5bJ7hLfMw2K3fksxX3Ul1ssw5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15",
|
||||||
|
"react": "^16.7.0 || ^17 || ^18 || ^19",
|
||||||
|
"react-dom": "^16.7.0 || ^17 || ^18 || ^19"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fullcalendar/timegrid": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fullcalendar/daygrid": "~6.1.15"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@hookform/resolvers": {
|
"node_modules/@hookform/resolvers": {
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz",
|
||||||
@ -392,6 +902,78 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@prisma/client": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-A7Mwx44+GVZVexT5e2GF/WcKkEkNNKbgr059xpr5mn+oUm2ZW1svhe+0TRNBwCdzhfIZ+q23jEgsNPvKD9u+6g==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*",
|
||||||
|
"typescript": ">=5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/debug": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-Q9xk6yjEGIThjSD8zZegxd5tBRNHYd13GOIG0nLsanbTXATiPXCLyvlYEfvbR2ft6dlRsziQXfQGxAgv7zcMUA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-KldENzMHtKYwsOSLThghOIdXOBEsfDuGSrxAZjMnimBiDKd3AE4JQ+Kv+gBD/x77WoV9xIPf25GXMWffXZ17BA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.4.1",
|
||||||
|
"@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d",
|
||||||
|
"@prisma/fetch-engine": "6.4.1",
|
||||||
|
"@prisma/get-platform": "6.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines-version": {
|
||||||
|
"version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d.tgz",
|
||||||
|
"integrity": "sha512-Xq54qw55vaCGrGgIJqyDwOq0TtjZPJEWsbQAHugk99hpDf2jcEeQhUcF+yzEsSqegBaDNLA4IC8Nn34sXmkiTQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/fetch-engine": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-uZ5hVeTmDspx7KcaRCNoXmcReOD+84nwlO2oFvQPRQh9xiFYnnUKDz7l9bLxp8t4+25CsaNlgrgilXKSQwrIGQ==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.4.1",
|
||||||
|
"@prisma/engines-version": "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d",
|
||||||
|
"@prisma/get-platform": "6.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/get-platform": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-gXqZaDI5scDkBF8oza7fOD3Q3QMD0e0rBynlzDDZdTWbWmzjuW58PRZtj+jkvKje2+ZigCWkH8SsWZAsH6q1Yw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "6.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/number": {
|
"node_modules/@radix-ui/number": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
|
||||||
@ -2667,11 +3249,30 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.0.0.tgz",
|
||||||
"integrity": "sha512-xjDz3rNN9jp+Lh3P/4MeY4E5HkaRnEnrJCcrdRZnKdn42gJlIe6hwrrwVXePRwVR2kh1UcMnz00erYBnHF8PFA==",
|
"integrity": "sha512-xjDz3rNN9jp+Lh3P/4MeY4E5HkaRnEnrJCcrdRZnKdn42gJlIe6hwrrwVXePRwVR2kh1UcMnz00erYBnHF8PFA==",
|
||||||
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
"url": "https://github.com/sponsors/kossnocorp"
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/decimal.js-light": {
|
"node_modules/decimal.js-light": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
@ -2741,6 +3342,60 @@
|
|||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/esbuild": {
|
||||||
|
"version": "0.25.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
|
||||||
|
"integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"esbuild": "bin/esbuild"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@esbuild/aix-ppc64": "0.25.0",
|
||||||
|
"@esbuild/android-arm": "0.25.0",
|
||||||
|
"@esbuild/android-arm64": "0.25.0",
|
||||||
|
"@esbuild/android-x64": "0.25.0",
|
||||||
|
"@esbuild/darwin-arm64": "0.25.0",
|
||||||
|
"@esbuild/darwin-x64": "0.25.0",
|
||||||
|
"@esbuild/freebsd-arm64": "0.25.0",
|
||||||
|
"@esbuild/freebsd-x64": "0.25.0",
|
||||||
|
"@esbuild/linux-arm": "0.25.0",
|
||||||
|
"@esbuild/linux-arm64": "0.25.0",
|
||||||
|
"@esbuild/linux-ia32": "0.25.0",
|
||||||
|
"@esbuild/linux-loong64": "0.25.0",
|
||||||
|
"@esbuild/linux-mips64el": "0.25.0",
|
||||||
|
"@esbuild/linux-ppc64": "0.25.0",
|
||||||
|
"@esbuild/linux-riscv64": "0.25.0",
|
||||||
|
"@esbuild/linux-s390x": "0.25.0",
|
||||||
|
"@esbuild/linux-x64": "0.25.0",
|
||||||
|
"@esbuild/netbsd-arm64": "0.25.0",
|
||||||
|
"@esbuild/netbsd-x64": "0.25.0",
|
||||||
|
"@esbuild/openbsd-arm64": "0.25.0",
|
||||||
|
"@esbuild/openbsd-x64": "0.25.0",
|
||||||
|
"@esbuild/sunos-x64": "0.25.0",
|
||||||
|
"@esbuild/win32-arm64": "0.25.0",
|
||||||
|
"@esbuild/win32-ia32": "0.25.0",
|
||||||
|
"@esbuild/win32-x64": "0.25.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/esbuild-register": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"esbuild": ">=0.12 <1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/escalade": {
|
"node_modules/escalade": {
|
||||||
"version": "3.2.0",
|
"version": "3.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||||
@ -2847,6 +3502,20 @@
|
|||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fullcalendar": {
|
||||||
|
"version": "6.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz",
|
||||||
|
"integrity": "sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@fullcalendar/core": "~6.1.15",
|
||||||
|
"@fullcalendar/daygrid": "~6.1.15",
|
||||||
|
"@fullcalendar/interaction": "~6.1.15",
|
||||||
|
"@fullcalendar/list": "~6.1.15",
|
||||||
|
"@fullcalendar/multimonth": "~6.1.15",
|
||||||
|
"@fullcalendar/timegrid": "~6.1.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
@ -3114,6 +3783,13 @@
|
|||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/mz": {
|
"node_modules/mz": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
@ -3576,6 +4252,36 @@
|
|||||||
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/prisma": {
|
||||||
|
"version": "6.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.4.1.tgz",
|
||||||
|
"integrity": "sha512-q2uJkgXnua/jj66mk6P9bX/zgYJFI/jn4Yp0aS6SPRrjH/n6VyOV7RDe1vHD0DX8Aanx4MvgmUPPoYnR6MJnPg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/engines": "6.4.1",
|
||||||
|
"esbuild": ">=0.12 <1",
|
||||||
|
"esbuild-register": "3.6.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prisma": "build/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.18"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "2.3.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": ">=5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.8.1",
|
"version": "15.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
|
||||||
@ -4189,7 +4895,7 @@
|
|||||||
"version": "5.7.3",
|
"version": "5.7.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
||||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@ -9,7 +9,9 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fullcalendar/react": "^6.1.15",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
"@prisma/client": "^6.4.1",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
||||||
@ -41,8 +43,9 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "1.0.4",
|
"cmdk": "1.0.4",
|
||||||
"date-fns": "3.0.0",
|
"date-fns": "^3.0.0",
|
||||||
"embla-carousel-react": "8.5.1",
|
"embla-carousel-react": "8.5.1",
|
||||||
|
"fullcalendar": "^6.1.15",
|
||||||
"input-otp": "1.4.1",
|
"input-otp": "1.4.1",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "14.2.24",
|
"next": "14.2.24",
|
||||||
@ -66,6 +69,7 @@
|
|||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
|
"prisma": "^6.4.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
}
|
}
|
||||||
|
|||||||
39
front/prisma/schema.prisma
Normal file
39
front/prisma/schema.prisma
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
//TODO: Modifier l'url
|
||||||
|
url = "postgresql://enkun:183d9ad665c9257703c2e0703f111d240266a56b33e10df04fb8c565e55e0b94@172.16.32.141:5432/enkun?schema=public"
|
||||||
|
}
|
||||||
|
|
||||||
|
model Calendar {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
color String @default("#0082c9")
|
||||||
|
description String?
|
||||||
|
userId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
events Event[]
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model Event {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
title String
|
||||||
|
description String?
|
||||||
|
start DateTime
|
||||||
|
end DateTime
|
||||||
|
location String?
|
||||||
|
isAllDay Boolean @default(false)
|
||||||
|
calendar Calendar @relation(fields: [calendarId], references: [id], onDelete: Cascade)
|
||||||
|
calendarId String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
@@index([calendarId])
|
||||||
|
@@index([start, end])
|
||||||
|
}
|
||||||
23
front/types/calendar.d.ts
vendored
Normal file
23
front/types/calendar.d.ts
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// front/types/calendar.d.ts
|
||||||
|
export interface Calendar {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
color: string;
|
||||||
|
description?: string;
|
||||||
|
userId: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CalendarEvent {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
location?: string;
|
||||||
|
isAllDay: boolean;
|
||||||
|
calendarId: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
}
|
||||||
175
front/yarn.lock
175
front/yarn.lock
@ -14,6 +14,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
regenerator-runtime "^0.14.0"
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64@0.25.0":
|
||||||
|
version "0.25.0"
|
||||||
|
resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz"
|
||||||
|
integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==
|
||||||
|
|
||||||
"@floating-ui/core@^1.6.0":
|
"@floating-ui/core@^1.6.0":
|
||||||
version "1.6.9"
|
version "1.6.9"
|
||||||
resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz"
|
resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz"
|
||||||
@ -41,6 +46,47 @@
|
|||||||
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz"
|
resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz"
|
||||||
integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==
|
integrity sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==
|
||||||
|
|
||||||
|
"@fullcalendar/core@~6.1.15":
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.15.tgz"
|
||||||
|
integrity sha512-BuX7o6ALpLb84cMw1FCB9/cSgF4JbVO894cjJZ6kP74jzbUZNjtwffwRdA+Id8rrLjT30d/7TrkW90k4zbXB5Q==
|
||||||
|
dependencies:
|
||||||
|
preact "~10.12.1"
|
||||||
|
|
||||||
|
"@fullcalendar/daygrid@~6.1.15":
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.15.tgz"
|
||||||
|
integrity sha512-j8tL0HhfiVsdtOCLfzK2J0RtSkiad3BYYemwQKq512cx6btz6ZZ2RNc/hVnIxluuWFyvx5sXZwoeTJsFSFTEFA==
|
||||||
|
|
||||||
|
"@fullcalendar/interaction@~6.1.15":
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz"
|
||||||
|
integrity sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==
|
||||||
|
|
||||||
|
"@fullcalendar/list@~6.1.15":
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz"
|
||||||
|
integrity sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==
|
||||||
|
|
||||||
|
"@fullcalendar/multimonth@~6.1.15":
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz"
|
||||||
|
integrity sha512-sEZY6jbOYkeF9TwhUldG+UUVv+hiPlGkS8zZEgPR7ypcjhipyA03c5rPjx7N6huOHqh6lCMH59zlohLooQRlaw==
|
||||||
|
dependencies:
|
||||||
|
"@fullcalendar/daygrid" "~6.1.15"
|
||||||
|
|
||||||
|
"@fullcalendar/react@^6.1.15":
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.15.tgz"
|
||||||
|
integrity sha512-L0b9hybS2J4e7lq6G2CD4nqriyLEqOH1tE8iI6JQjAMTVh5JicOo5Mqw+fhU5bJ7hLfMw2K3fksxX3Ul1ssw5w==
|
||||||
|
|
||||||
|
"@fullcalendar/timegrid@~6.1.15":
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz"
|
||||||
|
integrity sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==
|
||||||
|
dependencies:
|
||||||
|
"@fullcalendar/daygrid" "~6.1.15"
|
||||||
|
|
||||||
"@hookform/resolvers@^3.9.1":
|
"@hookform/resolvers@^3.9.1":
|
||||||
version "3.10.0"
|
version "3.10.0"
|
||||||
resolved "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz"
|
resolved "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz"
|
||||||
@ -131,6 +177,47 @@
|
|||||||
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
|
resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
|
||||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||||
|
|
||||||
|
"@prisma/client@^6.4.1":
|
||||||
|
version "6.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/@prisma/client/-/client-6.4.1.tgz"
|
||||||
|
integrity sha512-A7Mwx44+GVZVexT5e2GF/WcKkEkNNKbgr059xpr5mn+oUm2ZW1svhe+0TRNBwCdzhfIZ+q23jEgsNPvKD9u+6g==
|
||||||
|
|
||||||
|
"@prisma/debug@6.4.1":
|
||||||
|
version "6.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/@prisma/debug/-/debug-6.4.1.tgz"
|
||||||
|
integrity sha512-Q9xk6yjEGIThjSD8zZegxd5tBRNHYd13GOIG0nLsanbTXATiPXCLyvlYEfvbR2ft6dlRsziQXfQGxAgv7zcMUA==
|
||||||
|
|
||||||
|
"@prisma/engines-version@6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d":
|
||||||
|
version "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d"
|
||||||
|
resolved "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d.tgz"
|
||||||
|
integrity sha512-Xq54qw55vaCGrGgIJqyDwOq0TtjZPJEWsbQAHugk99hpDf2jcEeQhUcF+yzEsSqegBaDNLA4IC8Nn34sXmkiTQ==
|
||||||
|
|
||||||
|
"@prisma/engines@6.4.1":
|
||||||
|
version "6.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/@prisma/engines/-/engines-6.4.1.tgz"
|
||||||
|
integrity sha512-KldENzMHtKYwsOSLThghOIdXOBEsfDuGSrxAZjMnimBiDKd3AE4JQ+Kv+gBD/x77WoV9xIPf25GXMWffXZ17BA==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/debug" "6.4.1"
|
||||||
|
"@prisma/engines-version" "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d"
|
||||||
|
"@prisma/fetch-engine" "6.4.1"
|
||||||
|
"@prisma/get-platform" "6.4.1"
|
||||||
|
|
||||||
|
"@prisma/fetch-engine@6.4.1":
|
||||||
|
version "6.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.4.1.tgz"
|
||||||
|
integrity sha512-uZ5hVeTmDspx7KcaRCNoXmcReOD+84nwlO2oFvQPRQh9xiFYnnUKDz7l9bLxp8t4+25CsaNlgrgilXKSQwrIGQ==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/debug" "6.4.1"
|
||||||
|
"@prisma/engines-version" "6.4.0-29.a9055b89e58b4b5bfb59600785423b1db3d0e75d"
|
||||||
|
"@prisma/get-platform" "6.4.1"
|
||||||
|
|
||||||
|
"@prisma/get-platform@6.4.1":
|
||||||
|
version "6.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.4.1.tgz"
|
||||||
|
integrity sha512-gXqZaDI5scDkBF8oza7fOD3Q3QMD0e0rBynlzDDZdTWbWmzjuW58PRZtj+jkvKje2+ZigCWkH8SsWZAsH6q1Yw==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/debug" "6.4.1"
|
||||||
|
|
||||||
"@radix-ui/number@1.1.0":
|
"@radix-ui/number@1.1.0":
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz"
|
resolved "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz"
|
||||||
@ -1183,11 +1270,18 @@ d3-timer@^3.0.1:
|
|||||||
resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz"
|
resolved "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz"
|
||||||
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==
|
||||||
|
|
||||||
"date-fns@^2.28.0 || ^3.0.0", date-fns@3.0.0:
|
"date-fns@^2.28.0 || ^3.0.0", date-fns@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.0.0.tgz"
|
resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.0.0.tgz"
|
||||||
integrity sha512-xjDz3rNN9jp+Lh3P/4MeY4E5HkaRnEnrJCcrdRZnKdn42gJlIe6hwrrwVXePRwVR2kh1UcMnz00erYBnHF8PFA==
|
integrity sha512-xjDz3rNN9jp+Lh3P/4MeY4E5HkaRnEnrJCcrdRZnKdn42gJlIe6hwrrwVXePRwVR2kh1UcMnz00erYBnHF8PFA==
|
||||||
|
|
||||||
|
debug@^4.3.4:
|
||||||
|
version "4.4.0"
|
||||||
|
resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz"
|
||||||
|
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
|
||||||
|
dependencies:
|
||||||
|
ms "^2.1.3"
|
||||||
|
|
||||||
decimal.js-light@^2.4.1:
|
decimal.js-light@^2.4.1:
|
||||||
version "2.5.1"
|
version "2.5.1"
|
||||||
resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz"
|
resolved "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz"
|
||||||
@ -1254,6 +1348,44 @@ emoji-regex@^9.2.2:
|
|||||||
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
|
resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz"
|
||||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||||
|
|
||||||
|
esbuild-register@3.6.0:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz"
|
||||||
|
integrity sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.3.4"
|
||||||
|
|
||||||
|
"esbuild@>=0.12 <1":
|
||||||
|
version "0.25.0"
|
||||||
|
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz"
|
||||||
|
integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==
|
||||||
|
optionalDependencies:
|
||||||
|
"@esbuild/aix-ppc64" "0.25.0"
|
||||||
|
"@esbuild/android-arm" "0.25.0"
|
||||||
|
"@esbuild/android-arm64" "0.25.0"
|
||||||
|
"@esbuild/android-x64" "0.25.0"
|
||||||
|
"@esbuild/darwin-arm64" "0.25.0"
|
||||||
|
"@esbuild/darwin-x64" "0.25.0"
|
||||||
|
"@esbuild/freebsd-arm64" "0.25.0"
|
||||||
|
"@esbuild/freebsd-x64" "0.25.0"
|
||||||
|
"@esbuild/linux-arm" "0.25.0"
|
||||||
|
"@esbuild/linux-arm64" "0.25.0"
|
||||||
|
"@esbuild/linux-ia32" "0.25.0"
|
||||||
|
"@esbuild/linux-loong64" "0.25.0"
|
||||||
|
"@esbuild/linux-mips64el" "0.25.0"
|
||||||
|
"@esbuild/linux-ppc64" "0.25.0"
|
||||||
|
"@esbuild/linux-riscv64" "0.25.0"
|
||||||
|
"@esbuild/linux-s390x" "0.25.0"
|
||||||
|
"@esbuild/linux-x64" "0.25.0"
|
||||||
|
"@esbuild/netbsd-arm64" "0.25.0"
|
||||||
|
"@esbuild/netbsd-x64" "0.25.0"
|
||||||
|
"@esbuild/openbsd-arm64" "0.25.0"
|
||||||
|
"@esbuild/openbsd-x64" "0.25.0"
|
||||||
|
"@esbuild/sunos-x64" "0.25.0"
|
||||||
|
"@esbuild/win32-arm64" "0.25.0"
|
||||||
|
"@esbuild/win32-ia32" "0.25.0"
|
||||||
|
"@esbuild/win32-x64" "0.25.0"
|
||||||
|
|
||||||
escalade@^3.2.0:
|
escalade@^3.2.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
|
resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz"
|
||||||
@ -1307,11 +1439,23 @@ fraction.js@^4.3.7:
|
|||||||
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
|
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
|
||||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||||
|
|
||||||
fsevents@~2.3.2:
|
fsevents@~2.3.2, fsevents@2.3.3:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
|
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
|
||||||
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
|
|
||||||
|
fullcalendar@^6.1.15:
|
||||||
|
version "6.1.15"
|
||||||
|
resolved "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz"
|
||||||
|
integrity sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==
|
||||||
|
dependencies:
|
||||||
|
"@fullcalendar/core" "~6.1.15"
|
||||||
|
"@fullcalendar/daygrid" "~6.1.15"
|
||||||
|
"@fullcalendar/interaction" "~6.1.15"
|
||||||
|
"@fullcalendar/list" "~6.1.15"
|
||||||
|
"@fullcalendar/multimonth" "~6.1.15"
|
||||||
|
"@fullcalendar/timegrid" "~6.1.15"
|
||||||
|
|
||||||
function-bind@^1.1.2:
|
function-bind@^1.1.2:
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
|
||||||
@ -1506,6 +1650,11 @@ minimatch@^9.0.4:
|
|||||||
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
|
resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
|
||||||
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
|
||||||
|
|
||||||
|
ms@^2.1.3:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
|
||||||
|
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
|
||||||
|
|
||||||
mz@^2.7.0:
|
mz@^2.7.0:
|
||||||
version "2.7.0"
|
version "2.7.0"
|
||||||
resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz"
|
resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz"
|
||||||
@ -1730,11 +1879,27 @@ preact@^10.6.3, preact@>=10:
|
|||||||
resolved "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz"
|
resolved "https://registry.npmjs.org/preact/-/preact-10.26.2.tgz"
|
||||||
integrity sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==
|
integrity sha512-0gNmv4qpS9HaN3+40CLBAnKe0ZfyE4ZWo5xKlC1rVrr0ckkEvJvAQqKaHANdFKsGstoxrY4AItZ7kZSGVoVjgg==
|
||||||
|
|
||||||
|
preact@~10.12.1:
|
||||||
|
version "10.12.1"
|
||||||
|
resolved "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz"
|
||||||
|
integrity sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==
|
||||||
|
|
||||||
pretty-format@^3.8.0:
|
pretty-format@^3.8.0:
|
||||||
version "3.8.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz"
|
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz"
|
||||||
integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
|
integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==
|
||||||
|
|
||||||
|
prisma@*, prisma@^6.4.1:
|
||||||
|
version "6.4.1"
|
||||||
|
resolved "https://registry.npmjs.org/prisma/-/prisma-6.4.1.tgz"
|
||||||
|
integrity sha512-q2uJkgXnua/jj66mk6P9bX/zgYJFI/jn4Yp0aS6SPRrjH/n6VyOV7RDe1vHD0DX8Aanx4MvgmUPPoYnR6MJnPg==
|
||||||
|
dependencies:
|
||||||
|
"@prisma/engines" "6.4.1"
|
||||||
|
esbuild ">=0.12 <1"
|
||||||
|
esbuild-register "3.6.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "2.3.3"
|
||||||
|
|
||||||
prop-types@^15.6.2, prop-types@^15.8.1:
|
prop-types@^15.6.2, prop-types@^15.8.1:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
|
||||||
@ -1754,7 +1919,7 @@ react-day-picker@8.10.1:
|
|||||||
resolved "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz"
|
resolved "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz"
|
||||||
integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==
|
integrity sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==
|
||||||
|
|
||||||
"react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^17.0.2 || ^18 || ^19", react-dom@^18, "react-dom@^18 || ^19 || ^19.0.0-rc", "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8.0:
|
"react-dom@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.7.0 || ^17 || ^18 || ^19", "react-dom@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom@^17.0.2 || ^18 || ^19", react-dom@^18, "react-dom@^18 || ^19 || ^19.0.0-rc", "react-dom@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react-dom@^18.2.0, react-dom@>=16.6.0, react-dom@>=16.8.0:
|
||||||
version "18.3.1"
|
version "18.3.1"
|
||||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
|
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz"
|
||||||
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
|
integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==
|
||||||
@ -1833,7 +1998,7 @@ react-transition-group@^4.4.5:
|
|||||||
loose-envify "^1.4.0"
|
loose-envify "^1.4.0"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
react@*, "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^17.0.2 || ^18 || ^19", react@^18, "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.6.0, react@>=16.8.0:
|
react@*, "react@^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc", "react@^16.7.0 || ^17 || ^18 || ^19", "react@^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react@^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react@^17.0.2 || ^18 || ^19", react@^18, "react@^18 || ^19 || ^19.0.0-rc", "react@^18.0.0 || ^19.0.0 || ^19.0.0-rc", react@^18.2.0, react@^18.3.1, "react@>= 16.8.0 || 17.x.x || ^18.0.0-0", react@>=16.6.0, react@>=16.8.0:
|
||||||
version "18.3.1"
|
version "18.3.1"
|
||||||
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
||||||
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
||||||
@ -2087,7 +2252,7 @@ tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0:
|
|||||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
||||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||||
|
|
||||||
typescript@^5:
|
typescript@^5, typescript@>=5.1.0:
|
||||||
version "5.7.3"
|
version "5.7.3"
|
||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz"
|
resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz"
|
||||||
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
|
integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user