dumb refactor but sunk cost is to great keep pushing
This commit is contained in:
54
src/app/_components/CalenderFormField.tsx
Normal file
54
src/app/_components/CalenderFormField.tsx
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { format } from "date-fns";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import type { Control, FieldValues, Path } from "react-hook-form";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Calendar } from "~/components/ui/calendar";
|
||||||
|
import { FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
|
||||||
|
import { Input } from "~/components/ui/input";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
|
export default function CalendarFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string }) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={params.control}
|
||||||
|
name={params.name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>{params.label}</FormLabel>
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<FormControl>
|
||||||
|
<Button
|
||||||
|
variant={"outline"}
|
||||||
|
className={cn(
|
||||||
|
"w-[240px] pl-3 text-left font-normal",
|
||||||
|
!field.value && "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{field.value ? (
|
||||||
|
format(field.value, "PPP")
|
||||||
|
) : (
|
||||||
|
<span>Pick a date</span>
|
||||||
|
)}
|
||||||
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
||||||
|
</Button>
|
||||||
|
</FormControl>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-auto p-0" align="start">
|
||||||
|
<Calendar
|
||||||
|
mode="single"
|
||||||
|
selected={field.value}
|
||||||
|
onSelect={field.onChange}
|
||||||
|
disabled={(date) =>
|
||||||
|
date > new Date() || date < new Date("1900-01-01")
|
||||||
|
}
|
||||||
|
initialFocus
|
||||||
|
captionLayout="dropdown"
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
10
src/app/_components/DependentText.tsx
Normal file
10
src/app/_components/DependentText.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export default function DependentText(params: {true:string,false:string,bool:boolean}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{ params.bool ?
|
||||||
|
params.true :
|
||||||
|
params.false
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
25
src/app/_components/MdeFormField.tsx
Normal file
25
src/app/_components/MdeFormField.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import MDEditor from "@uiw/react-md-editor";
|
||||||
|
import type { Control, FieldValues, Path } from "react-hook-form";
|
||||||
|
import { FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
|
||||||
|
export default function MdeFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string, dataColorMode: "dark"|"light" }) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={params.control}
|
||||||
|
name={params.name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
Description
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<MDEditor
|
||||||
|
value={field.value ? field.value : ""}
|
||||||
|
onChange={field.onChange}
|
||||||
|
data-color-mode={params.dataColorMode}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
21
src/app/_components/MutationFormMessage.tsx
Normal file
21
src/app/_components/MutationFormMessage.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { FormMessage } from "~/components/ui/form";
|
||||||
|
|
||||||
|
interface Error {
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DependentFormMessaage( params: {trueStatus: string, falseStatus: string, falseError?: Error|null, trueError?:Error|null, bool: boolean} ) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
params.bool ?
|
||||||
|
<FormMessage className={params.trueStatus == "success" ? "text-green-500" : "text-red-500"}>
|
||||||
|
{params.trueError ? params.trueError.message : params.trueStatus}
|
||||||
|
</FormMessage> :
|
||||||
|
<FormMessage className={params.falseStatus == "success" ? "text-green-500" : "text-red-500"}>
|
||||||
|
{params.falseError ? params.falseError.message : params.falseStatus}
|
||||||
|
</FormMessage>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
29
src/app/_components/SelectFormField.tsx
Normal file
29
src/app/_components/SelectFormField.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import type { Control, FieldValues, Path } from "react-hook-form";
|
||||||
|
import { FormField,FormControl, FormItem, FormLabel } from "~/components/ui/form";
|
||||||
|
import { Select, SelectContent, SelectTrigger, SelectValue } from "~/components/ui/select";
|
||||||
|
export default function SelectFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label: string, defaultValue:string|undefined, placeholder:string|undefined, children: ReactNode}) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={params.control}
|
||||||
|
name={params.name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{params.label}
|
||||||
|
</FormLabel>
|
||||||
|
<Select onValueChange={field.onChange} value={field.value == null ? undefined : field.value} defaultValue={params.defaultValue}>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder={params.placeholder} />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{params.children}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
17
src/app/_components/TextInputFormField.tsx
Normal file
17
src/app/_components/TextInputFormField.tsx
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { Control, FieldValues, Path } from "react-hook-form";
|
||||||
|
import { FormField, FormItem, FormLabel } from "~/components/ui/form";
|
||||||
|
import { Input } from "~/components/ui/input";
|
||||||
|
export default function TexttInputFormField<T extends FieldValues>(params: { control: Control<T>, name: Path<T>, label:string }) {
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
control={params.control}
|
||||||
|
name={params.name}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-col">
|
||||||
|
<FormLabel>{params.label}</FormLabel>
|
||||||
|
<Input placeholder="release link" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
'use server'
|
'use client'
|
||||||
|
|
||||||
import UpdateCvCategoryForm from "../_components/UpdateForm";
|
import { trpc } from "~/app/_trpc/Client";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm";
|
||||||
|
|
||||||
|
|
||||||
export default async function Page({params}:{params: Promise<{id:string}>}) {
|
export default function Page() {
|
||||||
console.log(params)
|
const {id} = useParams<{id: string}>();
|
||||||
const {id} = await params;
|
const {data} = trpc.category.select.useQuery({id: id})
|
||||||
|
if (data !== undefined && data.length > 0) {
|
||||||
return (
|
return (
|
||||||
<UpdateCvCategoryForm id={id} />
|
<CreateUpdateCvCategoryForm category={data[0]}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return (<></>)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { ChevronsUpDown, Plus } from "lucide-react"
|
import { ChevronsUpDown, Plus } from "lucide-react"
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
||||||
import { type EntryRouterOutputs } from "~/server/routers/cv/entry";
|
|
||||||
import type { CategoryRouterOutputs } from "~/server/routers/cv/category";
|
|
||||||
import { type Element } from "~/lib/utils";
|
import { type Element } from "~/lib/utils";
|
||||||
import UpdateCvCategoryForm from "./UpdateForm";
|
import CreateUpdateCvCategoryForm from "./CreateUpdateForm";
|
||||||
import CreateCvCategoryForm from "./CreateForm";
|
import type { RouterOutputs } from "~/server/routers/_app";
|
||||||
export default function CollapsibleCvCategoryForm(params:{category?:Element<CategoryRouterOutputs['list']>}) {
|
export default function CollapsibleCvCategoryForm(params:{category?:Element<RouterOutputs['category']['select']>}) {
|
||||||
return (
|
return (
|
||||||
<Collapsible >
|
<Collapsible >
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
@@ -19,8 +17,8 @@ export default function CollapsibleCvCategoryForm(params:{category?:Element<Cate
|
|||||||
<CollapsibleContent className="autoAlpha">
|
<CollapsibleContent className="autoAlpha">
|
||||||
{
|
{
|
||||||
params.category ?
|
params.category ?
|
||||||
<UpdateCvCategoryForm className="w-full" id={params.category.id} key={params.category.id} /> :
|
<CreateUpdateCvCategoryForm className="w-full" category={params.category} /> :
|
||||||
<CreateCvCategoryForm className="w-full"/>
|
<CreateUpdateCvCategoryForm className="w-full"/>
|
||||||
|
|
||||||
}
|
}
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
|
|||||||
@@ -1,48 +1,43 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { insertSchema } from "~/lib/schema/cv/category"
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
import { Form, FormControl, FormField, FormItem, FormLabel } from "~/components/ui/form";
|
||||||
import { Input } from "~/components/ui/input";
|
import { Input } from "~/components/ui/input";
|
||||||
import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select";
|
import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select";
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
import { trpc } from "~/app/_trpc/Client";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import * as Card from '~/components/ui/card'
|
import * as Card from '~/components/ui/card'
|
||||||
import type { IterableElement } from 'type-fest'
|
import type { IterableElement } from 'type-fest'
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
import { entitySchemas, makeOnSuccess } from "~/lib/utils";
|
||||||
import { entitySchemas } from "~/lib/utils";
|
|
||||||
import type { RouterOutputs } from "~/server/routers/_app";
|
import type { RouterOutputs } from "~/server/routers/_app";
|
||||||
export default function CreateCvCategoryForm(params:{className?:string,category?:IterableElement<RouterOutputs['cv']['categoryv2']['select']>}) {
|
import DependentFormMessaage from '~/app/_components/MutationFormMessage';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import DependentText from '~/app/_components/DependentText';
|
||||||
|
export default function CreateUpdateCvCategoryForm(params:{className?:string,category?:IterableElement<RouterOutputs['category']['select']>,isUpdate?:boolean}) {
|
||||||
const schemas = entitySchemas('cvCategory')
|
const schemas = entitySchemas('cvCategory')
|
||||||
const pathname = usePathname();
|
const [isUpdate,setIsUpdate] = useState<boolean>(params.isUpdate ? params.isUpdate : (params.category ? true : false))
|
||||||
const router = useRouter();
|
|
||||||
const form = useForm<z.infer<typeof schemas.insert>>({
|
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||||
resolver: zodResolver(schemas.insert),
|
resolver: zodResolver(schemas.insert),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: crypto.randomUUID(),
|
id: params.category ? params.category.id : crypto.randomUUID(),
|
||||||
layoutPosition: "col1"
|
name: params.category ? params.category.name : "",
|
||||||
|
layoutPosition: params.category ? params.category.layoutPosition : "col1"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const createMutation = trpc.cv.categoryv2.insert.useMutation()
|
const createMutation = trpc.category.insert.useMutation({onSuccess: makeOnSuccess('create',form,setIsUpdate)})
|
||||||
|
const updateMutation = trpc.category.update.useMutation({onSuccess: makeOnSuccess('update',form)})
|
||||||
function onSubmit(values: z.infer<typeof schemas.insert>) {
|
function onSubmit(values: z.infer<typeof schemas.insert>) {
|
||||||
const res = createMutation.mutate(values)
|
isUpdate ?
|
||||||
|
updateMutation.mutate(values) :
|
||||||
|
createMutation.mutate(values)
|
||||||
}
|
}
|
||||||
const updateMutation = trpc.cv.categoryv2.update.useMutation()
|
//TODO use SelectFormField and TextInputFormField
|
||||||
const deleteMutation = trpc.cv.categoryv2.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
if (pathname.includes('list')) {
|
|
||||||
router.refresh()
|
|
||||||
} else {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return (
|
return (
|
||||||
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
||||||
<Card.CardHeader>
|
<Card.CardHeader>
|
||||||
<Card.CardTitle>
|
<Card.CardTitle>
|
||||||
Create Category
|
<DependentText bool={isUpdate} true='Update Category' false='Create Category'/>
|
||||||
</Card.CardTitle>
|
</Card.CardTitle>
|
||||||
</Card.CardHeader>
|
</Card.CardHeader>
|
||||||
<Card.CardContent>
|
<Card.CardContent>
|
||||||
@@ -60,7 +55,7 @@ export default function CreateCvCategoryForm(params:{className?:string,category?
|
|||||||
Name
|
Name
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input placeholder="name" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
<Input placeholder="name" onChange={field.onChange} value={field.value ? field.value : ""} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@@ -80,7 +75,7 @@ export default function CreateCvCategoryForm(params:{className?:string,category?
|
|||||||
<SelectValue placeholder={form.getValues().layoutPosition} />
|
<SelectValue placeholder={form.getValues().layoutPosition} />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{insertSchema.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
|
{schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
|
||||||
<SelectItem key={o} value={o}> {o} </SelectItem>
|
<SelectItem key={o} value={o}> {o} </SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -90,10 +85,10 @@ export default function CreateCvCategoryForm(params:{className?:string,category?
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
</FormField>
|
</FormField>
|
||||||
<Button type="submit"> Create </Button>
|
<Button type="submit">
|
||||||
<FormMessage className={createMutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
<DependentText bool={isUpdate} true='Update' false='Create'/>
|
||||||
{createMutation.error ? createMutation.error.message : createMutation.status}
|
</Button>
|
||||||
</FormMessage>
|
<DependentFormMessaage falseStatus={createMutation.status} trueStatus={updateMutation.status} falseError={createMutation.error} trueError={updateMutation.error} bool={isUpdate}/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { updateRouteSchema } from "~/lib/schema/cv/category"
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
|
||||||
import { useForm } from 'react-hook-form'
|
|
||||||
import { z } from "zod";
|
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
|
||||||
import { Input } from "~/components/ui/input";
|
|
||||||
import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select";
|
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import * as Card from '~/components/ui/card'
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import { Delete } from "lucide-react";
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
export default function UpdateCvCategoryForm(params: { id: string, className?: string }) {
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const id = params.id;
|
|
||||||
const category = trpc.cv.category.get.useQuery({ id: id })
|
|
||||||
const form = useForm<z.infer<typeof updateRouteSchema>>({
|
|
||||||
resolver: zodResolver(updateRouteSchema),
|
|
||||||
defaultValues: {
|
|
||||||
by: { id: id },
|
|
||||||
update: {
|
|
||||||
layoutPosition: category.data?.layoutPosition,
|
|
||||||
name: category.data?.layoutPosition
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
category.promise.then((data) => {
|
|
||||||
form.setValue("update.layoutPosition", data?.layoutPosition)
|
|
||||||
form.setValue("update.name", data?.name)
|
|
||||||
})
|
|
||||||
const updateMutation = trpc.cv.category.update.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
category.refetch()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const deleteMutation = trpc.cv.category.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
if (pathname.includes('list')) {
|
|
||||||
router.refresh()
|
|
||||||
} else {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const deleteCategory = (id: string) => {
|
|
||||||
console.log(`deleting ${id}`)
|
|
||||||
deleteMutation.mutate(id)
|
|
||||||
}
|
|
||||||
function onSubmit(values: z.infer<typeof updateRouteSchema>) {
|
|
||||||
updateMutation.mutate({
|
|
||||||
by: { id: id },
|
|
||||||
update: {
|
|
||||||
layoutPosition: values.update.layoutPosition,
|
|
||||||
name: values.update.name
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (category.data !== undefined) {
|
|
||||||
return (
|
|
||||||
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
|
||||||
<Card.CardHeader>
|
|
||||||
<Card.CardTitle>
|
|
||||||
Update Category
|
|
||||||
</Card.CardTitle>
|
|
||||||
</Card.CardHeader>
|
|
||||||
<Card.CardContent>
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-8"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.name"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Name
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="name" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</FormField>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="by.id"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormControl>
|
|
||||||
<Input hidden onChange={field.onChange} value={field.value} />
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</FormField>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.layoutPosition"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Layout Position
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value == null ? undefined : field.value}>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder={form.getValues().update.layoutPosition} />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
{updateRouteSchema.shape.update.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
|
|
||||||
<SelectItem key={o} value={o}> {o} </SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</FormField>
|
|
||||||
<Button type="submit"> Update </Button>
|
|
||||||
<FormMessage className={cn(updateMutation.status == "success" ? "text-green-500" : "text-red-500", "flex flex-row justify-between")}>
|
|
||||||
{updateMutation.error ? updateMutation.error.message : updateMutation.status}
|
|
||||||
<Button
|
|
||||||
className="ml-auto cursor-pointer" variant="destructive" onClick={() => { deleteCategory(id) }}>
|
|
||||||
Delete
|
|
||||||
<Delete />
|
|
||||||
</Button>
|
|
||||||
</FormMessage>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.Card>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import CreateCvCategoryForm from "../_components/CreateForm";
|
import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<CreateCvCategoryForm/>
|
<CreateUpdateCvCategoryForm/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,26 +2,27 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
import { trpc } from "~/app/_trpc/Client";
|
||||||
import { useGSAP } from '@gsap/react'
|
import { useGSAP } from '@gsap/react'
|
||||||
import { useRef } from "react";
|
import { Suspense, useRef } from "react";
|
||||||
import * as Card from '~/components/ui/card'
|
import * as Card from '~/components/ui/card'
|
||||||
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
||||||
import CollapsibleCvEntryForm from "../../entry/_components/CollapsibleForm";
|
import CollapsibleCvEntryForm from "../../entry/_components/CollapsibleForm";
|
||||||
import UpdateCvCategoryForm from "../_components/UpdateForm";
|
|
||||||
import CollapsibleCvCategoryForm from "../_components/CollapsibleForm";
|
import CollapsibleCvCategoryForm from "../_components/CollapsibleForm";
|
||||||
|
import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm";
|
||||||
export default function CvPage() {
|
export default function CvPage() {
|
||||||
const cvCategories = trpc.cv.category.list.useQuery(undefined,{refetchInterval:1000});
|
const categories = trpc.category.select.useQuery({},{refetchInterval:1000});
|
||||||
|
const entires = trpc.entry.select.useSuspenseQuery({},{refetchInterval:1000})
|
||||||
const gsap = useGsapContext()
|
const gsap = useGsapContext()
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
useGSAP(() => {
|
useGSAP(() => {
|
||||||
gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } });
|
gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } });
|
||||||
}, { scope: container, dependencies: [cvCategories.status], revertOnUpdate: true });
|
}, { scope: container, dependencies: [categories.status], revertOnUpdate: true });
|
||||||
return (
|
return (
|
||||||
<div ref={container} className="w-5/6 lg:w-1/2 flex flex-col gap-3">
|
<div ref={container} className="w-5/6 lg:w-1/2 flex flex-col gap-3">
|
||||||
{cvCategories.data == undefined ?
|
{categories.data == undefined ?
|
||||||
<div className="gsapan"></div>
|
<div className="gsapan"></div>
|
||||||
:
|
:
|
||||||
<>
|
<>
|
||||||
{cvCategories.data.map((cat) => {
|
{categories.data.map((cat) => {
|
||||||
return (
|
return (
|
||||||
<Card.Card className="gsapan" key={cat.id}>
|
<Card.Card className="gsapan" key={cat.id}>
|
||||||
<Link href={`/admin/cv/category/${cat.id}`}>
|
<Link href={`/admin/cv/category/${cat.id}`}>
|
||||||
@@ -33,20 +34,22 @@ export default function CvPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
<Card.CardContent className="flex flex-row">
|
<Card.CardContent className="flex flex-row">
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<UpdateCvCategoryForm id={cat.id} className="w-full" />
|
<CreateUpdateCvCategoryForm category={cat} className="w-full" />
|
||||||
<br />
|
<br />
|
||||||
<span>Entries:</span>
|
<span>Entries:</span>
|
||||||
|
<Suspense fallback={(<></>)}>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{
|
{
|
||||||
cat.cvEntry.length > 0 ? (
|
entires[0].filter((e) => {return e.categoryId == cat.id}).length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{cat.cvEntry.map((entry) => (
|
{entires[0].filter((e) => {return e.categoryId == cat.id}).map((entry) => (
|
||||||
<CollapsibleCvEntryForm key={entry.id} entry={entry}/>
|
<CollapsibleCvEntryForm key={entry.id} entry={entry}/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (<></>)
|
) : (<></>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</Suspense>
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col w-full">
|
||||||
<CollapsibleCvEntryForm categoryId={cat.id} />
|
<CollapsibleCvEntryForm categoryId={cat.id} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
'use server'
|
'use client'
|
||||||
|
|
||||||
import UpdateCvEntryForm from "../_components/UpdateForm";
|
import { trpc } from "~/app/_trpc/Client";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import CreateUpdateCvEntryForm from "../_components/CreateUpdateForm";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
export default async function Page({params}:{params: Promise<{id:string}>}) {
|
const {id} = useParams<{id: string}>();
|
||||||
console.log(params)
|
const {data} = trpc.entry.select.useQuery({id: id})
|
||||||
const {id} = await params;
|
if (data !== undefined && data.length > 0) {
|
||||||
return (
|
return (
|
||||||
<UpdateCvEntryForm id={id} className={undefined} />
|
<CreateUpdateCvEntryForm entry={data[0]}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return (<></>)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { ChevronsUpDown, Plus } from "lucide-react"
|
import { ChevronsUpDown, Plus } from "lucide-react"
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
||||||
import { type EntryRouterOutputs } from "~/server/routers/cv/entry";
|
import CreateUpdateCvEntryForm from "./CreateUpdateForm";
|
||||||
import type { CategoryRouterOutputs } from "~/server/routers/cv/category";
|
import type { IterableElement } from "type-fest";
|
||||||
import { type Element } from "~/lib/utils";
|
import type { RouterOutputs } from "~/server/routers/_app";
|
||||||
import UpdateCvEntryForm from "./UpdateForm";
|
export default function CollapsibleCvEntryForm(params:{entry?:IterableElement<RouterOutputs['entry']['select']>, categoryId?: string}) {
|
||||||
import CreateCvEntryForm from "./CreateForm";
|
|
||||||
export default function CollapsibleCvEntryForm(params:{entry?:Element<Element<CategoryRouterOutputs['list']>['cvEntry']>|EntryRouterOutputs['get']|Element<EntryRouterOutputs['list']>, categoryId?: string}) {
|
|
||||||
return (
|
return (
|
||||||
<Collapsible>
|
<Collapsible>
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
@@ -19,9 +17,8 @@ export default function CollapsibleCvEntryForm(params:{entry?:Element<Element<Ca
|
|||||||
<CollapsibleContent className="autoAlpha">
|
<CollapsibleContent className="autoAlpha">
|
||||||
{
|
{
|
||||||
params.entry ?
|
params.entry ?
|
||||||
<UpdateCvEntryForm id={params.entry.id} className="w-full" key={params.entry.id} /> :
|
<CreateUpdateCvEntryForm className="w-full" entry={params.entry}/> :
|
||||||
<CreateCvEntryForm className="w-full" categoryId={params.categoryId ? params.categoryId : undefined}/>
|
<CreateUpdateCvEntryForm className="w-full"/>
|
||||||
|
|
||||||
}
|
}
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
|||||||
@@ -1,254 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { insertSchema, insertSchemaForm } from "~/lib/schema/cv/entry"
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
|
||||||
import { useForm } from 'react-hook-form'
|
|
||||||
import { format } from 'date-fns'
|
|
||||||
import { z } from "zod";
|
|
||||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
|
||||||
import { Input } from "~/components/ui/input";
|
|
||||||
import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select";
|
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import * as Card from '~/components/ui/card'
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
|
||||||
import { CalendarIcon } from "lucide-react";
|
|
||||||
import { Calendar } from "~/components/ui/calendar";
|
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { Checkbox } from "~/components/ui/checkbox"
|
|
||||||
import MDEditor from '@uiw/react-md-editor'
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
|
|
||||||
export default function CreateCvEntryForm(params:{className:string|undefined, categoryId:string|undefined}) {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
const categories = trpc.cv.category.list.useQuery()
|
|
||||||
const [categorySelectPlaceHolder,setCategorySelectPlaceHolder] = useState("Select category")
|
|
||||||
const [categorySelectDefaultValue,setCategorySelectDefaultValue] = useState("")
|
|
||||||
const [mdeTab,setMdeTab] = useState<"write" | "preview" | undefined>("write");
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('category success effect')
|
|
||||||
if (categories.data !== undefined) {
|
|
||||||
setCategorySelectDefaultValue(categories.data[0]?.id? categories.data[0].id : "")
|
|
||||||
setCategorySelectPlaceHolder(categories.data[0]?.name? categories.data[0].name : "Select category")
|
|
||||||
}
|
|
||||||
if (params.categoryId) {
|
|
||||||
let matching = categories.data?.filter((c) => {
|
|
||||||
return c.id == params.categoryId
|
|
||||||
})
|
|
||||||
if (matching !== undefined && matching.length > 0) {
|
|
||||||
setCategorySelectDefaultValue(matching[0]?.id ? matching[0].id : categorySelectDefaultValue )
|
|
||||||
setCategorySelectPlaceHolder(matching[0]?.name ? matching[0].name : categorySelectPlaceHolder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},[categories.isSuccess])
|
|
||||||
const now = new Date();
|
|
||||||
now.setTime(Date.now());
|
|
||||||
const form = useForm<z.infer<typeof insertSchemaForm>>({
|
|
||||||
resolver: zodResolver(insertSchemaForm),
|
|
||||||
defaultValues: {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
categoryId: params.categoryId ? params.categoryId : "",
|
|
||||||
fromTime: now,
|
|
||||||
toTime: now,
|
|
||||||
hideDates: false,
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const mutation = trpc.cv.entry.create.useMutation({
|
|
||||||
onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") }
|
|
||||||
})
|
|
||||||
function onSubmit(values: z.infer<typeof insertSchemaForm>) {
|
|
||||||
let { id, title, categoryId, description, hideDates } = values
|
|
||||||
let v: z.infer<typeof insertSchema> = {
|
|
||||||
id: id,
|
|
||||||
categoryId: categoryId,
|
|
||||||
description: description,
|
|
||||||
title: title,
|
|
||||||
fromTime: format(values.fromTime,'yyyy-MM-dd'),
|
|
||||||
toTime: format(values.toTime,'yyyy-MM-dd'),
|
|
||||||
hideDates: hideDates
|
|
||||||
}
|
|
||||||
mutation.mutate(v)
|
|
||||||
form.setValue("id", crypto.randomUUID())
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
|
||||||
<Card.CardHeader>
|
|
||||||
<Card.CardTitle>
|
|
||||||
Create Entry
|
|
||||||
</Card.CardTitle>
|
|
||||||
</Card.CardHeader>
|
|
||||||
<Card.CardContent>
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-8"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="categoryId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Category
|
|
||||||
</FormLabel>
|
|
||||||
{
|
|
||||||
categories.data ? (
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={categorySelectDefaultValue}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder={categorySelectPlaceHolder} />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{ categories.data?.map((c) => {
|
|
||||||
return (<SelectItem key={c.id} value={c.id}> {c.name} </SelectItem>)
|
|
||||||
})}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
) : <Select></Select>
|
|
||||||
}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="title"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Title
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="title" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="description"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Description
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<MDEditor
|
|
||||||
value={field.value ? field.value : ""}
|
|
||||||
onChange={field.onChange}
|
|
||||||
data-color-mode={theme ? theme : "dark"}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="fromTime"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col">
|
|
||||||
<FormLabel>From Date</FormLabel>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<FormControl>
|
|
||||||
<Button
|
|
||||||
variant={"outline"}
|
|
||||||
className={cn(
|
|
||||||
"w-[240px] pl-3 text-left font-normal",
|
|
||||||
!field.value && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{field.value ? (
|
|
||||||
format(field.value, "PPP")
|
|
||||||
) : (
|
|
||||||
<span>Pick a date</span>
|
|
||||||
)}
|
|
||||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
|
||||||
<Calendar
|
|
||||||
mode="single"
|
|
||||||
selected={field.value}
|
|
||||||
onSelect={field.onChange}
|
|
||||||
disabled={(date) =>
|
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
|
||||||
}
|
|
||||||
initialFocus
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="toTime"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col">
|
|
||||||
<FormLabel>To Date</FormLabel>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<FormControl>
|
|
||||||
<Button
|
|
||||||
variant={"outline"}
|
|
||||||
className={cn(
|
|
||||||
"w-[240px] pl-3 text-left font-normal",
|
|
||||||
!field.value && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{field.value ? (
|
|
||||||
format(field.value, "PPP")
|
|
||||||
) : (
|
|
||||||
<span>Pick a date</span>
|
|
||||||
)}
|
|
||||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
|
||||||
<Calendar
|
|
||||||
mode="single"
|
|
||||||
selected={field.value}
|
|
||||||
onSelect={field.onChange}
|
|
||||||
disabled={(date) =>
|
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
|
||||||
}
|
|
||||||
initialFocus
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="hideDates"
|
|
||||||
render={({field}) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Hide dates</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value ? field.value : false}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button type="submit"> Create </Button>
|
|
||||||
<FormMessage className={mutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
|
||||||
{mutation.error ? mutation.error.message : mutation.status}
|
|
||||||
</FormMessage>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.Card>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
119
src/app/admin/cv/entry/_components/CreateUpdateForm.tsx
Normal file
119
src/app/admin/cv/entry/_components/CreateUpdateForm.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
'use client'
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
|
import { useForm, type UseFormReturn } from 'react-hook-form'
|
||||||
|
import { format } from 'date-fns'
|
||||||
|
import { z } from "zod";
|
||||||
|
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
||||||
|
import { Input } from "~/components/ui/input";
|
||||||
|
import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select";
|
||||||
|
import { trpc } from "~/app/_trpc/Client";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import * as Card from '~/components/ui/card'
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
||||||
|
import { CalendarIcon } from "lucide-react";
|
||||||
|
import { Calendar } from "~/components/ui/calendar";
|
||||||
|
import { cn, entitySchemas, ft, makeOnSuccess, tt, type DatesAreString, type Pretty } from "~/lib/utils";
|
||||||
|
import { Checkbox } from "~/components/ui/checkbox"
|
||||||
|
import MDEditor from '@uiw/react-md-editor'
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
import type { Entries, IterableElement } from 'type-fest';
|
||||||
|
import type { RouterOutputs } from '~/server/routers/_app';
|
||||||
|
import DependentText from '~/app/_components/DependentText';
|
||||||
|
import DependentFormMessaage from '~/app/_components/MutationFormMessage';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import SelectFormField from '~/app/_components/SelectFormField';
|
||||||
|
import TexttInputFormField from '~/app/_components/TextInputFormField';
|
||||||
|
import MdeFormField from '~/app/_components/MdeFormField';
|
||||||
|
import CalendarFormField from '~/app/_components/CalenderFormField';
|
||||||
|
|
||||||
|
export default function CreateUpdateCvEntryForm(params: { className?: string, entry?: IterableElement<RouterOutputs['entry']['select']>, isUpdate?: boolean }) {
|
||||||
|
const [isUpdate, setIsUpdate] = useState<boolean>(params.isUpdate ? params.isUpdate : (params.entry ? true : false))
|
||||||
|
const [categoryId,setCategoryId] = useState<string|undefined>()
|
||||||
|
const [categoryName,setCategoryName] = useState<string|undefined>()
|
||||||
|
const { theme } = useTheme()
|
||||||
|
const schemas = entitySchemas('cvEntry')
|
||||||
|
const {data:categories,isSuccess: categoriesSuccess} = trpc.category.select.useQuery({})
|
||||||
|
useEffect(() => {
|
||||||
|
if (isUpdate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setCategoryId(categories?.at(0)?.id)
|
||||||
|
if (categories !== undefined && categories[0]?.name !== null) {
|
||||||
|
setCategoryName(categories[0]?.name)
|
||||||
|
}
|
||||||
|
},[categoriesSuccess])
|
||||||
|
let defaultValues = {
|
||||||
|
id: params.entry ? params.entry.id : crypto.randomUUID(),
|
||||||
|
title: params.entry ? params.entry.title : "",
|
||||||
|
description: params.entry ? params.entry.description : "",
|
||||||
|
categoryId: params.entry ? params.entry.categoryId : categoriesSuccess ? categories.at(0)?.id : "",
|
||||||
|
fromTime: params.entry ? ft(params.entry).fromTime : new Date(),
|
||||||
|
toTime: params.entry ? tt(params.entry).toTime : new Date(),
|
||||||
|
hideDates: params.entry ? params.entry.hideDates : false,
|
||||||
|
};
|
||||||
|
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||||
|
resolver: zodResolver(schemas.insert),
|
||||||
|
defaultValues: defaultValues,
|
||||||
|
// values: params.entry ? ut(ct(tt(ff(params.entry)))) : defaultValues
|
||||||
|
})
|
||||||
|
const createMutation = trpc.entry.insert.useMutation({onSuccess: makeOnSuccess('create',form,setIsUpdate)})
|
||||||
|
const updateMutation = trpc.entry.update.useMutation({onSuccess: makeOnSuccess('update',form)})
|
||||||
|
function onSubmit(values: z.infer<typeof schemas.insert>) {
|
||||||
|
isUpdate ?
|
||||||
|
updateMutation.mutate(values) :
|
||||||
|
createMutation.mutate(values)
|
||||||
|
}
|
||||||
|
//TODO use SelectFormField and TextInputFormField
|
||||||
|
return (
|
||||||
|
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
||||||
|
<Card.CardHeader>
|
||||||
|
<Card.CardTitle>
|
||||||
|
<DependentText bool={isUpdate} true='Update Entry' false='Create Entry' />
|
||||||
|
</Card.CardTitle>
|
||||||
|
</Card.CardHeader>
|
||||||
|
<Card.CardContent>
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="space-y-8"
|
||||||
|
>
|
||||||
|
<SelectFormField control={form.control} name='categoryId' label='Category' defaultValue={categoryId} placeholder={categoryName}>
|
||||||
|
{
|
||||||
|
categoriesSuccess ?
|
||||||
|
<>
|
||||||
|
{categories.map((c) => {
|
||||||
|
return (<SelectItem key={c.id} value={c.id}> {c.name} </SelectItem>)
|
||||||
|
})}
|
||||||
|
</> :
|
||||||
|
<SelectItem key="abc" value="abcd"/>
|
||||||
|
}
|
||||||
|
</SelectFormField>
|
||||||
|
<TexttInputFormField control={form.control} name='title' label='Title'/>
|
||||||
|
<MdeFormField control={form.control} name='description' label='Description' dataColorMode={(theme as "dark" | "light") ? (theme as "dark" | "light") : "dark"}/>
|
||||||
|
<CalendarFormField control={form.control} name='fromTime' label='From Date'/>
|
||||||
|
<CalendarFormField control={form.control} name='toTime' label='To Date'/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="hideDates"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Hide dates</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value ? field.value : false}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button type="submit">
|
||||||
|
<DependentText bool={isUpdate} true='Update' false='Create' />
|
||||||
|
</Button>
|
||||||
|
<DependentFormMessaage falseStatus={createMutation.status} trueStatus={updateMutation.status} falseError={createMutation.error} trueError={updateMutation.error} bool={isUpdate} />
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</Card.CardContent>
|
||||||
|
</Card.Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
'use client'
|
|
||||||
import { updateRouteSchema, updateRouteSchemaCliennt } from "~/lib/schema/cv/entry"
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
|
||||||
import { useForm } from 'react-hook-form'
|
|
||||||
import { z } from "zod";
|
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
|
||||||
import { Input } from "~/components/ui/input";
|
|
||||||
import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~/components/ui/select";
|
|
||||||
import { trpc } from "~/app/_trpc/Client";
|
|
||||||
import { Button } from "~/components/ui/button";
|
|
||||||
import * as Card from '~/components/ui/card'
|
|
||||||
import { format } from 'date-fns'
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
|
||||||
import { cn, type Element } from "~/lib/utils";
|
|
||||||
import { CalendarIcon, Delete } from "lucide-react";
|
|
||||||
import { Calendar } from "~/components/ui/calendar";
|
|
||||||
import { Textarea } from "~/components/ui/textarea";
|
|
||||||
import { usePathname, useRouter } from "next/navigation";
|
|
||||||
import { Checkbox } from "~/components/ui/checkbox";
|
|
||||||
import MDEditor from '@uiw/react-md-editor'
|
|
||||||
import { useTheme } from "next-themes"
|
|
||||||
import type { EntryRouterOutputs } from "~/server/routers/cv/entry";
|
|
||||||
|
|
||||||
export default function UpdateCvEntryForm(params : { id: string, className: string | undefined }) {
|
|
||||||
const { theme } = useTheme()
|
|
||||||
const id = params.id
|
|
||||||
const categories = trpc.cv.category.list.useQuery()
|
|
||||||
const entry = trpc.cv.entry.get.useQuery({ id: id })
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const form = useForm<z.infer<typeof updateRouteSchemaCliennt>>({
|
|
||||||
resolver: zodResolver(updateRouteSchemaCliennt),
|
|
||||||
defaultValues: {
|
|
||||||
by: { id: id },
|
|
||||||
update: {
|
|
||||||
categoryId: entry.data?.categoryId,
|
|
||||||
title: entry.data?.title,
|
|
||||||
description: entry.data?.description,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const updateFormValues = (v:EntryRouterOutputs['get']|Element<EntryRouterOutputs['update']>) => {
|
|
||||||
form.setValue('update.title', v?.title)
|
|
||||||
form.setValue('update.description', v?.description);
|
|
||||||
form.setValue('update.fromTime', new Date(Date.parse(v ? v.fromTime : "")))
|
|
||||||
form.setValue('update.toTime', new Date(Date.parse(v ? v.toTime : "")))
|
|
||||||
form.setValue('update.categoryId', v?.categoryId)
|
|
||||||
form.setValue('update.hideDates', v?.hideDates)
|
|
||||||
}
|
|
||||||
entry.promise.then((v) => {
|
|
||||||
updateFormValues(v)
|
|
||||||
})
|
|
||||||
const updateMutation = trpc.cv.entry.update.useMutation({
|
|
||||||
onSuccess: (v) => {
|
|
||||||
if (v[0] !== undefined) {
|
|
||||||
updateFormValues(v[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const deleteMutation = trpc.cv.entry.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
if (!pathname.includes('list')) {
|
|
||||||
router.back()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const deleteEntry = (id: string) => {
|
|
||||||
deleteMutation.mutate(id)
|
|
||||||
}
|
|
||||||
function onSubmit(values: z.infer<typeof updateRouteSchemaCliennt>) {
|
|
||||||
let { title, categoryId, description, hideDates } = values.update;
|
|
||||||
updateMutation.mutate({
|
|
||||||
by: { id: id },
|
|
||||||
update: {
|
|
||||||
fromTime: format(values.update.fromTime, 'yyyy-MM-dd'),
|
|
||||||
toTime: format(values.update.toTime, 'yyyy-MM-dd'),
|
|
||||||
title: title,
|
|
||||||
categoryId: categoryId,
|
|
||||||
description: description,
|
|
||||||
hideDates: hideDates
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (entry.data !== undefined) {
|
|
||||||
return (
|
|
||||||
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
|
||||||
<Card.CardHeader>
|
|
||||||
<Card.CardTitle>
|
|
||||||
Create Entry
|
|
||||||
</Card.CardTitle>
|
|
||||||
</Card.CardHeader>
|
|
||||||
<Card.CardContent>
|
|
||||||
<Form {...form}>
|
|
||||||
<form
|
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
|
||||||
className="space-y-8"
|
|
||||||
>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.categoryId"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Category
|
|
||||||
</FormLabel>
|
|
||||||
{
|
|
||||||
categories.data ? (
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={categories.data[0]?.id}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder={categories.data[0]?.name ? categories.data[0]?.name : "Select category"} />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
{categories.data?.map((c) => {
|
|
||||||
return (<SelectItem key={c.id} value={c.id}> {c.name} </SelectItem>)
|
|
||||||
})}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
) : <Select></Select>
|
|
||||||
}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.title"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Title
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="title" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.description"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Description
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<MDEditor
|
|
||||||
value={field.value ? field.value : ""}
|
|
||||||
onChange={field.onChange}
|
|
||||||
data-color-mode={theme ? theme : "dark"}
|
|
||||||
previewOptions={{skipHtml:false}}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.fromTime"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col">
|
|
||||||
<FormLabel>From Date</FormLabel>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<FormControl>
|
|
||||||
<Button
|
|
||||||
variant={"outline"}
|
|
||||||
className={cn(
|
|
||||||
"w-[240px] pl-3 text-left font-normal",
|
|
||||||
!field.value && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{field.value ? (
|
|
||||||
format(field.value, "PPP")
|
|
||||||
) : (
|
|
||||||
<span>Pick a date</span>
|
|
||||||
)}
|
|
||||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
|
||||||
<Calendar
|
|
||||||
mode="single"
|
|
||||||
selected={field.value}
|
|
||||||
onSelect={field.onChange}
|
|
||||||
disabled={(date) =>
|
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
|
||||||
}
|
|
||||||
initialFocus
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.toTime"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col">
|
|
||||||
<FormLabel>To Date</FormLabel>
|
|
||||||
<Popover>
|
|
||||||
<PopoverTrigger asChild>
|
|
||||||
<FormControl>
|
|
||||||
<Button
|
|
||||||
variant={"outline"}
|
|
||||||
className={cn(
|
|
||||||
"w-[240px] pl-3 text-left font-normal",
|
|
||||||
!field.value && "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{field.value ? (
|
|
||||||
format(field.value, "PPP")
|
|
||||||
) : (
|
|
||||||
<span>Pick a date</span>
|
|
||||||
)}
|
|
||||||
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
||||||
</Button>
|
|
||||||
</FormControl>
|
|
||||||
</PopoverTrigger>
|
|
||||||
<PopoverContent className="w-auto p-0" align="start">
|
|
||||||
<Calendar
|
|
||||||
mode="single"
|
|
||||||
selected={field.value}
|
|
||||||
onSelect={field.onChange}
|
|
||||||
disabled={(date) =>
|
|
||||||
date > new Date() || date < new Date("1900-01-01")
|
|
||||||
}
|
|
||||||
initialFocus
|
|
||||||
/>
|
|
||||||
</PopoverContent>
|
|
||||||
</Popover>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="update.hideDates"
|
|
||||||
render={({field}) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>Hide dates</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value ? field.value : false}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button type="submit"> Update </Button>
|
|
||||||
<FormMessage className={cn(updateMutation.status == "success" ? "text-green-500" : "text-red-500", "flex flex-row justify-between")}>
|
|
||||||
{updateMutation.error ? updateMutation.error.message : updateMutation.status}
|
|
||||||
<Button
|
|
||||||
className="ml-auto cursor-pointer" variant="destructive" onClick={() => { deleteEntry(id) }}>
|
|
||||||
Delete
|
|
||||||
<Delete />
|
|
||||||
</Button>
|
|
||||||
</FormMessage>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
</Card.CardContent>
|
|
||||||
</Card.Card>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import CreateCvEntryForm from "../_components/CreateForm"
|
import CreateUpdateCvEntryForm from "../_components/CreateUpdateForm"
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<CreateCvEntryForm/>
|
<CreateUpdateCvEntryForm />
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,19 +7,19 @@ import * as Card from '~/components/ui/card'
|
|||||||
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
import { useGsapContext } from "~/app/_providers/GsapProvicer";
|
||||||
|
|
||||||
export default function CvPage() {
|
export default function CvPage() {
|
||||||
const cvEntries = trpc.cv.entry.list.useQuery();
|
const entires = trpc.entry.select.useQuery({});
|
||||||
const gsap = useGsapContext()
|
const gsap = useGsapContext()
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
useGSAP(() => {
|
useGSAP(() => {
|
||||||
gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } })
|
gsap?.from('.gsapan', { x: -100, opacity: 0, duration: 0.5, stagger: { each: 0.3 } })
|
||||||
}, { scope: container, dependencies: [cvEntries.status], revertOnUpdate: true });
|
}, { scope: container, dependencies: [entires.status], revertOnUpdate: true });
|
||||||
return (
|
return (
|
||||||
<div ref={container} className="w-5/6 lg:w-1/2 flex flex-col gap-3">
|
<div ref={container} className="w-5/6 lg:w-1/2 flex flex-col gap-3">
|
||||||
{cvEntries.data == undefined ?
|
{entires.data == undefined ?
|
||||||
<div className="gsapan"></div>
|
<div className="gsapan"></div>
|
||||||
:
|
:
|
||||||
<>
|
<>
|
||||||
{cvEntries.data.map((ent) => {
|
{entires.data.map((ent) => {
|
||||||
return (
|
return (
|
||||||
<Card.Card className="gsapan" key={ent.id}>
|
<Card.Card className="gsapan" key={ent.id}>
|
||||||
<Link href={`/admin/cv/entry/${ent.id}`}>
|
<Link href={`/admin/cv/entry/${ent.id}`}>
|
||||||
@@ -31,9 +31,8 @@ export default function CvPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
<Card.CardContent className="flex flex-row">
|
<Card.CardContent className="flex flex-row">
|
||||||
<div>
|
<div>
|
||||||
Category id : {ent.id} <br />
|
Category:
|
||||||
Category Name :
|
<Link href={`/admin/cv/category/${ent.categoryId}`}> {ent.categoryId} </Link>
|
||||||
<Link href={`/admin/cv/entry/${ent.categoryId}`}> {ent.category?.name} </Link>
|
|
||||||
</div>
|
</div>
|
||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
</Card.Card>
|
</Card.Card>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { ChevronsUpDown, Plus } from "lucide-react"
|
|||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
||||||
import { type Element } from "~/lib/utils";
|
import { type Element } from "~/lib/utils";
|
||||||
import CreateUpdateProjectForm from "./CreateForm";
|
import CreateUpdateProjectForm from "./CreateUpdateProjectForm";
|
||||||
import type { ProjectRouterOutputs } from "~/server/routers/project";
|
import type { RouterOutputs } from "~/server/routers/_app";
|
||||||
export default function CollapsibleForm(params:{project:Element<ProjectRouterOutputs['list']>|undefined}) {
|
export default function CollapsibleForm(params:{project?:Element<RouterOutputs['project']['select']>}) {
|
||||||
return (
|
return (
|
||||||
<Collapsible >
|
<Collapsible >
|
||||||
<CollapsibleTrigger asChild>
|
<CollapsibleTrigger asChild>
|
||||||
@@ -18,7 +18,7 @@ export default function CollapsibleForm(params:{project:Element<ProjectRouterOut
|
|||||||
{
|
{
|
||||||
params.project ?
|
params.project ?
|
||||||
<CreateUpdateProjectForm className="w-full" project={params.project}/> :
|
<CreateUpdateProjectForm className="w-full" project={params.project}/> :
|
||||||
<CreateUpdateProjectForm className="w-full" project={params.project}/>
|
<CreateUpdateProjectForm className="w-full"/>
|
||||||
|
|
||||||
}
|
}
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { insertSchema } from "~/lib/schema/project/project"
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -9,41 +8,56 @@ import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~
|
|||||||
import { trpc } from "~/app/_trpc/Client";
|
import { trpc } from "~/app/_trpc/Client";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import * as Card from '~/components/ui/card'
|
import * as Card from '~/components/ui/card'
|
||||||
import type { Entries } from 'type-fest'
|
import type { Entries, IterableElement } from 'type-fest'
|
||||||
import { type Element } from "~/lib/utils";
|
import { entitySchemas, makeOnSuccess } from "~/lib/utils";
|
||||||
import type { ProjectRouterOutputs } from "~/server/routers/project";
|
import { Suspense, useEffect, useState } from "react";
|
||||||
import { Suspense } from "react";
|
import type { RouterOutputs } from '~/server/routers/_app';
|
||||||
export default function CreateUpdateProjectForm(params:{className?:string, project?:Element<ProjectRouterOutputs['list']>}) {
|
import TexttInputFormField from '~/app/_components/TextInputFormField';
|
||||||
const [techStacks,] = trpc.project.stack.list.useSuspenseQuery()
|
import SelectFormField from '~/app/_components/SelectFormField';
|
||||||
const form = useForm<z.infer<typeof insertSchema>>({
|
import DependentFormMessaage from '~/app/_components/MutationFormMessage';
|
||||||
resolver: zodResolver(insertSchema),
|
import DependentText from '~/app/_components/DependentText';
|
||||||
|
export default function CreateUpdateProjectForm(params: { className?: string, project?: IterableElement<RouterOutputs['project']['select']>, isUpdate?: boolean }) {
|
||||||
|
const [isUpdate, setIsUpdate] = useState<boolean>(params.isUpdate ? params.isUpdate : (params.project ? true : false))
|
||||||
|
const [stackId, setstackId] = useState<string | undefined>()
|
||||||
|
const [stackName, setstackName] = useState<string | undefined>()
|
||||||
|
const schemas = entitySchemas('project')
|
||||||
|
const { data: stacks, isSuccess: stacksSuccess } = trpc.techStack.select.useQuery({})
|
||||||
|
useEffect(() => {
|
||||||
|
if (isUpdate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setstackId(stacks?.at(0)?.id)
|
||||||
|
if (stacks !== undefined) {
|
||||||
|
setstackName(stacks.at(0)?.stackItems ? stacks.at(0)?.stackItems?.join("-") : "")
|
||||||
|
}
|
||||||
|
}, [stacksSuccess])
|
||||||
|
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||||
|
resolver: zodResolver(schemas.insert),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: params.project ? params.project.id : crypto.randomUUID(),
|
id: params.project ? params.project.id : crypto.randomUUID(),
|
||||||
|
stackId: params.project ? params.project.stackId : stacksSuccess ? stacks?.at(0)?.id : "",
|
||||||
|
releaseStatus: params.project ? params.project.releaseStatus : "unreleased",
|
||||||
|
releaseLink: params.project ? params.project.releaseLink : "",
|
||||||
|
sourceType: params.project ? params.project.sourceType : "open",
|
||||||
|
sourceLink: params.project ? params.project.sourceLink : ""
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const createMutation = trpc.project.create.useMutation({
|
const createMutation = trpc.project.insert.useMutation({
|
||||||
onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") }
|
onSuccess: makeOnSuccess('create',form,setIsUpdate)
|
||||||
})
|
})
|
||||||
const updateMutation = trpc.project.update.useMutation({
|
const updateMutation = trpc.project.update.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: makeOnSuccess('update',form)
|
||||||
if (data.length > 0 && data[0] !== undefined) {
|
|
||||||
let entries = Object.entries(data[0]) as Entries<typeof data[0]>
|
|
||||||
entries.forEach( (entry) => {
|
|
||||||
form.setValue(entry[0],entry[1])
|
|
||||||
})
|
})
|
||||||
}
|
function onSubmit(values: z.infer<typeof schemas.insert>) {
|
||||||
}
|
|
||||||
})
|
|
||||||
function onSubmit(values: z.infer<typeof insertSchema>) {
|
|
||||||
params.project ?
|
params.project ?
|
||||||
updateMutation.mutate({by: {id: values.id}, update: { ...values}}) :
|
updateMutation.mutate(values) :
|
||||||
createMutation.mutate(values)
|
createMutation.mutate(values)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
||||||
<Card.CardHeader>
|
<Card.CardHeader>
|
||||||
<Card.CardTitle>
|
<Card.CardTitle>
|
||||||
Create Entry
|
<DependentText bool={isUpdate} true='Update Project' false='Create Project'/>
|
||||||
</Card.CardTitle>
|
</Card.CardTitle>
|
||||||
</Card.CardHeader>
|
</Card.CardHeader>
|
||||||
<Card.CardContent>
|
<Card.CardContent>
|
||||||
@@ -52,99 +66,32 @@ export default function CreateUpdateProjectForm(params:{className?:string, proje
|
|||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-8"
|
className="space-y-8"
|
||||||
>
|
>
|
||||||
<FormField
|
<SelectFormField control={form.control} name='stackId' label='Stack' defaultValue={stackId} placeholder={stackName} >
|
||||||
control={form.control}
|
{
|
||||||
name="stackId"
|
stacksSuccess ?
|
||||||
render={({ field }) => (
|
<>
|
||||||
<FormItem>
|
{stacks.map((stack) => {
|
||||||
<FormLabel>
|
|
||||||
Stack
|
|
||||||
</FormLabel>
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value ? field.value : ""}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder={field.value ? field.value : ""} />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<Suspense fallback={(<> </>)}>
|
|
||||||
{techStacks.map((stack) => {
|
|
||||||
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
|
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
|
||||||
})}
|
})}
|
||||||
</Suspense>
|
</> :
|
||||||
</SelectContent>
|
<SelectItem key='placeholder' value="placeholder" />
|
||||||
</Select>
|
}
|
||||||
</FormItem>
|
</SelectFormField>
|
||||||
)}
|
<TexttInputFormField control={form.control} name='title' label='Title' />
|
||||||
/>
|
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="title"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Title
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
|
||||||
<Input placeholder="title" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="sourceType"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
Source Type
|
|
||||||
</FormLabel>
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value ? field.value : "open"}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder={field.value} />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="open"> open </SelectItem>
|
<SelectItem value="open"> open </SelectItem>
|
||||||
<SelectItem value="closed"> closed </SelectItem>
|
<SelectItem value="closed"> closed </SelectItem>
|
||||||
</SelectContent>
|
</SelectFormField>
|
||||||
</Select>
|
<TexttInputFormField control={form.control} label='Source Link' name='sourceLink' />
|
||||||
</FormItem>
|
<SelectFormField control={form.control} name='releaseStatus' label='Release Status' defaultValue={'unreleased'} placeholder='unreleased' >
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="releaseStatus"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-col">
|
|
||||||
<FormLabel>Release Status</FormLabel>
|
|
||||||
<Select onValueChange={field.onChange} defaultValue={field.value ? field.value : "released"}>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue placeholder={field.value} />
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="released"> released </SelectItem>
|
<SelectItem value="released"> released </SelectItem>
|
||||||
<SelectItem value="unreleased"> unreleased </SelectItem>
|
<SelectItem value="unreleased"> unreleased </SelectItem>
|
||||||
</SelectContent>
|
</SelectFormField>
|
||||||
</Select>
|
<TexttInputFormField control={form.control} label='Release Link' name='releaseLink' />
|
||||||
</FormItem>
|
<Button type="submit">
|
||||||
)}
|
<DependentText bool={isUpdate} true='Update' false='Create'/>
|
||||||
/>
|
</Button>
|
||||||
<Button type="submit"> Create </Button>
|
<DependentFormMessaage falseStatus={createMutation.status} trueStatus={updateMutation.status} falseError={createMutation.error} trueError={updateMutation.error} bool={isUpdate}/>
|
||||||
<FormMessage className={updateMutation.status == "success" || createMutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
|
||||||
{ params.project ?
|
|
||||||
(
|
|
||||||
<>{updateMutation.error ? updateMutation.error.message : updateMutation.status}</>
|
|
||||||
) :
|
|
||||||
(
|
|
||||||
<>{createMutation.error ? createMutation.error.message : createMutation.status}</>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
</FormMessage>
|
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"use client"
|
"use client"
|
||||||
import { insertSchema } from "~/lib/schema/project/techStack"
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod'
|
import { zodResolver } from '@hookform/resolvers/zod'
|
||||||
import { useForm } from 'react-hook-form'
|
import { useForm } from 'react-hook-form'
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -8,24 +7,30 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
|
|||||||
import { trpc } from "~/app/_trpc/Client";
|
import { trpc } from "~/app/_trpc/Client";
|
||||||
import { Button } from "~/components/ui/button";
|
import { Button } from "~/components/ui/button";
|
||||||
import * as Card from '~/components/ui/card'
|
import * as Card from '~/components/ui/card'
|
||||||
import type { Entries } from 'type-fest'
|
import type { Entries, IterableElement } from 'type-fest'
|
||||||
import { type Element } from "~/lib/utils";
|
|
||||||
import type { StackRouter, StackRouterOutputs } from "~/server/routers/project/techStack";
|
|
||||||
import { stackItemEnum } from "~/server/db/schema";
|
import { stackItemEnum } from "~/server/db/schema";
|
||||||
import { ToggleGroup, ToggleGroupItem } from "~/components/ui/toggle-group";
|
import { ToggleGroup, ToggleGroupItem } from "~/components/ui/toggle-group";
|
||||||
|
import type { RouterOutputs } from '~/server/routers/_app';
|
||||||
|
import { entitySchemas } from '~/lib/utils';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
export default function CreateUpdateForm(params:{className:string|undefined, techStack?:Element<StackRouterOutputs['list']>}) {
|
export default function CreateUpdateStackForm(params:{className?:string, techStack?:IterableElement<RouterOutputs['techStack']['select']>,isUpdate?: boolean }) {
|
||||||
const form = useForm<z.infer<typeof insertSchema>>({
|
const schemas = entitySchemas('techStack')
|
||||||
resolver: zodResolver(insertSchema),
|
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||||
|
resolver: zodResolver(schemas.insert),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
id: params.techStack ? params.techStack.id : crypto.randomUUID(),
|
id: params.techStack ? params.techStack.id : crypto.randomUUID(),
|
||||||
stackItems: params.techStack ? params.techStack.stackItems : [],
|
stackItems: params.techStack ? params.techStack.stackItems : [],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const createMutation = trpc.project.stack.create.useMutation({
|
const [isUpdate, setIsUpdate] = useState<boolean>(params.isUpdate ? params.isUpdate : (params.techStack ? true : false))
|
||||||
onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") }
|
const createMutation = trpc.techStack.insert.useMutation({
|
||||||
|
onSuccess: (data) => {
|
||||||
|
form.setValue("id", data[0] ? data[0].id : "");
|
||||||
|
setIsUpdate(true)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
const updateMutation = trpc.project.stack.update.useMutation({
|
const updateMutation = trpc.techStack.update.useMutation({
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.length > 0 && data[0] !== undefined) {
|
if (data.length > 0 && data[0] !== undefined) {
|
||||||
let entries = Object.entries(data[0]) as Entries<typeof data[0]>
|
let entries = Object.entries(data[0]) as Entries<typeof data[0]>
|
||||||
@@ -35,9 +40,9 @@ export default function CreateUpdateForm(params:{className:string|undefined, tec
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
function onSubmit(values: z.infer<typeof insertSchema>) {
|
function onSubmit(values: z.infer<typeof schemas.insert>) {
|
||||||
params.techStack ?
|
params.techStack ?
|
||||||
updateMutation.mutate({by: {id: values.id}, update: { ...values}}) :
|
updateMutation.mutate(values) :
|
||||||
createMutation.mutate(values)
|
createMutation.mutate(values)
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { cvCategory } from "~/server/db/schema";
|
|
||||||
import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod'
|
|
||||||
import { z } from "zod";
|
|
||||||
export const selectSchema = createSelectSchema(cvCategory);
|
|
||||||
export const insertSchema = createInsertSchema(cvCategory);
|
|
||||||
export const updateSchema = createUpdateSchema(cvCategory);
|
|
||||||
export const getSchema = selectSchema.pick({id: true});
|
|
||||||
export const updateRouteSchema = z.object({
|
|
||||||
by: selectSchema.pick({id:true}),
|
|
||||||
update: updateSchema
|
|
||||||
})
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { cvEntry } from "~/server/db/schema";
|
|
||||||
import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod'
|
|
||||||
import { z } from "zod";
|
|
||||||
export const selectSchema = createSelectSchema(cvEntry);
|
|
||||||
export const insertSchema = createInsertSchema(cvEntry);
|
|
||||||
export const inserSchemaNoDates = insertSchema.omit({
|
|
||||||
fromTime:true,
|
|
||||||
toTime:true
|
|
||||||
})
|
|
||||||
export const insertSchemaForm = inserSchemaNoDates.merge(z.object({
|
|
||||||
fromTime: z.date(),
|
|
||||||
toTime: z.date()
|
|
||||||
}))
|
|
||||||
export const updateSchema = createUpdateSchema(cvEntry);
|
|
||||||
export const updateSchemaNoDates = updateSchema.omit({
|
|
||||||
fromTime:true,
|
|
||||||
toTime:true
|
|
||||||
})
|
|
||||||
export const getSchema = selectSchema.pick({id: true});
|
|
||||||
export const updateRouteSchema = z.object({
|
|
||||||
by: selectSchema.pick({id:true}),
|
|
||||||
update: updateSchema.omit({id:true})
|
|
||||||
})
|
|
||||||
export const updateRouteSchemaCliennt = z.object({
|
|
||||||
by: selectSchema.pick({id:true}),
|
|
||||||
update: updateSchemaNoDates.merge(z.object({
|
|
||||||
fromTime: z.date(),
|
|
||||||
toTime: z.date()
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { project } from "~/server/db/schema"
|
|
||||||
import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod'
|
|
||||||
import { z } from "zod";
|
|
||||||
export const selectSchema = createSelectSchema(project);
|
|
||||||
export const insertSchema = createInsertSchema(project);
|
|
||||||
export const updateSchema = createUpdateSchema(project);
|
|
||||||
export const getSchema = selectSchema.pick({id: true});
|
|
||||||
export const updateRouteSchema = z.object({
|
|
||||||
by: selectSchema.pick({id:true}),
|
|
||||||
update: updateSchema
|
|
||||||
})
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { techStack } from "~/server/db/schema"
|
|
||||||
import { createInsertSchema, createUpdateSchema, createSelectSchema} from 'drizzle-zod'
|
|
||||||
import { z } from "zod";
|
|
||||||
export const selectSchema = createSelectSchema(techStack);
|
|
||||||
export const insertSchema = createInsertSchema(techStack);
|
|
||||||
export const updateSchema = createUpdateSchema(techStack);
|
|
||||||
export const getSchema = selectSchema.pick({id: true});
|
|
||||||
export const updateRouteSchema = z.object({
|
|
||||||
by: selectSchema.pick({id:true}),
|
|
||||||
update: updateSchema
|
|
||||||
})
|
|
||||||
171
src/lib/utils.ts
171
src/lib/utils.ts
@@ -1,30 +1,67 @@
|
|||||||
import { clsx, type ClassValue } from "clsx"
|
import { clsx, type ClassValue } from "clsx"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
import { createInsertSchema, createUpdateSchema, createSelectSchema, type BuildSchema } from 'drizzle-zod'
|
import { createSchemaFactory, createSelectSchema, type BuildSchema } from 'drizzle-zod'
|
||||||
import * as schema from "~/server/db/schema";
|
import * as schema from "~/server/db/schema";
|
||||||
import { PgColumn, PgTable } from "drizzle-orm/pg-core";
|
import { PgTable } from "drizzle-orm/pg-core";
|
||||||
import type { Entries } from "type-fest";
|
import type { FieldValues, Path, UseFormReturn } from "react-hook-form";
|
||||||
import z from "zod";
|
|
||||||
import type { Prettify } from "node_modules/zod/v4/core/util.cjs";
|
|
||||||
import type { ExtractTablesWithRelations } from "drizzle-orm";
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Element<T extends Iterable<any> | undefined | null> = T extends Iterable<infer E> ? E : never;
|
|
||||||
|
|
||||||
export type Schema = typeof schema;
|
export type Schema = typeof schema;
|
||||||
|
|
||||||
export type SchemaKeys<S> = {
|
export type SchemaKeys<S> = {
|
||||||
[K in keyof S]: S[K] extends PgTable ? K : never
|
[K in keyof S]: S[K] extends PgTable ? K : never
|
||||||
}[keyof S]
|
}[keyof S]
|
||||||
|
|
||||||
export function entitySchemas<T extends SchemaKeys<Schema>>(table: T): {
|
export type Pretty<T> = {
|
||||||
insert: BuildSchema<'insert', Schema[T]['_']['columns'], undefined>
|
[K in keyof T]: T[K];
|
||||||
update: BuildSchema<'update', Schema[T]['_']['columns'], undefined>
|
} & {}
|
||||||
select: BuildSchema<'select', Schema[T]['_']['columns'], undefined>
|
|
||||||
} {
|
const { createInsertSchema, createUpdateSchema } = createSchemaFactory({
|
||||||
|
coerce: {
|
||||||
|
date: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export type DatesAreString<T extends {[x:string]: any}> = {
|
||||||
|
[K in keyof T] : T[K] extends Date ? string : T[K]
|
||||||
|
}
|
||||||
|
|
||||||
|
// type X = {
|
||||||
|
// foo: Date
|
||||||
|
// bar: number
|
||||||
|
// }
|
||||||
|
|
||||||
|
// type Y = DatesAreString<X>
|
||||||
|
|
||||||
|
export function makeOnSuccess<T extends FieldValues>(uc: 'update' | 'create', form: UseFormReturn<T>, setIsUpdate?: (arg0: boolean) => void) {
|
||||||
|
switch (uc) {
|
||||||
|
case 'update':
|
||||||
|
return (data: T[]) => {
|
||||||
|
if (data.length > 0 && data[0] !== undefined) {
|
||||||
|
for (let k in data[0]) {
|
||||||
|
form.setValue((k as unknown as Path<T>), data[0][k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'create':
|
||||||
|
if (setIsUpdate !== undefined) {
|
||||||
|
return (data: T[]) => {
|
||||||
|
form.setValue(("id" as Path<T>), data[0] ? data[0].id : "");
|
||||||
|
setIsUpdate(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function entitySchemas<T extends SchemaKeys<Schema>>(table: T)
|
||||||
|
// : {
|
||||||
|
// insert: Pretty<BuildSchema<'insert', Schema[T]['_']['columns'], undefined>>
|
||||||
|
// update: Pretty<BuildSchema<'update', Schema[T]['_']['columns'], undefined>>
|
||||||
|
// select: Pretty<BuildSchema<'select', Schema[T]['_']['columns'], undefined>>
|
||||||
|
// }
|
||||||
|
{
|
||||||
const insertSchema = createInsertSchema<Schema[T]>(schema[table]);
|
const insertSchema = createInsertSchema<Schema[T]>(schema[table]);
|
||||||
const updateSchema = createUpdateSchema<Schema[T]>(schema[table]);
|
const updateSchema = createUpdateSchema<Schema[T]>(schema[table]);
|
||||||
const selectSchema = createSelectSchema<Schema[T]>(schema[table]);
|
const selectSchema = createSelectSchema<Schema[T]>(schema[table]);
|
||||||
@@ -34,3 +71,109 @@ export function entitySchemas<T extends SchemaKeys<Schema>>(table: T): {
|
|||||||
select: selectSchema,
|
select: selectSchema,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// type StringToDateMapped<K extends string, T extends Record<K,any>> = Omit<T,K> & Record<K, Date>
|
||||||
|
|
||||||
|
// export function stringToDates<K extends string, T extends Record<K,any>>(
|
||||||
|
// obj: T,
|
||||||
|
// key: K
|
||||||
|
// ): Pretty<StringToDateMapped<K, T>> {
|
||||||
|
// return {
|
||||||
|
// ...obj,
|
||||||
|
// [key] : new Date(obj[key] as string)
|
||||||
|
// } as StringToDateMapped<K, T>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
// aproach above works but gets verbose to use
|
||||||
|
|
||||||
|
// functional concept:
|
||||||
|
//
|
||||||
|
// function curryConverter(key?,converter) {
|
||||||
|
// if key is undefined {
|
||||||
|
// return converter
|
||||||
|
// }
|
||||||
|
// new newConverter = (object) => {
|
||||||
|
// object = converter(object)
|
||||||
|
// convertOpereration(key,object)
|
||||||
|
// }
|
||||||
|
// return function (key) => {
|
||||||
|
// if key is undefined {
|
||||||
|
// curryConverter(key,newConverter)
|
||||||
|
// } else {
|
||||||
|
// return newConverter
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// curryConverter('nextKey',objectStrongToDateConverterCreator('prevKey'))('veryNextKey')()
|
||||||
|
// - key is defined
|
||||||
|
// - new converter created that applies previous converter
|
||||||
|
// - function is returned that accepts key
|
||||||
|
// - function gets called with veryNextKey at which point curryConverter gets Called again
|
||||||
|
// - since key is defined the cycle repeats once more
|
||||||
|
// - a function is returned that accepts key
|
||||||
|
// - no key is provided so the newConveerter from previous cycle gets returned
|
||||||
|
// - sadly i believe the type inference wont work but i will try
|
||||||
|
// - i was right it is not possible to corretly get return type inference for anything recursive no matter how hard you try to hack it :(
|
||||||
|
// - see: https://github.com/microsoft/TypeScript/issues/3336
|
||||||
|
// - leaving this as comment as a cautionary tale so i wont get lost in the sauce again
|
||||||
|
//
|
||||||
|
|
||||||
|
// type StDRecord<K extends string,T extends Record<K,any>> = Pretty<Omit<T,K> & Record<K,Date>>
|
||||||
|
// type Converter<K extends string> = <T extends Record<K, any>>(obj: T) => StDRecord<K,T>
|
||||||
|
// type CurriedConverter<PK extends string, K extends string|undefined> = (converter:Converter<PK>,key?: K) => K extends string ?
|
||||||
|
// CurriedConverter<K,typeof key> :
|
||||||
|
// Converter<PK>
|
||||||
|
|
||||||
|
// function curriedConverter<PK extends string>(converter: Converter<PK>): Converter<PK>;
|
||||||
|
// function curriedConverter<PK extends string, K extends string>(
|
||||||
|
// converter: Converter<PK>,
|
||||||
|
// key: K
|
||||||
|
// ): (k?: string) => Pretty<ReturnType<CurriedConverter<K,typeof k>>>;
|
||||||
|
// // Implementation
|
||||||
|
// function curriedConverter<PK extends string, K extends string | undefined>(
|
||||||
|
// converter: Converter<PK>,
|
||||||
|
// key?: K
|
||||||
|
// ): unknown {
|
||||||
|
// if (key === undefined) {
|
||||||
|
// return converter;
|
||||||
|
// }
|
||||||
|
// let newConverter = <T extends Record<typeof key, any>>(obj: T): Pretty<Omit<T, typeof key> & Record<typeof key, Date>> => {
|
||||||
|
// let updated = converter(obj as Record<string,any>);
|
||||||
|
// return {
|
||||||
|
// ...updated,
|
||||||
|
// [key]: new Date(updated[key as unknown as keyof typeof updated])
|
||||||
|
// } as any;
|
||||||
|
// };
|
||||||
|
// return function (nk?: string) {
|
||||||
|
// if (nk == undefined) {
|
||||||
|
// return curriedConverter(newConverter,key);
|
||||||
|
// }
|
||||||
|
// return newConverter;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
function objectStringToDateConverterCreator<K extends string>(key: K) {
|
||||||
|
return function <T extends Record<K, any>>(obj: T): Omit<T, K> & Record<K, Date> {
|
||||||
|
if (!(key in obj)) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if (typeof obj[key] !== 'string') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...obj,
|
||||||
|
[key]: new Date(obj[key])
|
||||||
|
} as any;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
type StrToDatesObj = { fromTime: string | null, toTime: string | null, createdAt: string | null, updatedAt: string | null }
|
||||||
|
export const ft = objectStringToDateConverterCreator('fromTime')
|
||||||
|
export const tt = objectStringToDateConverterCreator('toTime')
|
||||||
|
export const ct = objectStringToDateConverterCreator('createdAt')
|
||||||
|
export const ut = objectStringToDateConverterCreator('updatedAt')
|
||||||
|
export const strsToDates = (obj: Pretty<StrToDatesObj>) => {
|
||||||
|
return ft(tt(ct(ut(obj))))
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,16 +33,16 @@ export const cvEntry = createTable(
|
|||||||
(d) => ({
|
(d) => ({
|
||||||
id: d.uuid().primaryKey().notNull(),
|
id: d.uuid().primaryKey().notNull(),
|
||||||
categoryId: d.uuid('category_id'),
|
categoryId: d.uuid('category_id'),
|
||||||
fromTime: d.date().notNull(),
|
fromTime: d.timestamp().notNull().$type<Date>(),
|
||||||
toTime: d.date().notNull(),
|
toTime: d.timestamp().notNull().$type<Date>(),
|
||||||
title: d.varchar({length:50}).notNull(),
|
title: d.varchar({length:50}).notNull(),
|
||||||
description: d.text(),
|
description: d.text(),
|
||||||
hideDates: d.boolean(),
|
hideDates: d.boolean(),
|
||||||
createdAt: d
|
createdAt: d
|
||||||
.timestamp({ withTimezone: true })
|
.timestamp({ withTimezone: true })
|
||||||
.default(sql`CURRENT_TIMESTAMP`)
|
.default(sql`CURRENT_TIMESTAMP`)
|
||||||
.notNull(),
|
.notNull().$type<Date>(),
|
||||||
updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()),
|
updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()).$type<Date>(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,13 @@ import { isAdmin } from "~/app/actions";
|
|||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { entitySchemas, type Schema, type SchemaKeys } from "~/lib/utils";
|
import { entitySchemas, type Schema, type SchemaKeys } from "~/lib/utils";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function isKeyOf<T extends object>(k: any, obj: T): k is keyof T {
|
function isKeyOf<T extends object>(k: any, obj: T): k is keyof T {
|
||||||
return k in obj
|
return k in obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this sucks should have never refactored to it
|
||||||
export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(table: T) {
|
export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(table: T) {
|
||||||
const schemas = entitySchemas<T>(table)
|
const schemas = entitySchemas<T>(table)
|
||||||
if (!isKeyOf(table, schema)) {
|
if (!isKeyOf(table, schema)) {
|
||||||
@@ -46,19 +43,14 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
const { input } = opts;
|
const { input } = opts;
|
||||||
|
console.log(input)
|
||||||
if (input === undefined) {
|
if (input === undefined) {
|
||||||
throw new TRPCError({ message: "no update input", code: "BAD_REQUEST" })
|
throw new TRPCError({ message: "no update input", code: "BAD_REQUEST" })
|
||||||
}
|
}
|
||||||
const conds = (Object.entries(input) as Entries<typeof input>).map(([k, v]) => {
|
if (!isKeyOf('id',input) || !isKeyOf('id',schema[table])) {
|
||||||
if (!isKeyOf(k, schema[table])) {
|
throw new TRPCError({ message: "no id provided", code: "BAD_REQUEST" })
|
||||||
throw new TRPCError({ message: "Invalid key for column", code: "BAD_REQUEST" });
|
|
||||||
}
|
}
|
||||||
return eq(schema[table][k], v)
|
return await db.update(schema[table]).set(input).where(eq(schema[table]['id'],input['id'])).returning().execute();
|
||||||
})
|
|
||||||
if (conds.length > 0) {
|
|
||||||
return await db.update(schema[table]).set(input).where(and(...conds)).returning().execute();
|
|
||||||
}
|
|
||||||
throw new TRPCError({ message: "trying to update all entities", code: "BAD_REQUEST" })
|
|
||||||
}),
|
}),
|
||||||
insert: publicProcedure.input(schemas.insert).mutation(async (opts) => {
|
insert: publicProcedure.input(schemas.insert).mutation(async (opts) => {
|
||||||
let admin = await isAdmin();
|
let admin = await isAdmin();
|
||||||
@@ -86,7 +78,7 @@ export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(ta
|
|||||||
return eq(schema[table][k], v)
|
return eq(schema[table][k], v)
|
||||||
})
|
})
|
||||||
if (conds.length > 0) {
|
if (conds.length > 0) {
|
||||||
return await db.update(schema[table]).where(and(...conds)).returning().execute();
|
return await db.delete(schema[table]).where(and(...conds)).returning().execute();
|
||||||
}
|
}
|
||||||
throw new TRPCError({ message: "trying to delete all entities", code: "BAD_REQUEST" })
|
throw new TRPCError({ message: "trying to delete all entities", code: "BAD_REQUEST" })
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import type { inferRouterOutputs } from "@trpc/server";
|
import type { inferRouterOutputs } from "@trpc/server";
|
||||||
import { router } from "../trpc";
|
import { router } from "../trpc";
|
||||||
import { CvRouter } from "./cv";
|
|
||||||
import type { inferReactQueryProcedureOptions } from "@trpc/react-query";
|
import type { inferReactQueryProcedureOptions } from "@trpc/react-query";
|
||||||
import { ProjectRouter } from "./project";
|
import { trpcCrudRouterFromDrizzleEntity } from "../lib";
|
||||||
|
import type { inferRouterMeta } from "@trpc/server/unstable-core-do-not-import";
|
||||||
|
|
||||||
|
const { router : project } = trpcCrudRouterFromDrizzleEntity('project')
|
||||||
|
const { router : techStack } = trpcCrudRouterFromDrizzleEntity('techStack')
|
||||||
|
const { router : category } = trpcCrudRouterFromDrizzleEntity('cvCategory')
|
||||||
|
const { router : entry } = trpcCrudRouterFromDrizzleEntity('cvEntry')
|
||||||
|
const root = {}
|
||||||
export const trpcRouter = router({
|
export const trpcRouter = router({
|
||||||
cv: CvRouter,
|
project: project,
|
||||||
project: ProjectRouter
|
techStack: techStack,
|
||||||
|
category: category,
|
||||||
|
entry: entry
|
||||||
})
|
})
|
||||||
|
|
||||||
export type TrpcRouter = typeof trpcRouter
|
export type TrpcRouter = typeof trpcRouter
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
import { db } from "~/server/db";
|
|
||||||
import { publicProcedure, router } from "~/server/trpc";
|
|
||||||
import { cvCategory, cvEntry } from "~/server/db/schema";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { isAdmin } from "~/app/actions";
|
|
||||||
|
|
||||||
import * as Schemas from "~/lib/schema/cv/category"
|
|
||||||
import { TRPCError, type inferRouterOutputs } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const CategoryRouter = router({
|
|
||||||
list: publicProcedure.query(async () => {
|
|
||||||
const categories = await db.query.cvCategory.findMany({
|
|
||||||
orderBy: (model, { desc }) => desc(model.name),
|
|
||||||
with: {
|
|
||||||
cvEntry: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return categories;
|
|
||||||
}),
|
|
||||||
get: publicProcedure.input(Schemas.getSchema).query(async (opts) => {
|
|
||||||
const { input } = opts
|
|
||||||
const categories = await db.query.cvCategory.findMany({
|
|
||||||
with: {
|
|
||||||
cvEntry: true
|
|
||||||
},
|
|
||||||
where: eq(cvCategory.id, input.id),
|
|
||||||
limit: 1
|
|
||||||
})
|
|
||||||
if (categories[0] !== undefined) {
|
|
||||||
return categories[0]
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
delete: publicProcedure.input(z.string()).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
db.delete(cvCategory).where(eq(cvCategory.id,input)).execute()
|
|
||||||
}),
|
|
||||||
create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
const category = await db.insert(cvCategory).values(input).returning().execute()
|
|
||||||
return category
|
|
||||||
}),
|
|
||||||
update: publicProcedure
|
|
||||||
.input(Schemas.updateRouteSchema)
|
|
||||||
.mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
const category = await db.update(cvCategory)
|
|
||||||
.set(input.update)
|
|
||||||
.returning()
|
|
||||||
.where(eq(cvCategory.id, input.by.id))
|
|
||||||
return category
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
export type CategoryRouterOutputs = inferRouterOutputs<typeof CategoryRouter>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import { db } from "~/server/db";
|
|
||||||
import { publicProcedure, router } from "~/server/trpc";
|
|
||||||
import { cvEntry } from "~/server/db/schema";
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { isAdmin } from "~/app/actions";
|
|
||||||
|
|
||||||
import * as Schemas from "~/lib/schema/cv/entry"
|
|
||||||
import { TRPCError, type inferRouterOutputs } from "@trpc/server";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
export const EntryRouter = router({
|
|
||||||
list: publicProcedure.query(async () => {
|
|
||||||
const entries = await db.query.cvEntry.findMany({
|
|
||||||
orderBy: (model, { desc }) => desc(model.toTime),
|
|
||||||
with: {
|
|
||||||
category: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return entries;
|
|
||||||
}),
|
|
||||||
get: publicProcedure.input(Schemas.getSchema).query(async (opts) => {
|
|
||||||
const { input } = opts
|
|
||||||
const entries = await db.query.cvEntry.findMany({
|
|
||||||
with: {
|
|
||||||
category: true
|
|
||||||
},
|
|
||||||
where: eq(cvEntry.id, input.id),
|
|
||||||
limit: 1
|
|
||||||
})
|
|
||||||
if (entries[0] !== undefined) {
|
|
||||||
return entries[0]
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
delete: publicProcedure.input(z.string()).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
db.delete(cvEntry).where(eq(cvEntry.id,input)).execute()
|
|
||||||
}),
|
|
||||||
create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
return await db.insert(cvEntry).values(input).returning().execute()
|
|
||||||
}),
|
|
||||||
update: publicProcedure
|
|
||||||
.input(Schemas.updateRouteSchema)
|
|
||||||
.mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
const entry = await db.update(cvEntry)
|
|
||||||
.set(input.update)
|
|
||||||
.returning()
|
|
||||||
.where(eq(cvEntry.id, input.by.id))
|
|
||||||
return entry
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
export type EntryRouterOutputs = inferRouterOutputs<typeof EntryRouter>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import { router } from "~/server/trpc"
|
|
||||||
import { CategoryRouter } from "./category"
|
|
||||||
import { EntryRouter } from "./entry"
|
|
||||||
import { trpcCrudRouterFromDrizzleEntity } from "~/server/lib"
|
|
||||||
import * as schema from '~/server/db/schema'
|
|
||||||
|
|
||||||
const { router : categoryv2 } = trpcCrudRouterFromDrizzleEntity('cvCategory')
|
|
||||||
const { router : entryv2 } = trpcCrudRouterFromDrizzleEntity('cvEntry')
|
|
||||||
export const CvRouter = router({
|
|
||||||
category: categoryv2,
|
|
||||||
entry:entryv2
|
|
||||||
})
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import { publicProcedure, router } from "~/server/trpc"
|
|
||||||
import { db } from "~/server/db";
|
|
||||||
import * as Schemas from '~/lib/schema/project/project'
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { project } from "~/server/db/schema";
|
|
||||||
import { isAdmin } from "~/app/actions";
|
|
||||||
import z from "zod";
|
|
||||||
import { StackRouter } from "./techStack";
|
|
||||||
import { TRPCError, type inferRouterOutputs} from "@trpc/server";
|
|
||||||
import { trpcCrudRouterFromDrizzleEntity } from "~/server/lib";
|
|
||||||
const { router : project } = trpcCrudRouterFromDrizzleEntity('project')
|
|
||||||
const { router : techStack } = trpcCrudRouterFromDrizzleEntity('techStack')
|
|
||||||
export const ProjectRouter = router({
|
|
||||||
list: publicProcedure.query(async () => {
|
|
||||||
return await db.query.project.findMany({
|
|
||||||
orderBy: (model, {desc}) => desc(model.title),
|
|
||||||
with: {
|
|
||||||
techStack: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
get: publicProcedure.input(Schemas.getSchema).query(async (opts) => {
|
|
||||||
const {input} = opts;
|
|
||||||
const projects = await db.query.project.findMany({
|
|
||||||
with: {
|
|
||||||
techStack: true
|
|
||||||
},
|
|
||||||
where: eq(project.id,input.id),
|
|
||||||
limit: 1
|
|
||||||
});
|
|
||||||
if (projects[0] !== undefined) {
|
|
||||||
return projects[0]
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
delete: publicProcedure.input(z.string()).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
db.delete(project).where(eq(project.id,input)).execute()
|
|
||||||
}),
|
|
||||||
create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
return await db.insert(project).values(input).returning().execute();
|
|
||||||
}),
|
|
||||||
update: publicProcedure
|
|
||||||
.input(Schemas.updateRouteSchema)
|
|
||||||
.mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
return await db.update(project)
|
|
||||||
.set(input.update)
|
|
||||||
.returning()
|
|
||||||
.where(eq(project.id,input.by.id));
|
|
||||||
}),
|
|
||||||
stack: StackRouter,
|
|
||||||
})
|
|
||||||
|
|
||||||
export type ProjectRouterOutputs = inferRouterOutputs<typeof ProjectRouter>
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { publicProcedure, router } from "~/server/trpc"
|
|
||||||
import { db } from "~/server/db";
|
|
||||||
import * as Schemas from '~/lib/schema/project/techStack'
|
|
||||||
import { eq } from "drizzle-orm";
|
|
||||||
import { techStack } from "~/server/db/schema";
|
|
||||||
import { isAdmin } from "~/app/actions";
|
|
||||||
import { TRPCError, type inferRouterOutputs} from "@trpc/server";
|
|
||||||
import z from "zod";
|
|
||||||
export const StackRouter = router({
|
|
||||||
list: publicProcedure.query(async () => {
|
|
||||||
return await db.query.techStack.findMany();
|
|
||||||
}),
|
|
||||||
get: publicProcedure.input(Schemas.getSchema).query(async (opts) => {
|
|
||||||
const {input} = opts;
|
|
||||||
const techStacks = await db.query.techStack.findMany({
|
|
||||||
where: eq(techStack.id,input.id),
|
|
||||||
limit: 1
|
|
||||||
});
|
|
||||||
if (techStacks[0] !== undefined) {
|
|
||||||
return techStacks[0]
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
delete: publicProcedure.input(z.string()).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
db.delete(techStack).where(eq(techStack.id,input)).execute()
|
|
||||||
}),
|
|
||||||
create: publicProcedure.input(Schemas.insertSchema).mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
return await db.insert(techStack).values(input).returning().execute();
|
|
||||||
}),
|
|
||||||
update: publicProcedure
|
|
||||||
.input(Schemas.updateRouteSchema)
|
|
||||||
.mutation(async (opts) => {
|
|
||||||
let admin = await isAdmin()
|
|
||||||
if (!admin) {
|
|
||||||
throw new TRPCError(
|
|
||||||
{ message: "Access denied", code: "FORBIDDEN", }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const { input } = opts;
|
|
||||||
return await db.update(techStack)
|
|
||||||
.set(input.update)
|
|
||||||
.returning()
|
|
||||||
.where(eq(techStack.id,input.by.id))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
export type StackRouterOutputs = inferRouterOutputs<typeof StackRouter>
|
|
||||||
Reference in New Issue
Block a user