Pfadauswahl hinzugefügt
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
||||
import { prisma } from "@repo/db";
|
||||
import { KursItem } from "../events/_components/item";
|
||||
import { EventCard } from "../events/_components/item";
|
||||
import { RocketIcon } from "lucide-react";
|
||||
import { eventCompleted } from "../../../helper/events";
|
||||
|
||||
@@ -15,15 +15,15 @@ const page = async () => {
|
||||
|
||||
const events = await prisma.event.findMany({
|
||||
where: {
|
||||
type: "OBLIGATED_COURSE",
|
||||
type: "EVENT",
|
||||
},
|
||||
include: {
|
||||
participants: {
|
||||
Participants: {
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
appointments: {
|
||||
Appointments: {
|
||||
include: {
|
||||
Participants: {
|
||||
where: {
|
||||
@@ -36,12 +36,10 @@ const page = async () => {
|
||||
});
|
||||
|
||||
const filteredEvents = events.filter((event) => {
|
||||
const userParticipant = event.participants.find(
|
||||
const userParticipant = event.Participants.find(
|
||||
(participant) => participant.userId === user.id,
|
||||
);
|
||||
if (eventCompleted(event, userParticipant)) return false;
|
||||
if (event.type === "OBLIGATED_COURSE" && !eventCompleted(event, event.participants[0]))
|
||||
return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -56,9 +54,9 @@ const page = async () => {
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
{filteredEvents.map((event) => {
|
||||
return (
|
||||
<KursItem
|
||||
appointments={event.appointments}
|
||||
selectedAppointments={event.appointments.filter((a) =>
|
||||
<EventCard
|
||||
appointments={event.Appointments}
|
||||
selectedAppointments={event.Appointments.filter((a) =>
|
||||
a.Participants.find((p) => p.userId == user.id),
|
||||
)}
|
||||
user={user}
|
||||
|
||||
@@ -1,9 +1,100 @@
|
||||
"use client";
|
||||
import { editUser } from "(app)/admin/user/action";
|
||||
import { Button } from "_components/ui/Button";
|
||||
import { Plane, Workflow } from "lucide-react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { getEvents } from "../../../helper/events";
|
||||
import { EventCard } from "(app)/events/_components/item";
|
||||
|
||||
const PathsOptions = ({
|
||||
selected,
|
||||
setSelected,
|
||||
}: {
|
||||
selected: "disponent" | "pilot" | null;
|
||||
setSelected: (value: "disponent" | "pilot") => void;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-6">
|
||||
{/* Disponent Card */}
|
||||
<div
|
||||
className={`cursor-pointer border rounded-lg p-6 w-80 transition-colors ${
|
||||
selected === "disponent" ? "border-info ring-2 ring-info" : "border-base-300"
|
||||
}`}
|
||||
onClick={() => setSelected("disponent")}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={selected === "disponent"}
|
||||
>
|
||||
<h1 className="font-semibold text-lg mb-2 flex gap-2 justify-center items-center">
|
||||
Disponent <Workflow />
|
||||
</h1>
|
||||
<div className="text-sm text-base-content/70">
|
||||
Denkt sich realistische Einsatzszenarien aus, koordiniert deren Ablauf und ist die
|
||||
zentrale Schnittstelle zwischen Piloten und bodengebundenen Rettungsmitteln. Er trägt
|
||||
die Verantwortung für einen reibungslosen Ablauf und der erfolgreichen Durchführung der
|
||||
Einsätze.
|
||||
<div className="badge badge-sm badge-secondary mt-3">
|
||||
Teilnahme an Einführungsevent Nötig
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Pilot Card */}
|
||||
<div
|
||||
className={`cursor-pointer border rounded-lg p-6 w-80 transition-colors ${
|
||||
selected === "pilot" ? "border-info ring-2 ring-info" : "border-base-300"
|
||||
}`}
|
||||
onClick={() => setSelected("pilot")}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={selected === "pilot"}
|
||||
>
|
||||
<h1 className="font-semibold text-lg mb-2 flex gap-2 justify-center items-center">
|
||||
Pilot <Plane />
|
||||
</h1>
|
||||
<div className="text-sm text-base-content/70">
|
||||
Fliegt die vom Disponenten erstellten Einsätze und transportiert die Med-Crew sicher zum
|
||||
Einsatzort. Er übernimmt die navigatorische Vorbereitung, achtet auf Wetterentwicklungen
|
||||
und sorgt für die Sicherheit seiner Crew im Flug.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const EventSelect = ({ pathSelected }: { pathSelected: "disponent" | "pilot" }) => {
|
||||
const { data: events } = useQuery({
|
||||
queryKey: ["events", "initial", pathSelected],
|
||||
queryFn: () =>
|
||||
getEvents({
|
||||
type: pathSelected === "pilot" ? "PILOT_STARTER" : "DISPATCH_STARTER",
|
||||
}),
|
||||
});
|
||||
const user = useSession().data?.user;
|
||||
if (!user) return null;
|
||||
return events?.map((event) => {
|
||||
return (
|
||||
<EventCard
|
||||
appointments={event.Appointments}
|
||||
selectedAppointments={event.Appointments.filter((a) =>
|
||||
a.Participants.find((p) => p.userId == user.id),
|
||||
)}
|
||||
user={user}
|
||||
event={event}
|
||||
key={event.id}
|
||||
/>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const FirstPath = () => {
|
||||
const modalRef = useRef<HTMLDialogElement>(null);
|
||||
const { data: session } = useSession();
|
||||
const [selected, setSelected] = useState<"disponent" | "pilot" | null>(null);
|
||||
const [page, setPage] = useState<"path" | "event-select">("path");
|
||||
|
||||
useEffect(() => {
|
||||
if (modalRef.current && !modalRef.current.open) {
|
||||
@@ -17,59 +108,40 @@ export const FirstPath = () => {
|
||||
<h3 className="flex items-center gap-2 text-lg font-bold mb-10">Wähle deinen Einstieg!</h3>
|
||||
<p className="mb-8 text-base text-base-content/80 text-center">
|
||||
Willkommen bei Virtual Air Rescue!
|
||||
<br /> Wähle deinen ersten Pfad aus. Du kannst später jederzeit auch den anderen Pfad
|
||||
<br /> Wie möchtest du bei uns starten? Du kannst später jederzeit auch den anderen Pfad
|
||||
ausprobieren, wenn du möchtest.
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center m-20">
|
||||
<div className="flex gap-6">
|
||||
{/* Disponent Card */}
|
||||
<div
|
||||
className={`cursor-pointer border rounded-lg p-6 w-80 transition-colors ${
|
||||
selected === "disponent" ? "border-info ring-2 ring-info" : "border-base-300"
|
||||
}`}
|
||||
onClick={() => setSelected("disponent")}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={selected === "disponent"}
|
||||
>
|
||||
<div className="font-semibold text-lg mb-2">Disponent</div>
|
||||
<div className="text-sm text-base-content/70">
|
||||
Denkt sich realistische Einsatzszenarien aus, koordiniert deren Ablauf und ist die
|
||||
zentrale Schnittstelle zwischen Piloten und bodengebundenen Rettungsmitteln. Er
|
||||
trägt die Verantwortung für einen reibungslosen Ablauf und der erfolgreichen
|
||||
Durchführung der Einsätze.
|
||||
<div className="badge badge-sm badge-secondary mt-3">
|
||||
Teilnahme an Einführungsevent Nötig
|
||||
</div>
|
||||
{page === "path" && <PathsOptions selected={selected} setSelected={setSelected} />}
|
||||
{page === "event-select" && (
|
||||
<div className="flex flex-col gap-3 min-w-[800px]">
|
||||
<div>
|
||||
<p className="text-left text-gray-400 text-sm">Wähle dein Einführungs-Event aus:</p>
|
||||
</div>
|
||||
<EventSelect pathSelected={selected!} />
|
||||
</div>
|
||||
{/* Pilot Card */}
|
||||
<div
|
||||
className={`cursor-pointer border rounded-lg p-6 w-80 transition-colors ${
|
||||
selected === "pilot" ? "border-info ring-2 ring-info" : "border-base-300"
|
||||
}`}
|
||||
onClick={() => setSelected("pilot")}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
aria-pressed={selected === "pilot"}
|
||||
>
|
||||
<div className="font-semibold text-lg mb-2">Pilot</div>
|
||||
<div className="text-sm text-base-content/70">
|
||||
Fliegt die vom Disponenten erstellten Einsätze und transportiert die Med-Crew sicher
|
||||
zum Einsatzort. Er übernimmt die navigatorische Vorbereitung, achtet auf
|
||||
Wetterentwicklungen und sorgt für die Sicherheit seiner Crew im Flug.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="modal-action">
|
||||
<button
|
||||
<button className="btn" disabled={page === "path"} onClick={() => setPage("path")}>
|
||||
Zurück
|
||||
</button>
|
||||
<Button
|
||||
className="btn btn-info"
|
||||
disabled={!selected}
|
||||
onClick={() => modalRef.current?.close()}
|
||||
onClick={async () => {
|
||||
if (page === "path") {
|
||||
setPage("event-select");
|
||||
} else if (session?.user.id) {
|
||||
await editUser(session?.user.id, {
|
||||
pathSelected: true,
|
||||
});
|
||||
modalRef.current?.close();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Auswahl Bestätigen
|
||||
</button>
|
||||
{page === "path" ? "Weiter" : "Pfad auswählen"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
@@ -5,7 +5,7 @@ import ModalBtn from "./modalBtn";
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
import { Badge } from "../../../_components/Badge/Badge";
|
||||
|
||||
export const KursItem = ({
|
||||
export const EventCard = ({
|
||||
user,
|
||||
event,
|
||||
selectedAppointments,
|
||||
@@ -13,8 +13,8 @@ export const KursItem = ({
|
||||
}: {
|
||||
user: User;
|
||||
event: Event & {
|
||||
appointments: EventAppointment[];
|
||||
participants: Participant[];
|
||||
Appointments: EventAppointment[];
|
||||
Participants: Participant[];
|
||||
};
|
||||
selectedAppointments: EventAppointment[];
|
||||
appointments: EventAppointment[];
|
||||
@@ -28,8 +28,8 @@ export const KursItem = ({
|
||||
{event.type === "COURSE" && (
|
||||
<span className="badge badge-info badge-outline">Zusatzqualifikation</span>
|
||||
)}
|
||||
{event.type === "OBLIGATED_COURSE" && (
|
||||
<span className="badge badge-secondary badge-outline">Verpflichtend</span>
|
||||
{event.type === "EVENT" && (
|
||||
<span className="badge badge-secondary badge-outline">Event</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
@@ -75,7 +75,7 @@ export const KursItem = ({
|
||||
event={event}
|
||||
title={event.name}
|
||||
dates={appointments}
|
||||
participant={event.participants[0]}
|
||||
participant={event.Participants[0]}
|
||||
modalId={`${event.name}_modal.${event.id}`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +92,7 @@ const ModalBtn = ({
|
||||
<button
|
||||
className={cn(
|
||||
"btn btn-outline btn-info btn-wide",
|
||||
event.type === "OBLIGATED_COURSE" && "btn-secondary",
|
||||
event.type === "COURSE" && "btn-secondary",
|
||||
eventCompleted(event, participant) && "btn-success",
|
||||
)}
|
||||
onClick={openModal}
|
||||
@@ -219,7 +219,7 @@ const ModalBtn = ({
|
||||
<button
|
||||
className={cn(
|
||||
"btn btn-info btn-outline btn-wide",
|
||||
event.type === "OBLIGATED_COURSE" && "btn-secondary",
|
||||
event.type === "COURSE" && "btn-secondary",
|
||||
)}
|
||||
onClick={async () => {
|
||||
const data = selectAppointmentForm.getValues();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { prisma } from "@repo/db";
|
||||
import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
||||
import { KursItem } from "./_components/item";
|
||||
import { EventCard } from "./_components/item";
|
||||
import { RocketIcon } from "@radix-ui/react-icons";
|
||||
|
||||
const page = async () => {
|
||||
@@ -11,14 +11,14 @@ const page = async () => {
|
||||
|
||||
const events = await prisma.event.findMany({
|
||||
include: {
|
||||
appointments: {
|
||||
Appointments: {
|
||||
where: {
|
||||
appointmentDate: {
|
||||
gte: new Date(),
|
||||
},
|
||||
},
|
||||
},
|
||||
participants: {
|
||||
Participants: {
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
@@ -69,7 +69,7 @@ const page = async () => {
|
||||
|
||||
{events.map((event) => {
|
||||
return (
|
||||
<KursItem
|
||||
<EventCard
|
||||
appointments={appointments}
|
||||
selectedAppointments={userAppointments}
|
||||
user={user}
|
||||
|
||||
@@ -46,7 +46,7 @@ export default async function RootLayout({
|
||||
<EmailVerification />
|
||||
</div>
|
||||
)}
|
||||
<FirstPath />
|
||||
{!session.user.pathSelected && <FirstPath />}
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
25
apps/hub/app/_components/QueryClient.tsx
Normal file
25
apps/hub/app/_components/QueryClient.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
// components/TanstackProvider.tsx
|
||||
"use client";
|
||||
|
||||
import { toast } from "react-hot-toast";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
|
||||
export function QueryProvider({ children }: { children: ReactNode }) {
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
defaultOptions: {
|
||||
mutations: {
|
||||
onError: (error) => {
|
||||
toast.error("An error occurred: " + (error as Error).message, {
|
||||
position: "top-right",
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
}
|
||||
46
apps/hub/app/api/event/route.ts
Normal file
46
apps/hub/app/api/event/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Prisma, prisma } from "@repo/db";
|
||||
import { getServerSession } from "api/auth/[...nextauth]/auth";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: Request): Promise<NextResponse> {
|
||||
try {
|
||||
const session = await getServerSession();
|
||||
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const filter = JSON.parse(
|
||||
new URL(request.url).searchParams.get("filter") || "{}",
|
||||
) as Prisma.EventWhereInput;
|
||||
|
||||
const connectedAircraft = await prisma.event.findMany({
|
||||
where: {
|
||||
...filter, // Ensure filter is parsed correctly
|
||||
},
|
||||
include: {
|
||||
Participants: {
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
},
|
||||
},
|
||||
Appointments: {
|
||||
include: {
|
||||
Participants: {
|
||||
where: {
|
||||
appointmentCancelled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(connectedAircraft, {
|
||||
status: 200,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return NextResponse.json({ error: "Failed to fetch Aircrafts" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { getServerSession } from "./api/auth/[...nextauth]/auth";
|
||||
import { CustomErrorBoundary } from "_components/ErrorBoundary";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import "./globals.css";
|
||||
import { QueryProvider } from "_components/QueryClient";
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
@@ -33,7 +34,9 @@ const RootLayout = async ({
|
||||
reverseOrder={false}
|
||||
/>
|
||||
</div>
|
||||
<CustomErrorBoundary>{children}</CustomErrorBoundary>
|
||||
<QueryProvider>
|
||||
<CustomErrorBoundary>{children}</CustomErrorBoundary>
|
||||
</QueryProvider>
|
||||
</body>
|
||||
</NextAuthSessionProvider>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
import { Event, Participant } from "@repo/db";
|
||||
import { Event, EventAppointment, Participant, Prisma } from "@repo/db";
|
||||
import axios from "axios";
|
||||
import { da } from "date-fns/locale";
|
||||
|
||||
export const getEvents = async (filter: Prisma.EventWhereInput) => {
|
||||
const { data } = await axios.get<
|
||||
(Event & {
|
||||
Appointments: (EventAppointment & {
|
||||
Appointments: EventAppointment[];
|
||||
Participants: Participant[];
|
||||
})[];
|
||||
Participants: Participant[];
|
||||
})[]
|
||||
>(`/api/event`, {
|
||||
params: {
|
||||
filter: JSON.stringify(filter),
|
||||
},
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const eventCompleted = (event: Event, participant?: Participant) => {
|
||||
if (!participant) return false;
|
||||
|
||||
@@ -10,19 +10,29 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
"@next-auth/prisma-adapter": "^1.0.7",
|
||||
"@radix-ui/react-icons": "^1.3.2",
|
||||
"@repo/db": "workspace:*",
|
||||
"@repo/eslint-config": "workspace:*",
|
||||
"@repo/typescript-config": "workspace:*",
|
||||
"@tanstack/react-query": "^5.79.0",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@tanstack/react-query": "^5.79.2",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/react": "^19.1.6",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@uiw/react-md-editor": "^4.0.7",
|
||||
"axios": "^1.9.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"clsx": "^2.1.1",
|
||||
"daisyui": "^5.0.43",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-config-next": "^15.3.3",
|
||||
"i": "^0.3.7",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
@@ -31,6 +41,7 @@
|
||||
"next-auth": "^4.24.11",
|
||||
"next-remove-imports": "^1.0.12",
|
||||
"npm": "^11.4.1",
|
||||
"postcss": "^8.5.4",
|
||||
"react": "^19.1.0",
|
||||
"react-datepicker": "^8.4.0",
|
||||
"react-day-picker": "^9.7.0",
|
||||
@@ -40,20 +51,8 @@
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-select": "^5.10.1",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"zod": "^3.25.46",
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@tailwindcss/postcss": "^4.1.8",
|
||||
"@types/bcryptjs": "^3.0.0",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/react": "^19.1.6",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"daisyui": "^5.0.43",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-config-next": "^15.3.3",
|
||||
"postcss": "^8.5.4",
|
||||
"tailwindcss": "^4.1.8",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"typescript": "^5.8.3",
|
||||
"zod": "^3.25.46"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
enum EVENT_TYPE {
|
||||
COURSE
|
||||
OBLIGATED_COURSE
|
||||
PILOT_STARTER
|
||||
DISPATCH_STARTER
|
||||
EVENT
|
||||
}
|
||||
|
||||
@@ -12,7 +13,7 @@ model EventAppointment {
|
||||
// relations:
|
||||
Users User[] @relation("EventAppointmentUser")
|
||||
Participants Participant[]
|
||||
Event Event @relation(fields: [eventId], references: [id])
|
||||
Event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
||||
Presenter User @relation(fields: [presenterId], references: [id])
|
||||
}
|
||||
|
||||
@@ -29,7 +30,7 @@ model Participant {
|
||||
eventId Int
|
||||
// relations:
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
Event Event @relation(fields: [eventId], references: [id])
|
||||
Event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
||||
EventAppointment EventAppointment? @relation(fields: [eventAppointmentId], references: [id])
|
||||
}
|
||||
|
||||
@@ -48,8 +49,8 @@ model Event {
|
||||
hidden Boolean @default(true)
|
||||
|
||||
// relations:
|
||||
participants Participant[]
|
||||
appointments EventAppointment[]
|
||||
Participants Participant[]
|
||||
Appointments EventAppointment[]
|
||||
}
|
||||
|
||||
model File {
|
||||
|
||||
@@ -33,6 +33,7 @@ model User {
|
||||
moodleId Int? @map(name: "moodle_id")
|
||||
|
||||
// Settings:
|
||||
pathSelected Boolean @default(false)
|
||||
settingsNtfyRoom String? @map(name: "settings_ntfy_room")
|
||||
settingsMicDevice String? @map(name: "settings_mic_device")
|
||||
settingsMicVolume Float? @map(name: "settings_mic_volume")
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@@ -342,7 +342,7 @@ importers:
|
||||
specifier: ^4.1.8
|
||||
version: 4.1.8
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.79.0
|
||||
specifier: ^5.79.2
|
||||
version: 5.79.2(react@19.1.0)
|
||||
'@tanstack/react-table':
|
||||
specifier: ^8.21.3
|
||||
|
||||
Reference in New Issue
Block a user