nice category list, markdown
This commit is contained in:
@@ -6,7 +6,7 @@
|
|||||||
"tailwind": {
|
"tailwind": {
|
||||||
"config": "",
|
"config": "",
|
||||||
"css": "src/styles/globals.css",
|
"css": "src/styles/globals.css",
|
||||||
"baseColor": "slate",
|
"baseColor": "zinc",
|
||||||
"cssVariables": true,
|
"cssVariables": true,
|
||||||
"prefix": ""
|
"prefix": ""
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
"gsap": "^3.13.0",
|
"gsap": "^3.13.0",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.503.0",
|
||||||
|
"marked-react": "^3.0.0",
|
||||||
"next": "15.4.0-canary.17",
|
"next": "15.4.0-canary.17",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"postgres": "^3.4.7",
|
"postgres": "^3.4.7",
|
||||||
|
|||||||
20
pnpm-lock.yaml
generated
20
pnpm-lock.yaml
generated
@@ -161,6 +161,9 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.503.0
|
specifier: ^0.503.0
|
||||||
version: 0.503.0(react@19.1.0)
|
version: 0.503.0(react@19.1.0)
|
||||||
|
marked-react:
|
||||||
|
specifier: ^3.0.0
|
||||||
|
version: 3.0.0(react@19.1.0)
|
||||||
next:
|
next:
|
||||||
specifier: 15.4.0-canary.17
|
specifier: 15.4.0-canary.17
|
||||||
version: 15.4.0-canary.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
version: 15.4.0-canary.17(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||||
@@ -2100,6 +2103,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
|
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
marked-react@3.0.0:
|
||||||
|
resolution: {integrity: sha512-We5IPtdfarVIqypy4LmQ3O8xe9Hk8EBNs4MRUYg5077mbD7GJ+rmV9XAPJ0XK8LfQkCOwGv4I7niXWLXWywyoQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || >=17.0.0
|
||||||
|
|
||||||
|
marked@15.0.12:
|
||||||
|
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
|
||||||
|
engines: {node: '>= 18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
minipass@7.1.2:
|
minipass@7.1.2:
|
||||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||||
engines: {node: '>=16 || 14 >=14.17'}
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
@@ -4026,6 +4039,13 @@ snapshots:
|
|||||||
|
|
||||||
map-obj@4.3.0: {}
|
map-obj@4.3.0: {}
|
||||||
|
|
||||||
|
marked-react@3.0.0(react@19.1.0):
|
||||||
|
dependencies:
|
||||||
|
marked: 15.0.12
|
||||||
|
react: 19.1.0
|
||||||
|
|
||||||
|
marked@15.0.12: {}
|
||||||
|
|
||||||
minipass@7.1.2: {}
|
minipass@7.1.2: {}
|
||||||
|
|
||||||
minizlib@3.0.2:
|
minizlib@3.0.2:
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import UpdateCvCategoryForm from "./UpdateForm";
|
import UpdateCvCategoryForm from "../_components/UpdateForm";
|
||||||
|
|
||||||
|
|
||||||
export default async function Page({params}:{params: Promise<{id:string}>}) {
|
export default async function Page({params}:{params: Promise<{id:string}>}) {
|
||||||
console.log(params)
|
console.log(params)
|
||||||
const {id} = await params;
|
const {id} = await params;
|
||||||
return (
|
return (
|
||||||
<UpdateCvCategoryForm params={{id:id}}/>
|
<UpdateCvCategoryForm id={id} className={undefined}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/app/admin/cv/category/_components/CollapsibleForm.tsx
Normal file
29
src/app/admin/cv/category/_components/CollapsibleForm.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { ChevronsUpDown, Plus } from "lucide-react"
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
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 UpdateCvCategoryForm from "./UpdateForm";
|
||||||
|
import CreateCvCategoryForm from "./CreateForm";
|
||||||
|
export default function CollapsibleCvCategoryForm(params:{category:Element<CategoryRouterOutputs['list']>|undefined}) {
|
||||||
|
return (
|
||||||
|
<Collapsible >
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button variant={"ghost"}>
|
||||||
|
{ params.category ?
|
||||||
|
<>{params.category.name} <ChevronsUpDown/></> : <>New <Plus/></>
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent className="autoAlpha">
|
||||||
|
{
|
||||||
|
params.category ?
|
||||||
|
<UpdateCvCategoryForm className="w-full" id={params.category.id} key={params.category.id} /> :
|
||||||
|
<CreateCvCategoryForm className="w-full"/>
|
||||||
|
|
||||||
|
}
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
)
|
||||||
|
}
|
||||||
87
src/app/admin/cv/category/_components/CreateForm.tsx
Normal file
87
src/app/admin/cv/category/_components/CreateForm.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
'use client'
|
||||||
|
import { insertSchema } 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'
|
||||||
|
|
||||||
|
export default function CreateCvCategoryForm(params:{className:string|undefined}) {
|
||||||
|
const form = useForm<z.infer<typeof insertSchema>>({
|
||||||
|
resolver: zodResolver(insertSchema),
|
||||||
|
defaultValues: {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
layoutPosition: "col1"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const mutation = trpc.cv.category.create.useMutation()
|
||||||
|
function onSubmit(values: z.infer<typeof insertSchema>) {
|
||||||
|
mutation.mutate(values)
|
||||||
|
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 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 == null ? undefined : 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>
|
||||||
|
{insertSchema.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
|
||||||
|
<SelectItem key={o} value={o}> {o} </SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
</FormField>
|
||||||
|
<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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { insertSchema, updateRouteSchema, updateSchema } from "~/lib/schema/cv/category"
|
import { updateRouteSchema } 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";
|
||||||
@@ -9,11 +9,13 @@ 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 { useRouter } from "next/navigation";
|
import { useRouter, usePathname } from "next/navigation";
|
||||||
import { use } from "react";
|
import { Delete } from "lucide-react";
|
||||||
|
import { cn } from "~/lib/utils";
|
||||||
export default function UpdateCvCategoryForm({params}:{params:{id:string}}) {
|
export default function UpdateCvCategoryForm(params: { id: string, className: string | undefined }) {
|
||||||
const id = params.id
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
const id = params.id;
|
||||||
console.log(id)
|
console.log(id)
|
||||||
const category = trpc.cv.category.get.useQuery({ id: id })
|
const category = trpc.cv.category.get.useQuery({ id: id })
|
||||||
const form = useForm<z.infer<typeof updateRouteSchema>>({
|
const form = useForm<z.infer<typeof updateRouteSchema>>({
|
||||||
@@ -30,11 +32,26 @@ export default function UpdateCvCategoryForm({params}:{params:{id:string}}) {
|
|||||||
form.setValue("update.layoutPosition", data?.layoutPosition)
|
form.setValue("update.layoutPosition", data?.layoutPosition)
|
||||||
form.setValue("update.name", data?.name)
|
form.setValue("update.name", data?.name)
|
||||||
})
|
})
|
||||||
const mutation = trpc.cv.category.update.useMutation({onSuccess: () => {
|
const updateMutation = trpc.cv.category.update.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
category.refetch()
|
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>) {
|
function onSubmit(values: z.infer<typeof updateRouteSchema>) {
|
||||||
mutation.mutate({
|
updateMutation.mutate({
|
||||||
by: { id: id },
|
by: { id: id },
|
||||||
update: {
|
update: {
|
||||||
layoutPosition: values.update.layoutPosition,
|
layoutPosition: values.update.layoutPosition,
|
||||||
@@ -44,7 +61,7 @@ export default function UpdateCvCategoryForm({params}:{params:{id:string}}) {
|
|||||||
}
|
}
|
||||||
if (category.data !== undefined) {
|
if (category.data !== undefined) {
|
||||||
return (
|
return (
|
||||||
<Card.Card 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>
|
||||||
Update Category
|
Update Category
|
||||||
@@ -108,8 +125,13 @@ export default function UpdateCvCategoryForm({params}:{params:{id:string}}) {
|
|||||||
>
|
>
|
||||||
</FormField>
|
</FormField>
|
||||||
<Button type="submit"> Update </Button>
|
<Button type="submit"> Update </Button>
|
||||||
<FormMessage className={mutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
<FormMessage className={cn(updateMutation.status == "success" ? "text-green-500" : "text-red-500", "flex flex-row justify-between")}>
|
||||||
{mutation.error ? mutation.error.message : mutation.status}
|
{updateMutation.error ? updateMutation.error.message : updateMutation.status}
|
||||||
|
<Button
|
||||||
|
className="ml-auto cursor-pointer" variant="destructive" onClick={() => { deleteCategory(id) }}>
|
||||||
|
Delete
|
||||||
|
<Delete />
|
||||||
|
</Button>
|
||||||
</FormMessage>
|
</FormMessage>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -1,87 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { insertSchema } from "~/lib/schema/cv/category"
|
import CreateCvCategoryForm from "../_components/CreateForm";
|
||||||
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'
|
|
||||||
|
|
||||||
export default function CreateCvCategoryForm() {
|
export default function Page() {
|
||||||
const form = useForm<z.infer<typeof insertSchema>>({
|
|
||||||
resolver: zodResolver(insertSchema),
|
|
||||||
defaultValues: {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
layoutPosition: "col1"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const mutation = trpc.cv.category.create.useMutation()
|
|
||||||
function onSubmit(values: z.infer<typeof insertSchema>) {
|
|
||||||
mutation.mutate(values)
|
|
||||||
form.setValue("id",crypto.randomUUID())
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Card.Card className="w-5/6 lg:w-1/2">
|
<CreateCvCategoryForm/>
|
||||||
<Card.CardHeader>
|
|
||||||
<Card.CardTitle>
|
|
||||||
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 == null ? undefined : 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>
|
|
||||||
{insertSchema.shape.layoutPosition.unwrap().unwrap().options.map((o) => (
|
|
||||||
<SelectItem key={o} value={o}> {o} </SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
</FormField>
|
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,29 +2,18 @@
|
|||||||
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 { useEffect, useRef } from "react";
|
import { 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 { Button } from "~/components/ui/button";
|
import CollapsibleCvEntryForm from "../../entry/_components/CollapsibleForm";
|
||||||
import { ChevronsUpDown, Delete } from 'lucide-react'
|
import UpdateCvCategoryForm from "../_components/UpdateForm";
|
||||||
import UpdateCvEntryForm from "../../entry/[id]/UpdateForm";
|
import CollapsibleCvCategoryForm from "../_components/CollapsibleForm";
|
||||||
import { CollapsibleContent, CollapsibleTrigger, Collapsible } from "~/components/ui/collapsible";
|
|
||||||
export default function CvPage() {
|
export default function CvPage() {
|
||||||
const cvCategories = trpc.cv.category.list.useQuery();
|
const cvCategories = trpc.cv.category.list.useQuery(undefined,{refetchInterval:1000});
|
||||||
const gsap = useGsapContext()
|
const gsap = useGsapContext()
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
const mut = trpc.cv.category.delete.useMutation({
|
|
||||||
onSuccess: () => {
|
|
||||||
cvCategories.refetch()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
useEffect(() => { console.log(cvCategories.data) }, [cvCategories.data])
|
|
||||||
const deleteCategory = (id: string) => {
|
|
||||||
console.log(`deleting ${id}`)
|
|
||||||
mut.mutate(id)
|
|
||||||
}
|
|
||||||
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: [cvCategories.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">
|
||||||
@@ -44,8 +33,7 @@ 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">
|
||||||
<span>Category id : {cat.id}</span>
|
<UpdateCvCategoryForm id={cat.id} className="w-full" />
|
||||||
<span>Category Position : {cat.layoutPosition}</span>
|
|
||||||
<br />
|
<br />
|
||||||
<span>Entries:</span>
|
<span>Entries:</span>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -53,30 +41,21 @@ export default function CvPage() {
|
|||||||
cat.cvEntry.length > 0 ? (
|
cat.cvEntry.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
{cat.cvEntry.map((entry) => (
|
{cat.cvEntry.map((entry) => (
|
||||||
<Collapsible>
|
<CollapsibleCvEntryForm key={entry.id} entry={entry} />
|
||||||
<CollapsibleTrigger>
|
|
||||||
<Button variant={"ghost"}>
|
|
||||||
{entry.title} <ChevronsUpDown/>
|
|
||||||
</Button>
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<UpdateCvEntryForm params={{ id: entry.id, className: "w-full" }} key={entry.id} />
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (<></>)
|
) : (<></>)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col w-full">
|
||||||
|
<CollapsibleCvEntryForm entry={undefined} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
className="ml-auto cursor-pointer" variant="destructive" onClick={() => { deleteCategory(cat.id) }}>
|
|
||||||
<Delete />
|
|
||||||
</Button>
|
|
||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
</Card.Card>
|
</Card.Card>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
<CollapsibleCvCategoryForm category={undefined} />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
'use server'
|
'use server'
|
||||||
|
|
||||||
import UpdateCvEntryForm from "./UpdateForm";
|
import UpdateCvEntryForm from "../_components/UpdateForm";
|
||||||
|
|
||||||
|
|
||||||
export default async function Page({params}:{params: Promise<{id:string}>}) {
|
export default async function Page({params}:{params: Promise<{id:string}>}) {
|
||||||
console.log(params)
|
console.log(params)
|
||||||
const {id} = await params;
|
const {id} = await params;
|
||||||
return (
|
return (
|
||||||
<UpdateCvEntryForm params={{id:id}}/>
|
<UpdateCvEntryForm params={{id:id, className: undefined}}/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/app/admin/cv/entry/_components/CollapsibleForm.tsx
Normal file
29
src/app/admin/cv/entry/_components/CollapsibleForm.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { ChevronsUpDown, Plus } from "lucide-react"
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
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 UpdateCvEntryForm from "./UpdateForm";
|
||||||
|
import CreateCvEntryForm from "./CreateForm";
|
||||||
|
export default function CollapsibleCvEntryForm(params:{entry:Element<Element<CategoryRouterOutputs['list']>['cvEntry']>|EntryRouterOutputs['get']|Element<EntryRouterOutputs['list']>|undefined}) {
|
||||||
|
return (
|
||||||
|
<Collapsible>
|
||||||
|
<CollapsibleTrigger asChild>
|
||||||
|
<Button variant={"ghost"}>
|
||||||
|
{ params.entry ?
|
||||||
|
<>{params.entry.title} <ChevronsUpDown/></> : <>New <Plus/></>
|
||||||
|
}
|
||||||
|
</Button>
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent className="autoAlpha">
|
||||||
|
{
|
||||||
|
params.entry ?
|
||||||
|
<UpdateCvEntryForm params={{ id: params.entry.id, className: "w-full" }} key={params.entry.id} /> :
|
||||||
|
<CreateCvEntryForm className="w-full"/>
|
||||||
|
|
||||||
|
}
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,62 +1,49 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { updateRouteSchema, updateRouteSchemaCliennt } from "~/lib/schema/cv/entry"
|
import { insertSchema, insertSchemaForm } from "~/lib/schema/cv/entry"
|
||||||
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 { format } from 'date-fns'
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } 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 { format } from 'date-fns'
|
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "~/components/ui/popover";
|
||||||
import { cn } from "~/lib/utils";
|
|
||||||
import { CalendarIcon } from "lucide-react";
|
import { CalendarIcon } from "lucide-react";
|
||||||
import { Calendar } from "~/components/ui/calendar";
|
import { Calendar } from "~/components/ui/calendar";
|
||||||
import { Textarea } from "~/components/ui/textarea";
|
import { cn } from "~/lib/utils";
|
||||||
export default function UpdateCvEntryForm({ params }: { params: { id: string, className: string|undefined } }) {
|
import { useState } from "react"
|
||||||
console.log(params)
|
|
||||||
const id = params.id
|
export default function CreateCvEntryForm(params:{className:string|undefined}) {
|
||||||
const categories = trpc.cv.category.list.useQuery()
|
const categories = trpc.cv.category.list.useQuery()
|
||||||
const entry = trpc.cv.entry.get.useQuery({ id: id })
|
const form = useForm<z.infer<typeof insertSchemaForm>>({
|
||||||
const form = useForm<z.infer<typeof updateRouteSchemaCliennt>>({
|
resolver: zodResolver(insertSchemaForm),
|
||||||
resolver: zodResolver(updateRouteSchemaCliennt),
|
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
by: { id: id },
|
id: crypto.randomUUID(),
|
||||||
update: {
|
title: "",
|
||||||
categoryId: entry.data?.categoryId,
|
description: "",
|
||||||
title: entry.data?.title,
|
categoryId: ""
|
||||||
description: entry.data?.description,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
entry.promise.then((v) => {
|
const mutation = trpc.cv.entry.create.useMutation({
|
||||||
form.setValue('update.title',v?.title)
|
onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") }
|
||||||
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)
|
|
||||||
})
|
})
|
||||||
const mutation = trpc.cv.entry.update.useMutation({
|
function onSubmit(values: z.infer<typeof insertSchemaForm>) {
|
||||||
onSuccess: () => {
|
let { id, title, categoryId, description } = values
|
||||||
entry.refetch()
|
let v: z.infer<typeof insertSchema> = {
|
||||||
}
|
id: id,
|
||||||
})
|
|
||||||
function onSubmit(values: z.infer<typeof updateRouteSchemaCliennt>) {
|
|
||||||
let { title, categoryId, description } = values.update;
|
|
||||||
mutation.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,
|
categoryId: categoryId,
|
||||||
description: description
|
description: description,
|
||||||
|
title: title,
|
||||||
|
fromTime: format(values.fromTime,'yyyy-MM-dd'),
|
||||||
|
toTime: format(values.toTime,'yyyy-MM-dd'),
|
||||||
}
|
}
|
||||||
})
|
mutation.mutate(v)
|
||||||
|
form.setValue("id", crypto.randomUUID())
|
||||||
}
|
}
|
||||||
if (entry.data !== undefined) {
|
|
||||||
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>
|
||||||
@@ -72,7 +59,7 @@ export default function UpdateCvEntryForm({ params }: { params: { id: string, cl
|
|||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="update.categoryId"
|
name="categoryId"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
@@ -99,7 +86,7 @@ export default function UpdateCvEntryForm({ params }: { params: { id: string, cl
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="update.title"
|
name="title"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
@@ -113,21 +100,21 @@ export default function UpdateCvEntryForm({ params }: { params: { id: string, cl
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="update.description"
|
name="description"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
Description
|
Description
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Textarea placeholder="description" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
<Input placeholder="description" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="update.fromTime"
|
name="fromTime"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-col">
|
<FormItem className="flex flex-col">
|
||||||
<FormLabel>From Date</FormLabel>
|
<FormLabel>From Date</FormLabel>
|
||||||
@@ -168,7 +155,7 @@ export default function UpdateCvEntryForm({ params }: { params: { id: string, cl
|
|||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="update.toTime"
|
name="toTime"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex flex-col">
|
<FormItem className="flex flex-col">
|
||||||
<FormLabel>To Date</FormLabel>
|
<FormLabel>To Date</FormLabel>
|
||||||
@@ -207,7 +194,7 @@ export default function UpdateCvEntryForm({ params }: { params: { id: string, cl
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<Button type="submit"> Update </Button>
|
<Button type="submit"> Create </Button>
|
||||||
<FormMessage className={mutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
<FormMessage className={mutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
||||||
{mutation.error ? mutation.error.message : mutation.status}
|
{mutation.error ? mutation.error.message : mutation.status}
|
||||||
</FormMessage>
|
</FormMessage>
|
||||||
@@ -216,10 +203,4 @@ export default function UpdateCvEntryForm({ params }: { params: { id: string, cl
|
|||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
</Card.Card>
|
</Card.Card>
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
245
src/app/admin/cv/entry/_components/UpdateForm.tsx
Normal file
245
src/app/admin/cv/entry/_components/UpdateForm.tsx
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
'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 } 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";
|
||||||
|
export default function UpdateCvEntryForm({ params }: { params: { id: string, className: string | undefined } }) {
|
||||||
|
console.log(params)
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
entry.promise.then((v) => {
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
const updateMutation = trpc.cv.entry.update.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
if (pathname.includes('list')) {
|
||||||
|
router.refresh()
|
||||||
|
} else {
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const deleteMutation = trpc.cv.entry.delete.useMutation({
|
||||||
|
onSuccess: () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const deleteEntry = (id: string) => {
|
||||||
|
deleteMutation.mutate(id)
|
||||||
|
}
|
||||||
|
function onSubmit(values: z.infer<typeof updateRouteSchemaCliennt>) {
|
||||||
|
let { title, categoryId, description } = 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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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>
|
||||||
|
<Textarea placeholder="description" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
||||||
|
</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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<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,206 +1,8 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { insertSchema, insertSchemaForm } from "~/lib/schema/cv/entry"
|
import CreateCvEntryForm from "../_components/CreateForm"
|
||||||
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 { useState } from "react"
|
|
||||||
|
|
||||||
export default function CreateCvEntryForm() {
|
export default function Page() {
|
||||||
const categories = trpc.cv.category.list.useQuery()
|
|
||||||
const form = useForm<z.infer<typeof insertSchemaForm>>({
|
|
||||||
resolver: zodResolver(insertSchemaForm),
|
|
||||||
defaultValues: {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
categoryId: ""
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
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 } = 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'),
|
|
||||||
}
|
|
||||||
mutation.mutate(v)
|
|
||||||
form.setValue("id", crypto.randomUUID())
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Card.Card className="w-5/6 lg:w-1/2">
|
<CreateCvEntryForm/>
|
||||||
<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={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 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>
|
|
||||||
<Input placeholder="description" onChange={field.onChange} value={field.value == null ? undefined : field.value} />
|
|
||||||
</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>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,20 +5,11 @@ import { useGSAP } from '@gsap/react'
|
|||||||
import { useRef } from "react";
|
import { 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 { Button } from "~/components/ui/button";
|
|
||||||
import { Delete } from 'lucide-react'
|
|
||||||
export default function CvPage() {
|
export default function CvPage() {
|
||||||
const cvEntries = trpc.cv.entry.list.useQuery();
|
const cvEntries = trpc.cv.entry.list.useQuery();
|
||||||
const gsap = useGsapContext()
|
const gsap = useGsapContext()
|
||||||
const container = useRef<HTMLDivElement>(null);
|
const container = useRef<HTMLDivElement>(null);
|
||||||
const mut = trpc.cv.entry.delete.useMutation({onSuccess: () => {
|
|
||||||
console.log('success')
|
|
||||||
cvEntries.refetch()
|
|
||||||
}})
|
|
||||||
const deleteEntry = (id: string) => {
|
|
||||||
console.log(`deleting ${id}`)
|
|
||||||
mut.mutate(id)
|
|
||||||
}
|
|
||||||
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: [cvEntries.status], revertOnUpdate: true });
|
||||||
@@ -44,10 +35,6 @@ export default function CvPage() {
|
|||||||
Category Name :
|
Category Name :
|
||||||
<Link href={`/admin/cv/entry/${ent.categoryId}`}> {ent.category?.name} </Link>
|
<Link href={`/admin/cv/entry/${ent.categoryId}`}> {ent.category?.name} </Link>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
|
||||||
className="ml-auto cursor-pointer" variant="destructive" onClick={() => { deleteEntry(ent.id) }}>
|
|
||||||
<Delete />
|
|
||||||
</Button>
|
|
||||||
</Card.CardContent>
|
</Card.CardContent>
|
||||||
</Card.Card>
|
</Card.Card>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,9 @@ export default function CvCategory(props:CvCategoryProps) {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
{(query.data?.cvEntry.length ? query.data?.cvEntry.length : 0 ) > 0 ?
|
{(query.data?.cvEntry.length ? query.data?.cvEntry.length : 0 ) > 0 ?
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
{query.data?.cvEntry.map((entry) => (
|
||||||
|
<CvEntry key={entry.id} initialData={entry}/>
|
||||||
|
))}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
:
|
:
|
||||||
<></>
|
<></>
|
||||||
|
|||||||
@@ -1,9 +1,55 @@
|
|||||||
import { trpc } from "~/app/_trpc/Client"
|
import { trpc } from "~/app/_trpc/Client"
|
||||||
|
import { Card, CardAction, CardContent, CardFooter, CardHeader, CardTitle } from "~/components/ui/card"
|
||||||
|
import { Skeleton } from "~/components/ui/skeleton"
|
||||||
|
import type { Element } from "~/lib/utils"
|
||||||
import type { cvEntry } from "~/server/db/schema"
|
import type { cvEntry } from "~/server/db/schema"
|
||||||
|
import type { CategoryRouterOutputs } from "~/server/routers/cv/category"
|
||||||
|
import type { EntryRouterOutputs } from "~/server/routers/cv/entry"
|
||||||
|
import Markdown from 'marked-react'
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
export default function CvEntry(params: {
|
||||||
|
initialData: EntryRouterOutputs['get'] | Element<Element<CategoryRouterOutputs['list']>['cvEntry']>
|
||||||
|
}) {
|
||||||
|
const query = trpc.cv.entry.get.useQuery({ id: params.initialData?.id ? params.initialData.id : "" })
|
||||||
|
const { data } = query
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{
|
||||||
|
data ?
|
||||||
|
<>
|
||||||
|
<Card className="w-fit">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle> {data.title} </CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div>
|
||||||
|
<Markdown>{data.description ? data.description : undefined}</Markdown>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="text-sm">
|
||||||
|
{`${data.fromTime}-${data.toTime}`}
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</> :
|
||||||
|
<>
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<div className="flex flex-row">
|
||||||
|
<CardTitle> <Skeleton className="h-2rem w-5rem" /> </CardTitle>
|
||||||
|
<span className="ml-auto text-sm"> <Skeleton className="h-1rem w-3rem" /> - <Skeleton className="h-1rem w-3rem" /> </span>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div>
|
||||||
|
<Skeleton className="h-4 w-[250px]" />
|
||||||
|
<Skeleton className="h-4 w-[200px]" />
|
||||||
|
<Skeleton className="h-4 w-[200px]" />
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
export type CvEntryProps = typeof cvEntry
|
</>
|
||||||
|
}
|
||||||
export default function CvEntry(cvEntry: CvEntryProps) {
|
</>
|
||||||
const query = trpc.cv.entry.list.useQuery()
|
)
|
||||||
return (<></>)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,8 @@
|
|||||||
import { useGSAP } from "@gsap/react";
|
import { useGSAP } from "@gsap/react";
|
||||||
import { useGsapContext } from "../_providers/GsapProvicer";
|
import { useGsapContext } from "../_providers/GsapProvicer";
|
||||||
import { trpc } from "../_trpc/Client";
|
import { trpc } from "../_trpc/Client";
|
||||||
import { useRef, useState } from "react";
|
import { useRef } from "react";
|
||||||
import type { Element } from "~/lib/utils";
|
import { SidebarContent, SidebarProvider, Sidebar } from "~/components/ui/sidebar";
|
||||||
import { Card } from "~/components/ui/card";
|
|
||||||
import { SidebarContent, SidebarProvider, Sidebar, SidebarTrigger } from "~/components/ui/sidebar";
|
|
||||||
import SidebarTriggerDisappearsOnMobile from "./_components/SidebarTriggerDisappearsOnMobile";
|
import SidebarTriggerDisappearsOnMobile from "./_components/SidebarTriggerDisappearsOnMobile";
|
||||||
import CvCategory from "./_components/CvCategory";
|
import CvCategory from "./_components/CvCategory";
|
||||||
export default function CvPage() {
|
export default function CvPage() {
|
||||||
@@ -30,8 +28,6 @@ export default function CvPage() {
|
|||||||
return { y: 100, opacity: 0, duration: 0.5 }
|
return { y: 100, opacity: 0, duration: 0.5 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataElement = Element<typeof categories.data>
|
|
||||||
useGSAP(() => {
|
useGSAP(() => {
|
||||||
const items = gsap?.utils.toArray<GSAPTweenTarget>('.gsapan');
|
const items = gsap?.utils.toArray<GSAPTweenTarget>('.gsapan');
|
||||||
const tl = gsap?.timeline();
|
const tl = gsap?.timeline();
|
||||||
@@ -56,7 +52,7 @@ export default function CvPage() {
|
|||||||
<SidebarContent className="p-2">
|
<SidebarContent className="p-2">
|
||||||
{categories.data.filter((cat) => cat.layoutPosition == 'sidebar').map((cat) => {
|
{categories.data.filter((cat) => cat.layoutPosition == 'sidebar').map((cat) => {
|
||||||
return (
|
return (
|
||||||
<CvCategory layout="col" initialData={cat} />
|
<CvCategory layout="col" initialData={cat} key={cat.id} />
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
@@ -65,29 +61,32 @@ export default function CvPage() {
|
|||||||
<></>
|
<></>
|
||||||
}
|
}
|
||||||
<div className="h-full w-full flex flex-wrap flex-row p-2 ">
|
<div className="h-full w-full flex flex-wrap flex-row p-2 ">
|
||||||
<div id="mainwrap" className="flex flex-wrap w-full flex-col">
|
<div id="mainwrap" className="flex w-full flex-col gap-[1rem]">
|
||||||
<div id="header" className="flex flex-wrap w-full h-1/4 flex-row">
|
<div id="header" className="flex w-full h-fit flex-row">
|
||||||
{categories.data.filter((cat) => cat.layoutPosition == 'header').map((cat) => {
|
{categories.data.filter((cat) => cat.layoutPosition == 'header').map((cat) => {
|
||||||
return (
|
return (
|
||||||
<CvCategory layout="row" initialData={cat} />
|
<CvCategory layout="row" initialData={cat} key={cat.id} />
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div id="colwrapper" className="flex flex-wrap flex-row w-full h-3/4">
|
<div id="colwrapper" className="flex flex-col md:flex-row w-full h-3/4 gap-[1rem]">
|
||||||
<div id="col1" className="flex flex-col w-full lg:w-1/2 h-full">
|
<div id="col1" className="flex flex-col w-full h-full">
|
||||||
{categories.data.filter((cat) => cat.layoutPosition == 'col1').map((cat) => {
|
{categories.data.filter((cat) => cat.layoutPosition == 'col1').map((cat) => {
|
||||||
return (
|
return (
|
||||||
<CvCategory layout="col" initialData={cat} />
|
<CvCategory layout="col" initialData={cat} key={cat.id} />
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
{categories.data.filter((cat) => cat.layoutPosition == 'col2').length > 0 ?
|
||||||
<div id="col2" className="flex flex-wrap flex-col w-full lg:w-1/2 h-full">
|
<div id="col2" className="flex flex-wrap flex-col w-full lg:w-1/2 h-full">
|
||||||
{categories.data.filter((cat) => cat.layoutPosition == 'col2').map((cat) => {
|
{categories.data.filter((cat) => cat.layoutPosition == 'col2').map((cat) => {
|
||||||
return (
|
return (
|
||||||
<CvCategory layout="col" initialData={cat} />
|
<CvCategory layout="col" initialData={cat} key={cat.id} />
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div> :
|
||||||
|
<></>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { eq } from "drizzle-orm";
|
|||||||
import { isAdmin } from "~/app/actions";
|
import { isAdmin } from "~/app/actions";
|
||||||
|
|
||||||
import * as Schemas from "~/lib/schema/cv/category"
|
import * as Schemas from "~/lib/schema/cv/category"
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError, type inferRouterOutputs } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const CategoryRouter = router({
|
export const CategoryRouter = router({
|
||||||
@@ -72,3 +72,4 @@ export const CategoryRouter = router({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export type CategoryRouterOutputs = inferRouterOutputs<typeof CategoryRouter>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { eq } from "drizzle-orm";
|
|||||||
import { isAdmin } from "~/app/actions";
|
import { isAdmin } from "~/app/actions";
|
||||||
|
|
||||||
import * as Schemas from "~/lib/schema/cv/entry"
|
import * as Schemas from "~/lib/schema/cv/entry"
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError, type inferRouterOutputs } from "@trpc/server";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
export const EntryRouter = router({
|
export const EntryRouter = router({
|
||||||
@@ -72,3 +72,4 @@ export const EntryRouter = router({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export type EntryRouterOutputs = inferRouterOutputs<typeof EntryRouter>
|
||||||
|
|||||||
@@ -47,72 +47,72 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.1rem;
|
--radius: 0rem;
|
||||||
--background: oklch(1 0 0);
|
--background: oklch(1 0 0);
|
||||||
--foreground: oklch(0.129 0.042 264.695);
|
--foreground: oklch(0.141 0.005 285.823);
|
||||||
--card: oklch(1 0 0);
|
--card: oklch(1 0 0);
|
||||||
--card-foreground: oklch(0.129 0.042 264.695);
|
--card-foreground: oklch(0.141 0.005 285.823);
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||||
--primary: oklch(0.208 0.042 265.755);
|
--primary: oklch(0.705 0.213 47.604);
|
||||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
--primary-foreground: oklch(0.98 0.016 73.684);
|
||||||
--secondary: oklch(0.968 0.007 247.896);
|
--secondary: oklch(0.967 0.001 286.375);
|
||||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||||
--muted: oklch(0.968 0.007 247.896);
|
--muted: oklch(0.967 0.001 286.375);
|
||||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||||
--accent: oklch(0.968 0.007 247.896);
|
--accent: oklch(0.967 0.001 286.375);
|
||||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
--border: oklch(0.929 0.013 255.508);
|
--border: oklch(0.92 0.004 286.32);
|
||||||
--input: oklch(0.929 0.013 255.508);
|
--input: oklch(0.92 0.004 286.32);
|
||||||
--ring: oklch(0.704 0.04 256.788);
|
--ring: oklch(0.705 0.213 47.604);
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
--chart-1: oklch(0.646 0.222 41.116);
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
--chart-2: oklch(0.6 0.118 184.704);
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
--sidebar: oklch(0.984 0.003 247.858);
|
--sidebar: oklch(0.985 0 0);
|
||||||
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||||
--sidebar-primary: oklch(0.208 0.042 265.755);
|
--sidebar-primary: oklch(0.705 0.213 47.604);
|
||||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
--sidebar-primary-foreground: oklch(0.98 0.016 73.684);
|
||||||
--sidebar-accent: oklch(0.968 0.007 247.896);
|
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||||
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||||
--sidebar-border: oklch(0.929 0.013 255.508);
|
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||||
--sidebar-ring: oklch(0.704 0.04 256.788);
|
--sidebar-ring: oklch(0.705 0.213 47.604);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: oklch(0.129 0.042 264.695);
|
--background: oklch(0.141 0.005 285.823);
|
||||||
--foreground: oklch(0.984 0.003 247.858);
|
--foreground: oklch(0.985 0 0);
|
||||||
--card: oklch(0.208 0.042 265.755);
|
--card: oklch(0.21 0.006 285.885);
|
||||||
--card-foreground: oklch(0.984 0.003 247.858);
|
--card-foreground: oklch(0.985 0 0);
|
||||||
--popover: oklch(0.208 0.042 265.755);
|
--popover: oklch(0.21 0.006 285.885);
|
||||||
--popover-foreground: oklch(0.984 0.003 247.858);
|
--popover-foreground: oklch(0.985 0 0);
|
||||||
--primary: oklch(0.929 0.013 255.508);
|
--primary: oklch(0.646 0.222 41.116);
|
||||||
--primary-foreground: oklch(0.208 0.042 265.755);
|
--primary-foreground: oklch(0.98 0.016 73.684);
|
||||||
--secondary: oklch(0.279 0.041 260.031);
|
--secondary: oklch(0.274 0.006 286.033);
|
||||||
--secondary-foreground: oklch(0.984 0.003 247.858);
|
--secondary-foreground: oklch(0.985 0 0);
|
||||||
--muted: oklch(0.279 0.041 260.031);
|
--muted: oklch(0.274 0.006 286.033);
|
||||||
--muted-foreground: oklch(0.704 0.04 256.788);
|
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||||
--accent: oklch(0.279 0.041 260.031);
|
--accent: oklch(0.274 0.006 286.033);
|
||||||
--accent-foreground: oklch(0.984 0.003 247.858);
|
--accent-foreground: oklch(0.985 0 0);
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
--destructive: oklch(0.704 0.191 22.216);
|
||||||
--border: oklch(1 0 0 / 10%);
|
--border: oklch(1 0 0 / 10%);
|
||||||
--input: oklch(1 0 0 / 15%);
|
--input: oklch(1 0 0 / 15%);
|
||||||
--ring: oklch(0.551 0.027 264.364);
|
--ring: oklch(0.646 0.222 41.116);
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
--chart-1: oklch(0.488 0.243 264.376);
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
--chart-2: oklch(0.696 0.17 162.48);
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
--chart-3: oklch(0.769 0.188 70.08);
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
--chart-4: oklch(0.627 0.265 303.9);
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
--chart-5: oklch(0.645 0.246 16.439);
|
||||||
--sidebar: oklch(0.208 0.042 265.755);
|
--sidebar: oklch(0.21 0.006 285.885);
|
||||||
--sidebar-foreground: oklch(0.984 0.003 247.858);
|
--sidebar-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
--sidebar-primary: oklch(0.646 0.222 41.116);
|
||||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
--sidebar-primary-foreground: oklch(0.98 0.016 73.684);
|
||||||
--sidebar-accent: oklch(0.279 0.041 260.031);
|
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||||
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.551 0.027 264.364);
|
--sidebar-ring: oklch(0.646 0.222 41.116);
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
Reference in New Issue
Block a user