diff --git a/apps/hub/app/(app)/admin/station/[id]/page.tsx b/apps/hub/app/(app)/admin/station/[id]/page.tsx new file mode 100644 index 00000000..399adf90 --- /dev/null +++ b/apps/hub/app/(app)/admin/station/[id]/page.tsx @@ -0,0 +1,13 @@ +import { prisma } from '@repo/db'; +import { StationForm } from '../_components/Form'; + +export default async ({ params }: { params: Promise<{ id: string }> }) => { + const { id } = await params; + const station = await prisma.station.findUnique({ + where: { + id: parseInt(id), + }, + }); + if (!station) return
Station not found
; + return ; +}; diff --git a/apps/hub/app/(app)/admin/station/_components/Form.tsx b/apps/hub/app/(app)/admin/station/_components/Form.tsx new file mode 100644 index 00000000..c8bfe068 --- /dev/null +++ b/apps/hub/app/(app)/admin/station/_components/Form.tsx @@ -0,0 +1,246 @@ +'use client'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { StationOptionalDefaultsSchema } from '@repo/db/zod'; +import { set, useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { BosUse, Country, Station } from '@repo/db'; +import { FileText, LocateIcon, PlaneIcon } from 'lucide-react'; +import { Input } from '../../../../_components/ui/Input'; +import { useState } from 'react'; +import { deleteStation, upsertStation } from '../action'; +import { Button } from '../../../../_components/ui/Button'; +import { redirect } from 'next/navigation'; + +export const StationForm = ({ station }: { station?: Station }) => { + const form = useForm>({ + resolver: zodResolver(StationOptionalDefaultsSchema), + defaultValues: station, + }); + const [loading, setLoading] = useState(false); + const [deleteLoading, setDeleteLoading] = useState(false); + console.log(form.formState.errors); + return ( + <> +
{ + setLoading(true); + const createdStation = await upsertStation(values, station?.id); + setLoading(false); + if (!station) redirect(`/admin/station`); + })} + className="grid grid-cols-6 gap-3" + > +
+
+

+ Allgemeines +

+ + + + + + + + +
+
+
+
+

+ Standort + Ausrüstung +

+ + + + + Ausgerüstet mit: + +
+ + + + +
+ + + + +
+
+
+
+

+ Hubschrauber +

+ + + +
+
+
+
+
+ + {station && ( + + )} +
+
+
+
+ + ); +}; diff --git a/apps/hub/app/(app)/admin/station/action.ts b/apps/hub/app/(app)/admin/station/action.ts new file mode 100644 index 00000000..34ffa68d --- /dev/null +++ b/apps/hub/app/(app)/admin/station/action.ts @@ -0,0 +1,20 @@ +'use server'; + +import { prisma, Prisma, Station } from '@repo/db'; + +export const upsertStation = async ( + station: Prisma.StationCreateInput, + id?: Station['id'] +) => { + const newStation = id + ? await prisma.station.update({ + where: { id: id }, + data: station, + }) + : await prisma.station.create({ data: station }); + return newStation; +}; + +export const deleteStation = async (id: Station['id']) => { + await prisma.station.delete({ where: { id: id } }); +}; diff --git a/apps/hub/app/(app)/admin/station/new/page.tsx b/apps/hub/app/(app)/admin/station/new/page.tsx new file mode 100644 index 00000000..a7d5f4de --- /dev/null +++ b/apps/hub/app/(app)/admin/station/new/page.tsx @@ -0,0 +1,5 @@ +import { StationForm } from '../_components/Form'; + +export default () => { + return ; +}; diff --git a/apps/hub/app/(app)/admin/station/page.tsx b/apps/hub/app/(app)/admin/station/page.tsx new file mode 100644 index 00000000..70c3c91c --- /dev/null +++ b/apps/hub/app/(app)/admin/station/page.tsx @@ -0,0 +1,42 @@ +import { DatabaseBackupIcon } from 'lucide-react'; +import { PaginatedTable } from '../../../_components/PaginatedTable'; +import Link from 'next/link'; + +export default () => { + return ( + <> +

+ + Stationen + + + + +

+ + + ); +}; diff --git a/apps/hub/app/(app)/admin/user/loading.tsx b/apps/hub/app/(app)/admin/user/loading.tsx new file mode 100644 index 00000000..dfffa537 --- /dev/null +++ b/apps/hub/app/(app)/admin/user/loading.tsx @@ -0,0 +1,5 @@ +import { Loading } from '../../../_components/ui/Loading'; + +export default () => { + ; +}; diff --git a/apps/hub/app/(app)/admin/user/page.tsx b/apps/hub/app/(app)/admin/user/page.tsx index b6a9b4a2..d386dff1 100644 --- a/apps/hub/app/(app)/admin/user/page.tsx +++ b/apps/hub/app/(app)/admin/user/page.tsx @@ -1,29 +1,34 @@ -import Link from 'next/link'; +import { User2 } from 'lucide-react'; import { PaginatedTable } from '../../../_components/PaginatedTable'; export default async () => { return ( - + <> +

+ Benutzer +

+ + ); }; diff --git a/apps/hub/app/(app)/page.tsx b/apps/hub/app/(app)/page.tsx index b37a4be8..4a6b72b8 100644 --- a/apps/hub/app/(app)/page.tsx +++ b/apps/hub/app/(app)/page.tsx @@ -1,7 +1,6 @@ import Link from 'next/link'; import { PaginatedTable } from '../_components/PaginatedTable'; import { Header } from '../_components/ui/Header'; -import { PrismaClient } from '@repo/db'; export default async function Home() { return ( diff --git a/apps/hub/app/(app)/settings/account/_components/forms.tsx b/apps/hub/app/(app)/settings/account/_components/forms.tsx index 537d6108..b4701a2c 100644 --- a/apps/hub/app/(app)/settings/account/_components/forms.tsx +++ b/apps/hub/app/(app)/settings/account/_components/forms.tsx @@ -227,6 +227,7 @@ export const SocialForm = ({ type="number" className="input input-bordered w-full" placeholder="1445241" + defaultValue={user.vatsimCid as number | undefined} {...form.register('vatsimCid', { valueAsNumber: true, })} diff --git a/apps/hub/app/_components/Table.tsx b/apps/hub/app/_components/Table.tsx index e63373d0..aaa16372 100644 --- a/apps/hub/app/_components/Table.tsx +++ b/apps/hub/app/_components/Table.tsx @@ -39,7 +39,7 @@ export default function SortableTable({ - + ), diff --git a/apps/hub/app/_components/ui/Input.tsx b/apps/hub/app/_components/ui/Input.tsx new file mode 100644 index 00000000..1bb89d3e --- /dev/null +++ b/apps/hub/app/_components/ui/Input.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { + FieldValues, + Path, + RegisterOptions, + UseFormReturn, +} from 'react-hook-form'; +import { cn } from '../../../helper/cn'; + +interface InputProps + extends Omit, 'form'> { + name: Path; + form: UseFormReturn; + formOptions?: RegisterOptions; + label?: string; + placeholder?: string; +} + +export const Input = ({ + name, + label = name, + placeholder = label, + form, + formOptions, + className, + ...inputProps +}: InputProps) => { + return ( + + ); +}; diff --git a/apps/hub/app/_components/ui/Loading.tsx b/apps/hub/app/_components/ui/Loading.tsx new file mode 100644 index 00000000..9003604f --- /dev/null +++ b/apps/hub/app/_components/ui/Loading.tsx @@ -0,0 +1,8 @@ +export const Loading = () => { + return ( +
+
+

LAAAADEN

+
+ ); +}; diff --git a/apps/hub/app/_components/ui/Nav.tsx b/apps/hub/app/_components/ui/Nav.tsx index 1e800aad..17090ff3 100644 --- a/apps/hub/app/_components/ui/Nav.tsx +++ b/apps/hub/app/_components/ui/Nav.tsx @@ -29,7 +29,10 @@ export const VerticalNav = () => {
  • - User + Benutzer +
  • +
  • + Stationen
diff --git a/package-lock.json b/package-lock.json index 3c7cf4cb..42c89ea2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -107,6 +107,7 @@ "dependencies": { "@hookform/resolvers": "^3.10.0", "@next-auth/prisma-adapter": "^1.0.7", + "@repo/db": "*", "@repo/ui": "*", "@tanstack/react-table": "^8.20.6", "axios": "^1.7.9", @@ -1175,6 +1176,58 @@ } } }, + "node_modules/@prisma/debug": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.3.1.tgz", + "integrity": "sha512-RrEBkd+HLZx+ydfmYT0jUj7wjLiS95wfTOSQ+8FQbvb6vHh5AeKfEPt/XUQ5+Buljj8hltEfOslEW57/wQIVeA==" + }, + "node_modules/@prisma/engines": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.3.1.tgz", + "integrity": "sha512-sXdqEVLyGAJ5/iUoG/Ea5AdHMN71m6PzMBWRQnLmhhOejzqAaEr8rUd623ql6OJpED4s/U4vIn4dg1qkF7vGag==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@prisma/debug": "6.3.1", + "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "@prisma/fetch-engine": "6.3.1", + "@prisma/get-platform": "6.3.1" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0.tgz", + "integrity": "sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==", + "peer": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.3.1.tgz", + "integrity": "sha512-HOf/0umOgt+/S2xtZze+FHKoxpVg4YpVxROr6g2YG09VsI3Ipyb+rGvD6QGbCqkq5NTWAAZoOGNL+oy7t+IhaQ==", + "peer": true, + "dependencies": { + "@prisma/debug": "6.3.1", + "@prisma/engines-version": "6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0", + "@prisma/get-platform": "6.3.1" + } + }, + "node_modules/@prisma/generator-helper": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/generator-helper/-/generator-helper-6.3.1.tgz", + "integrity": "sha512-hX2fxjMksyAWAS0OcDi7GVmRUqsZ35ZY3Zla1EfO+uDYW6BY+om8kuKHyKkIvvRcUlTmL+xccl+nJwNToqP/aA==", + "dependencies": { + "@prisma/debug": "6.3.1" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.3.1.tgz", + "integrity": "sha512-AYLq6Hk9xG73JdLWJ3Ip9Wg/vlP7xPvftGBalsPzKDOHr/ImhwJ09eS8xC2vNT12DlzGxhfk8BkL0ve2OriNhQ==", + "peer": true, + "dependencies": { + "@prisma/debug": "6.3.1" + } + }, "node_modules/@radix-ui/react-icons": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", @@ -2666,6 +2719,11 @@ "node": ">=6" } }, + "node_modules/code-block-writer": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", + "integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==" + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -4274,7 +4332,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -7314,6 +7371,33 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" }, + "node_modules/prisma": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.3.1.tgz", + "integrity": "sha512-JKCZWvBC3enxk51tY4TWzS4b5iRt4sSU1uHn2I183giZTvonXaQonzVtjLzpOHE7qu9MxY510kAtFGJwryKe3Q==", + "hasInstallScript": true, + "peer": true, + "dependencies": { + "@prisma/engines": "6.3.1" + }, + "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": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -8869,7 +8953,7 @@ "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9282,6 +9366,24 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zod-prisma-types": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/zod-prisma-types/-/zod-prisma-types-3.2.4.tgz", + "integrity": "sha512-S4spVBMJAmecLv+aLyRhXK26qW9nWwcsOf1H1fRcEmiI8DPbftZ99u0fhqHlymuTpmMcUpuKxcNNOIqNY0ScSQ==", + "dependencies": { + "@prisma/generator-helper": "^6.3.0", + "code-block-writer": "^12.0.0", + "lodash": "^4.17.21", + "zod": "^3.24.1" + }, + "bin": { + "zod-prisma-types": "dist/bin.js" + }, + "peerDependencies": { + "@prisma/client": "^4.x.x || ^5.x.x || ^6.x.x", + "prisma": "^4.x.x || ^5.x.x || ^6.x.x" + } + }, "node_modules/zustand": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", @@ -9315,7 +9417,8 @@ "version": "0.0.0", "license": "ISC", "dependencies": { - "@prisma/client": "^6.2.1" + "@prisma/client": "^6.2.1", + "zod-prisma-types": "^3.2.4" } }, "packages/eslint-config": { diff --git a/packages/database/index.ts b/packages/database/index.ts index c2256878..263476f3 100644 --- a/packages/database/index.ts +++ b/packages/database/index.ts @@ -1,2 +1,6 @@ export { prisma } from './prisma/client'; // exports instance of prisma export * from './generated/client'; // exports generated types from prisma + +import * as zodTypes from './generated/zod'; + +export const zod = zodTypes; diff --git a/packages/database/package.json b/packages/database/package.json index 939f3e60..0c445ddd 100644 --- a/packages/database/package.json +++ b/packages/database/package.json @@ -5,16 +5,18 @@ "main": "generated/client/index.js", "types": "generated/client/index.d.ts", "scripts": { - "db:generate": "npx prisma generate", + "db:generate": "npx prisma generate && npx prisma generate zod", "db:migrate": "npx prisma migrate dev", "db:deploy": "npx prisma migrate deploy" }, "exports": { - ".": "./index.ts" + ".": "./index.ts", + "./zod": "./zod.ts" }, "author": "", "license": "ISC", "dependencies": { - "@prisma/client": "^6.2.1" + "@prisma/client": "^6.2.1", + "zod-prisma-types": "^3.2.4" } } diff --git a/packages/database/prisma/migrations/20250216163541_/migration.sql b/packages/database/prisma/migrations/20250216163541_/migration.sql new file mode 100644 index 00000000..1d19d6e0 --- /dev/null +++ b/packages/database/prisma/migrations/20250216163541_/migration.sql @@ -0,0 +1,32 @@ +-- CreateEnum +CREATE TYPE "BosUse" AS ENUM ('PRIMARY', 'SECONDARY', 'DUAL_USE'); + +-- CreateEnum +CREATE TYPE "Country" AS ENUM ('DE', 'AT', 'CH'); + +-- CreateTable +CREATE TABLE "Station" ( + "id" SERIAL NOT NULL, + "bosUse" "BosUse" NOT NULL, + "bosCallsign" TEXT NOT NULL, + "bosCallsignShort" TEXT NOT NULL, + "bosRadioArea" TEXT NOT NULL, + "country" "Country" NOT NULL, + "operator" TEXT NOT NULL, + "aircraft" TEXT NOT NULL, + "aircraftRegistration" TEXT NOT NULL, + "aircraftSpeed" INTEGER NOT NULL, + "hasWinch" BOOLEAN NOT NULL, + "is24h" BOOLEAN NOT NULL, + "hasNvg" BOOLEAN NOT NULL, + "locationState" TEXT NOT NULL, + "locationStateShort" TEXT NOT NULL, + "hasRope" BOOLEAN NOT NULL, + "fir" TEXT NOT NULL, + "latitude" DOUBLE PRECISION NOT NULL, + "longitude" DOUBLE PRECISION NOT NULL, + "atcCallsign" TEXT NOT NULL, + "hideRangeRings" BOOLEAN NOT NULL, + + CONSTRAINT "Station_pkey" PRIMARY KEY ("id") +); diff --git a/packages/database/prisma/schema/schema.prisma b/packages/database/prisma/schema/schema.prisma index 17820e12..5a9a79c5 100644 --- a/packages/database/prisma/schema/schema.prisma +++ b/packages/database/prisma/schema/schema.prisma @@ -7,7 +7,13 @@ generator client { provider = "prisma-client-js" previewFeatures = ["prismaSchemaFolder"] - output = "../../generated/client" + output = "../../generated/client" +} + +generator zod { + provider = "zod-prisma-types" + output = "../../generated/zod" + createOptionalDefaultValuesTypes = true } datasource db { diff --git a/packages/database/prisma/schema/station.prisma b/packages/database/prisma/schema/station.prisma new file mode 100644 index 00000000..cc429682 --- /dev/null +++ b/packages/database/prisma/schema/station.prisma @@ -0,0 +1,35 @@ +enum BosUse { + PRIMARY + SECONDARY + DUAL_USE +} + +enum Country { + DE + AT + CH +} + +model Station { + id Int @id @default(autoincrement()) + bosUse BosUse + bosCallsign String + bosCallsignShort String + bosRadioArea String + country Country + operator String + aircraft String + aircraftRegistration String + aircraftSpeed Int + hasWinch Boolean + is24h Boolean + hasNvg Boolean + locationState String + locationStateShort String + hasRope Boolean + fir String + latitude Float + longitude Float + atcCallsign String + hideRangeRings Boolean +} diff --git a/packages/database/zod.ts b/packages/database/zod.ts new file mode 100644 index 00000000..ca41570e --- /dev/null +++ b/packages/database/zod.ts @@ -0,0 +1 @@ +export * from './generated/zod';