testing setup

This commit is contained in:
2025-08-29 14:19:38 +02:00
parent e74b30e04c
commit 869cc07fdd
46 changed files with 6710 additions and 1460 deletions

View File

@@ -18,6 +18,7 @@ export default async function AdminSideBar() {
<SimpleSidebarGroup lable="Projects">
<Link href={"/admin/project/create"}> Create Project </Link>
<Link href={"/admin/project/techStack/create"}> Create Stack </Link>
<Link href={"/admin/project/list"}> Project List </Link>
</SimpleSidebarGroup>
<SimpleSidebarGroup lable="Blog">
<Link href={"/"}> Some Blog Action </Link>

View File

@@ -10,7 +10,7 @@ export default function Page() {
const {data} = trpc.category.select.useQuery({id: id})
if (data !== undefined && data.length > 0) {
return (
<CreateUpdateCvCategoryForm category={data[0]}/>
<CreateUpdateCvCategoryForm entity={data[0]}/>
)
}
return (<></>)

View File

@@ -1,27 +0,0 @@
import { ChevronsUpDown, Plus } from "lucide-react"
import { Button } from "~/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
import { type Element } from "~/lib/utils";
import CreateUpdateCvCategoryForm from "./CreateUpdateForm";
import type { RouterOutputs } from "~/server/routers/_app";
export default function CollapsibleCvCategoryForm(params:{category?:Element<RouterOutputs['category']['select']>}) {
return (
<Collapsible >
<CollapsibleTrigger asChild>
<Button variant={"ghost"}>
{ params.category ?
<>{params.category.name} <ChevronsUpDown/></> : <>New <Plus/></>
}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="autoAlpha">
{
params.category ?
<CreateUpdateCvCategoryForm className="w-full" category={params.category} /> :
<CreateUpdateCvCategoryForm className="w-full"/>
}
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -2,96 +2,53 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { z } from "zod";
import { Form, FormControl, FormField, FormItem, FormLabel } 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 type { IterableElement } from 'type-fest'
import { entitySchemas, makeOnSuccess } from "~/lib/utils";
import type { RouterOutputs } from "~/server/routers/_app";
import DependentFormMessaage from '~/app/_components/MutationFormMessage';
import { CollapsibleForm, FormScaffold } from '~/app/_components/Form/Components';
import { useState } from 'react';
import DependentText from '~/app/_components/DependentText';
export default function CreateUpdateCvCategoryForm(params:{className?:string,category?:IterableElement<RouterOutputs['category']['select']>,isUpdate?:boolean}) {
import { SelectFormField, TextInputFormField } from '~/app/_components/Form/Fields';
import { usePathname, useRouter } from 'next/navigation';
import { SelectItem } from '~/components/ui/select';
export default function CreateUpdateCvCategoryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['category']['select']> }) {
const schemas = entitySchemas('cvCategory')
const [isUpdate,setIsUpdate] = useState<boolean>(params.isUpdate ? params.isUpdate : (params.category ? true : false))
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
const form = useForm<z.infer<typeof schemas.insert>>({
resolver: zodResolver(schemas.insert),
defaultValues: {
id: params.category ? params.category.id : crypto.randomUUID(),
name: params.category ? params.category.name : "",
layoutPosition: params.category ? params.category.layoutPosition : "col1"
id: params.entity ? params.entity.id : crypto.randomUUID(),
name: params.entity ? params.entity.name : "",
layoutPosition: params.entity ? params.entity.layoutPosition : "col1"
}
})
const createMutation = trpc.category.insert.useMutation({onSuccess: makeOnSuccess('create',form,setIsUpdate)})
const updateMutation = trpc.category.update.useMutation({onSuccess: makeOnSuccess('update',form)})
let path = usePathname()
let router = useRouter()
const createMutation = trpc.category.insert.useMutation({ onSuccess: makeOnSuccess('create', form, setId) })
const updateMutation = trpc.category.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
const deleteMutation = trpc.category.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
function onSubmit(values: z.infer<typeof schemas.insert>) {
isUpdate ?
id ?
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 Category' false='Create Category'/>
</Card.CardTitle>
</Card.CardHeader>
<Card.CardContent>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
Name
</FormLabel>
<FormControl>
<Input placeholder="name" onChange={field.onChange} value={field.value ? field.value : ""} />
</FormControl>
</FormItem>
)}
>
</FormField>
<FormField
control={form.control}
name="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().layoutPosition} />
</SelectTrigger>
<SelectContent>
{schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
<SelectItem key={o} value={o}> {o} </SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
</FormItem>
)}
>
</FormField>
<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>
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Category'
id={id}
className={params.className}
>
<TextInputFormField control={form.control} name='name' label='Name' />
<SelectFormField control={form.control} name='layoutPosition' label='Layout Position' placeholder={form.getValues().layoutPosition == null ? undefined : form.getValues().layoutPosition}>
{schemas.insert.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
<SelectItem key={o} value={o}> {o} </SelectItem>
))}
</SelectFormField>
</FormScaffold>
)
}

View File

@@ -5,12 +5,12 @@ import { useGSAP } from '@gsap/react'
import { Suspense, useRef } from "react";
import * as Card from '~/components/ui/card'
import { useGsapContext } from "~/app/_providers/GsapProvicer";
import CollapsibleCvEntryForm from "../../entry/_components/CollapsibleForm";
import CollapsibleCvCategoryForm from "../_components/CollapsibleForm";
import { CollapsibleForm } from "~/app/_components/Form/Components";
import CreateUpdateCvEntryForm from "../../entry/_components/CreateUpdateForm";
import CreateUpdateCvCategoryForm from "../_components/CreateUpdateForm";
export default function CvPage() {
const categories = trpc.category.select.useQuery({},{refetchInterval:1000});
const entires = trpc.entry.select.useSuspenseQuery({},{refetchInterval:1000})
const categories = trpc.category.select.useQuery({}, { refetchInterval: 1000 });
const entires = trpc.entry.select.useSuspenseQuery({}, { refetchInterval: 1000 })
const gsap = useGsapContext()
const container = useRef<HTMLDivElement>(null);
useGSAP(() => {
@@ -34,31 +34,34 @@ export default function CvPage() {
</Link>
<Card.CardContent className="flex flex-row">
<div className="flex flex-col w-full">
<CreateUpdateCvCategoryForm category={cat} className="w-full" />
<br />
<span>Entries:</span>
<Suspense fallback={(<></>)}>
<div className="w-full">
{
entires[0].filter((e) => {return e.categoryId == cat.id}).length > 0 ? (
<>
{entires[0].filter((e) => {return e.categoryId == cat.id}).map((entry) => (
<CollapsibleCvEntryForm key={entry.id} entry={entry}/>
))}
</>
) : (<></>)
}
</div>
</Suspense>
<div className="flex flex-col w-full">
<CollapsibleCvEntryForm categoryId={cat.id} />
</div>
<CreateUpdateCvCategoryForm entity={cat} className="w-full" />
<Card.Card className="w-full">
<Card.CardHeader>
<Card.CardTitle>
Entries:
</Card.CardTitle>
</Card.CardHeader>
<Card.CardContent className="w-full">
<Suspense fallback={(<></>)}>
{
entires[0].filter((e) => { return e.categoryId == cat.id }).length > 0 ? (
<>
{entires[0].filter((e) => { return e.categoryId == cat.id }).map((entry) => (
<CollapsibleForm entityName="Entry" form={CreateUpdateCvEntryForm} entity={entry} entityLabelIndex="title" />
))}
</>
) : (<></>)
}
</Suspense>
<CollapsibleForm entityName="Entry" form={CreateUpdateCvEntryForm} />
</Card.CardContent>
</Card.Card>
</div>
</Card.CardContent>
</Card.Card>
)
})}
<CollapsibleCvCategoryForm />
<CollapsibleForm entityName="Category" form={CreateUpdateCvCategoryForm} />
</>
}
</div>

View File

@@ -9,7 +9,7 @@ export default function Page() {
const {data} = trpc.entry.select.useQuery({id: id})
if (data !== undefined && data.length > 0) {
return (
<CreateUpdateCvEntryForm entry={data[0]}/>
<CreateUpdateCvEntryForm entity={data[0]}/>
)
}
return (<></>)

View File

@@ -1,26 +0,0 @@
import { ChevronsUpDown, Plus } from "lucide-react"
import { Button } from "~/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
import CreateUpdateCvEntryForm from "./CreateUpdateForm";
import type { IterableElement } from "type-fest";
import type { RouterOutputs } from "~/server/routers/_app";
export default function CollapsibleCvEntryForm(params:{entry?:IterableElement<RouterOutputs['entry']['select']>, categoryId?: string}) {
return (
<Collapsible>
<CollapsibleTrigger asChild>
<Button variant={"ghost"}>
{ params.entry ?
<>{params.entry.title} <ChevronsUpDown/></> : <>New <Plus/></>
}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="autoAlpha">
{
params.entry ?
<CreateUpdateCvEntryForm className="w-full" entry={params.entry}/> :
<CreateUpdateCvEntryForm className="w-full"/>
}
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -1,119 +1,73 @@
'use client'
import { zodResolver } from '@hookform/resolvers/zod'
import { useForm, type UseFormReturn } from 'react-hook-form'
import { format } from 'date-fns'
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 { SelectItem } 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 { entitySchemas, ft, makeOnSuccess, tt } from "~/lib/utils";
import { useRelationShip } from '~/lib/hooks';
import { useTheme } from "next-themes"
import type { Entries, IterableElement } from 'type-fest';
import type { 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>()
import { FormScaffold } from '~/app/_components/Form/Components';
import { useState } from 'react';
import { SelectFormField, TextInputFormField, MdeFormField, CalenderFormField, BooleanFormField } from '~/app/_components/Form/Fields'
import { usePathname, useRouter } from 'next/navigation';
export default function CreateUpdateCvEntryForm(params: { className?: string, entity?: IterableElement<RouterOutputs['entry']['select']>, isUpdate?: boolean }) {
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : 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])
const {data: categories,id:categoryId,name:categoryName,success:categoriesSuccess} = useRelationShip(trpc.category.select.useQuery({}),'name',id)
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,
id: params.entity ? params.entity.id : crypto.randomUUID(),
title: params.entity ? params.entity.title : "",
description: params.entity ? params.entity.description : "",
categoryId: params.entity ? params.entity.categoryId : categoriesSuccess ? categories?.at(0)?.id : "",
fromTime: params.entity ? ft(params.entity).fromTime : new Date(),
toTime: params.entity ? tt(params.entity).toTime : new Date(),
hideDates: params.entity ? params.entity.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)})
let path = usePathname()
let router = useRouter()
const createMutation = trpc.entry.insert.useMutation({ onSuccess: makeOnSuccess('create', form, setId) })
const updateMutation = trpc.entry.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
const deleteMutation = trpc.entry.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
function onSubmit(values: z.infer<typeof schemas.insert>) {
isUpdate ?
id ?
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>
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Entry'
id={id}
className={params.className}
>
<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>
<TextInputFormField 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"} />
<CalenderFormField control={form.control} name='fromTime' label='From Date' />
<CalenderFormField control={form.control} name='toTime' label='To Date' />
<BooleanFormField control={form.control} name='hideDates' label='Hide Dates' />
</FormScaffold>
)
}

View File

@@ -1,27 +0,0 @@
import { ChevronsUpDown, Plus } from "lucide-react"
import { Button } from "~/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
import { type Element } from "~/lib/utils";
import CreateUpdateProjectForm from "./CreateUpdateProjectForm";
import type { RouterOutputs } from "~/server/routers/_app";
export default function CollapsibleForm(params:{project?:Element<RouterOutputs['project']['select']>}) {
return (
<Collapsible >
<CollapsibleTrigger asChild>
<Button variant={"ghost"}>
{ params.project ?
<>{params.project.title} <ChevronsUpDown/></> : <>New <Plus/></>
}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="autoAlpha">
{
params.project ?
<CreateUpdateProjectForm className="w-full" project={params.project}/> :
<CreateUpdateProjectForm className="w-full"/>
}
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -0,0 +1,15 @@
'use server'
import { expect, test } from 'vitest'
import { render, screen } from '@testing-library/react'
import CreateUpdateProjectForm from './CreateUpdateProjectForm'
import TrpcProvider from '~/../test/trpc/vitest.trpcProvider.mock'
test('CreateUpdateProjectForm', () => {
render(
<TrpcProvider>
<CreateUpdateProjectForm/>
</TrpcProvider>
)
let options = screen.getAllByRole('combobox')
console.log(options)
})

View File

@@ -2,99 +2,76 @@
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 { SelectItem } from "~/components/ui/select";
import { trpc } from "~/app/_trpc/Client";
import { Button } from "~/components/ui/button";
import * as Card from '~/components/ui/card'
import type { Entries, IterableElement } from 'type-fest'
import type { IterableElement } from 'type-fest'
import { entitySchemas, makeOnSuccess } from "~/lib/utils";
import { Suspense, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import type { RouterOutputs } from '~/server/routers/_app';
import TexttInputFormField from '~/app/_components/TextInputFormField';
import SelectFormField from '~/app/_components/SelectFormField';
import DependentFormMessaage from '~/app/_components/MutationFormMessage';
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>()
import { SelectFormField, TextInputFormField } from '~/app/_components/Form/Fields'
import { FormScaffold } from '~/app/_components/Form/Components';
import { usePathname, useRouter } from 'next/navigation';
import { makeUseRelationShipWithNameIndex } from '~/lib/hooks';
export default function CreateUpdateProjectForm(params: { className?: string, entity?: IterableElement<RouterOutputs['project']['select']> }) {
const [id, setId] = useState<string|undefined>(params.entity ? params.entity.id : 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])
console.log('using trpc')
const { data:stacks,id:stackId, name:stackName,success:stacksSuccess, error:stackError } = makeUseRelationShipWithNameIndex('stackItems')(trpc.techStack.select.useQuery({}),id,(items) => {return items ? items.join('-') : ""})
console.log(stackError)
const form = useForm<z.infer<typeof schemas.insert>>({
resolver: zodResolver(schemas.insert),
defaultValues: {
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 : ""
id: id ? id : crypto.randomUUID(),
stackId: params.entity ? params.entity.stackId : stacksSuccess ? stacks?.at(0)?.id : "",
releaseStatus: params.entity ? params.entity.releaseStatus : "unreleased",
releaseLink: params.entity ? params.entity.releaseLink : "",
sourceType: params.entity ? params.entity.sourceType : "open",
sourceLink: params.entity ? params.entity.sourceLink : ""
}
})
const createMutation = trpc.project.insert.useMutation({
onSuccess: makeOnSuccess('create',form,setIsUpdate)
})
const updateMutation = trpc.project.update.useMutation({
onSuccess: makeOnSuccess('update',form)
})
let path = usePathname()
let router = useRouter()
const createMutation = trpc.project.insert.useMutation({ onSuccess: makeOnSuccess('create', form, setId) })
const updateMutation = trpc.project.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
const deleteMutation = trpc.project.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
function onSubmit(values: z.infer<typeof schemas.insert>) {
params.project ?
id ?
updateMutation.mutate(values) :
createMutation.mutate(values)
}
return (
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
<Card.CardHeader>
<Card.CardTitle>
<DependentText bool={isUpdate} true='Update Project' false='Create Project'/>
</Card.CardTitle>
</Card.CardHeader>
<Card.CardContent>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8"
>
<SelectFormField control={form.control} name='stackId' label='Stack' defaultValue={stackId} placeholder={stackName} >
{
stacksSuccess ?
<>
{stacks.map((stack) => {
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
})}
</> :
<SelectItem key='placeholder' value="placeholder" />
}
</SelectFormField>
<TexttInputFormField control={form.control} name='title' label='Title' />
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
<SelectItem value="open"> open </SelectItem>
<SelectItem value="closed"> closed </SelectItem>
</SelectFormField>
<TexttInputFormField control={form.control} label='Source Link' name='sourceLink' />
<SelectFormField control={form.control} name='releaseStatus' label='Release Status' defaultValue={'unreleased'} placeholder='unreleased' >
<SelectItem value="released"> released </SelectItem>
<SelectItem value="unreleased"> unreleased </SelectItem>
</SelectFormField>
<TexttInputFormField control={form.control} label='Release Link' name='releaseLink' />
<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>
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Project'
id={id}
className={params.className}
>
<SelectFormField control={form.control} name='stackId' label='Stack' defaultValue={stackId} placeholder={stackName} >
{
stacksSuccess ?
<>
{stacks?.map((stack) => {
return (<SelectItem key={stack.id} value={stack.id}> {stack.stackItems ? stack.stackItems.join("-") : "Empty Stack"} </SelectItem>)
})}
</> :
<SelectItem key='placeholder' value="placeholder" />
}
</SelectFormField>
<TextInputFormField control={form.control} name='title' label='Title' />
<SelectFormField control={form.control} name='sourceType' label='Source Type' defaultValue={'open'} placeholder='open' >
<SelectItem value="open"> open </SelectItem>
<SelectItem value="closed"> closed </SelectItem>
</SelectFormField>
<TextInputFormField control={form.control} label='Source Link' name='sourceLink' />
<SelectFormField control={form.control} name='releaseStatus' label='Release Status' defaultValue={'unreleased'} placeholder='unreleased' >
<SelectItem value="released"> released </SelectItem>
<SelectItem value="unreleased"> unreleased </SelectItem>
</SelectFormField>
<TextInputFormField control={form.control} label='Release Link' name='releaseLink' />
</FormScaffold>
)
}

View File

@@ -0,0 +1,60 @@
'use client'
import * as Card from '~/components/ui/card'
import { trpc } from "~/app/_trpc/Client"
import CreateUpdateProjectForm from '../_components/CreateUpdateProjectForm'
import CreateUpdateStackForm from '../techStack/_components/CreateUpdateForm'
import { CollapsibleForm } from '~/app/_components/Form/Components'
import { Suspense } from 'react'
export default function ProjectList() {
const projects = trpc.project.select.useQuery({}, { refetchInterval: 1000 })
const techStacks = trpc.techStack.select.useSuspenseQuery({}, { refetchInterval: 1000 })
return (
<div className="w-5/6 lg:w-1/2 flex flex-col gap-3">
{
projects.data == undefined ?
<></> :
<>
{projects.data.map((project) => {
return (
<Card.Card className="gsapan" key={project.id}>
<Card.CardHeader>
<Card.CardTitle>
Project: {project.title}
</Card.CardTitle>
</Card.CardHeader>
<Card.CardContent className="flex flex-row">
<div className="flex flex-col w-full">
<CreateUpdateProjectForm entity={project} className="w-full" />
<Card.Card className="w-full">
<Card.CardHeader>
<Card.CardTitle>
TechStack:
</Card.CardTitle>
</Card.CardHeader>
<Card.CardContent className="w-full">
<Suspense fallback={(<></>)}>
{
techStacks[0].filter((e) => { return e.id == project.stackId }).length > 0 ? (
<>
{techStacks[0].filter((e) => { return e.id == project.stackId }).map((stack) => (
<CollapsibleForm entityName="Stack" form={CreateUpdateStackForm} entity={stack} entityLabelIndex="stackItems"/>
))}
</>
) : (<></>)
}
</Suspense>
<CollapsibleForm entityName="Stack" form={CreateUpdateStackForm} />
</Card.CardContent>
</Card.Card>
</div>
</Card.CardContent>
</Card.Card>
)
})}
<CollapsibleForm entityName="Project" form={CreateUpdateProjectForm} />
</>
}
</div>
)
}

View File

@@ -1,27 +0,0 @@
import { ChevronsUpDown, Plus } from "lucide-react"
import { Button } from "~/components/ui/button";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
import { type Element } from "~/lib/utils";
import CreateUpdateProjectForm from "./CreateForm";
import type { ProjectRouterOutputs } from "~/server/routers/project";
export default function CollapsibleForm(params:{project:Element<ProjectRouterOutputs['list']>|undefined}) {
return (
<Collapsible >
<CollapsibleTrigger asChild>
<Button variant={"ghost"}>
{ params.project ?
<>{params.project.title} <ChevronsUpDown/></> : <>New <Plus/></>
}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="autoAlpha">
{
params.project ?
<CreateUpdateProjectForm className="w-full" project={params.project}/> :
<CreateUpdateProjectForm className="w-full" project={params.project}/>
}
</CollapsibleContent>
</Collapsible>
)
}

View File

@@ -2,104 +2,52 @@
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 { Form } from "~/components/ui/form";
import { trpc } from "~/app/_trpc/Client";
import { Button } from "~/components/ui/button";
import * as Card from '~/components/ui/card'
import type { Entries, IterableElement } from 'type-fest'
import type { IterableElement } from 'type-fest'
import { stackItemEnum } from "~/server/db/schema";
import { ToggleGroup, ToggleGroupItem } from "~/components/ui/toggle-group";
import type { RouterOutputs } from '~/server/routers/_app';
import { entitySchemas } from '~/lib/utils';
import { entitySchemas, makeOnSuccess } from '~/lib/utils';
import { useState } from 'react';
import { MultiBooleanFormField } from "~/app/_components/Form/Fields"
import { DependentFormMessaage, FormScaffold } from '~/app/_components/Form/Components';
import DependentText from '~/app/_components/DependentText';
import { usePathname, useRouter } from 'next/navigation';
export default function CreateUpdateStackForm(params:{className?:string, techStack?:IterableElement<RouterOutputs['techStack']['select']>,isUpdate?: boolean }) {
export default function CreateUpdateStackForm(params: { className?: string, entity?: IterableElement<RouterOutputs['techStack']['select']> }) {
const schemas = entitySchemas('techStack')
const form = useForm<z.infer<typeof schemas.insert>>({
resolver: zodResolver(schemas.insert),
defaultValues: {
id: params.techStack ? params.techStack.id : crypto.randomUUID(),
stackItems: params.techStack ? params.techStack.stackItems : [],
}
})
const [isUpdate, setIsUpdate] = useState<boolean>(params.isUpdate ? params.isUpdate : (params.techStack ? true : false))
const createMutation = trpc.techStack.insert.useMutation({
onSuccess: (data) => {
form.setValue("id", data[0] ? data[0].id : "");
setIsUpdate(true)
}
})
const updateMutation = trpc.techStack.update.useMutation({
onSuccess: (data) => {
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])
})
}
id: params.entity ? params.entity.id : crypto.randomUUID(),
stackItems: params.entity ? params.entity.stackItems : [],
}
})
const [id, setId] = useState<string | undefined>(params.entity ? params.entity.id : undefined)
let path = usePathname()
let router = useRouter()
const createMutation = trpc.techStack.insert.useMutation({ onSuccess: makeOnSuccess('create', form, setId) })
const updateMutation = trpc.techStack.update.useMutation({ onSuccess: makeOnSuccess('update', form) })
const deleteMutation = trpc.techStack.delete.useMutation({ onSuccess: makeOnSuccess('delete', form, undefined, path, router) })
function onSubmit(values: z.infer<typeof schemas.insert>) {
params.techStack ?
params.entity ?
updateMutation.mutate(values) :
createMutation.mutate(values)
}
return (
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
<Card.CardHeader>
<Card.CardTitle>
{params.techStack ? "Update" : "Create"} Tech Stack
</Card.CardTitle>
</Card.CardHeader>
<Card.CardContent>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-8"
>
<FormField
control={form.control}
name="stackItems"
render={({ field }) => (
<FormItem>
<FormLabel>
Stack Items
</FormLabel>
<FormControl>
<ToggleGroup
variant="outline"
type="multiple"
onValueChange={field.onChange}
className="flex flex-wrap"
>
{stackItemEnum.enumValues.map((v) => {
return (
<ToggleGroupItem className="w-fit" key={v} value={v}>
{v}
</ToggleGroupItem>
)
})}
</ToggleGroup>
</FormControl>
</FormItem>
)}
/>
<Button type="submit"> {params.techStack ? "Update" : "Create"} </Button>
<FormMessage className={updateMutation.status == "success" || createMutation.status == "success" ? "text-green-500" : "text-red-500"}>
{ params.techStack ?
(
<>{updateMutation.error ? updateMutation.error.message : updateMutation.status}</>
) :
(
<>{createMutation.error ? createMutation.error.message : createMutation.status}</>
)
}
</FormMessage>
</form>
</Form>
</Card.CardContent>
</Card.Card>
<FormScaffold
form={form}
createMutation={createMutation}
updateMutation={updateMutation}
deleteMutation={deleteMutation}
onSubmit={onSubmit}
title='Entry'
id={id}
className={params.className}
>
<MultiBooleanFormField control={form.control} name='stackItems' label='Stack Items' options={stackItemEnum.enumValues} defaultValues={params.entity?.stackItems} />
</FormScaffold>
)
}