Bulk commit: Stand ende 22.01.
This commit is contained in:
@@ -1,9 +1,240 @@
|
||||
import { cn } from '@/lib/utils'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { format } from 'date-fns'
|
||||
import {
|
||||
BotIcon,
|
||||
CircleUserIcon,
|
||||
ClipboardCheckIcon,
|
||||
ProjectorIcon,
|
||||
ReceiptEuroIcon,
|
||||
SpeakerIcon,
|
||||
} from 'lucide-react'
|
||||
import { ReactNode } from 'react'
|
||||
import { string } from 'zod'
|
||||
|
||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/audit')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumb: 'Audit',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/_sidebar/projects/view/$id/audit"!</div>
|
||||
enum TimelineAction {
|
||||
General,
|
||||
Equipment,
|
||||
Personal,
|
||||
Finance,
|
||||
Todo,
|
||||
}
|
||||
|
||||
enum TimelineActionType {
|
||||
Good,
|
||||
Bad,
|
||||
Neutral,
|
||||
}
|
||||
|
||||
type TimelineEntry = {
|
||||
title: string
|
||||
description: string
|
||||
date: Date
|
||||
action: TimelineAction
|
||||
actionType: TimelineActionType
|
||||
}
|
||||
|
||||
const timelineEntries: Array<TimelineEntry> = [
|
||||
{
|
||||
title: 'Payed #5',
|
||||
description: 'Client payed bill number 5.',
|
||||
action: TimelineAction.Finance,
|
||||
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'Confirmed #1',
|
||||
description: 'Client confirmed project 1.',
|
||||
action: TimelineAction.General,
|
||||
date: new Date(2025, 12, 4, 11),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'New Task',
|
||||
description: 'Konstantin Hintermayer added task 1.',
|
||||
action: TimelineAction.Todo,
|
||||
date: new Date(),
|
||||
actionType: TimelineActionType.Neutral,
|
||||
},
|
||||
{
|
||||
title: 'Payed #5',
|
||||
description: 'Client payed bill number 5.',
|
||||
action: TimelineAction.Finance,
|
||||
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'Confirmed #1',
|
||||
description: 'Client confirmed project 1.',
|
||||
action: TimelineAction.General,
|
||||
date: new Date(2025, 12, 4, 11),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'New Task',
|
||||
description: 'Konstantin Hintermayer added task 1.',
|
||||
action: TimelineAction.Todo,
|
||||
date: new Date(),
|
||||
actionType: TimelineActionType.Neutral,
|
||||
},
|
||||
{
|
||||
title: 'Payed #5',
|
||||
description: 'Client payed bill number 5.',
|
||||
action: TimelineAction.Finance,
|
||||
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'Confirmed #1',
|
||||
description: 'Client confirmed project 1.',
|
||||
action: TimelineAction.General,
|
||||
date: new Date(2025, 12, 4, 11),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'New Task',
|
||||
description: 'Konstantin Hintermayer added task 1.',
|
||||
action: TimelineAction.Todo,
|
||||
date: new Date(),
|
||||
actionType: TimelineActionType.Neutral,
|
||||
},
|
||||
{
|
||||
title: 'Payed #5',
|
||||
description: 'Client payed bill number 5.',
|
||||
action: TimelineAction.Finance,
|
||||
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'Confirmed #1',
|
||||
description: 'Client confirmed project 1.',
|
||||
action: TimelineAction.General,
|
||||
date: new Date(2025, 12, 4, 11),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'New Task',
|
||||
description: 'Konstantin Hintermayer added task 1.',
|
||||
action: TimelineAction.Todo,
|
||||
date: new Date(),
|
||||
actionType: TimelineActionType.Neutral,
|
||||
},
|
||||
{
|
||||
title: 'Payed #5',
|
||||
description: 'Client payed bill number 5.',
|
||||
action: TimelineAction.Finance,
|
||||
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'Confirmed #1',
|
||||
description: 'Client confirmed project 1.',
|
||||
action: TimelineAction.General,
|
||||
date: new Date(2025, 12, 4, 11),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'New Task',
|
||||
description: 'Konstantin Hintermayer added task 1.',
|
||||
action: TimelineAction.Todo,
|
||||
date: new Date(),
|
||||
actionType: TimelineActionType.Neutral,
|
||||
},
|
||||
{
|
||||
title: 'Payed #5',
|
||||
description: 'Client payed bill number 5.',
|
||||
action: TimelineAction.Finance,
|
||||
date: new Date(2025, 12, 5, 10, 54, 23),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'Confirmed #1',
|
||||
description: 'Client confirmed project 1.',
|
||||
action: TimelineAction.General,
|
||||
date: new Date(2025, 12, 4, 11),
|
||||
actionType: TimelineActionType.Good,
|
||||
},
|
||||
{
|
||||
title: 'New Task',
|
||||
description: 'Konstantin Hintermayer added task 1.',
|
||||
action: TimelineAction.Todo,
|
||||
date: new Date(),
|
||||
actionType: TimelineActionType.Neutral,
|
||||
},
|
||||
]
|
||||
|
||||
function GetIconFromAction(ta: TimelineAction) {
|
||||
switch (ta) {
|
||||
case TimelineAction.Equipment:
|
||||
return <SpeakerIcon className="h-6" />
|
||||
case TimelineAction.Finance:
|
||||
return <ReceiptEuroIcon className="h-6" />
|
||||
case TimelineAction.General:
|
||||
return <BotIcon className="h-6" />
|
||||
case TimelineAction.Personal:
|
||||
return <CircleUserIcon className="h-6" />
|
||||
case TimelineAction.Todo:
|
||||
return <ClipboardCheckIcon className="h-6" />
|
||||
default:
|
||||
return <p>Icon not found</p>
|
||||
}
|
||||
}
|
||||
|
||||
function GetColorFromActionType(at: TimelineActionType) {
|
||||
switch (at) {
|
||||
case TimelineActionType.Bad:
|
||||
return 'bg-red-500'
|
||||
case TimelineActionType.Good:
|
||||
return 'bg-green-500'
|
||||
case TimelineActionType.Neutral:
|
||||
return 'bg-muted'
|
||||
}
|
||||
}
|
||||
|
||||
function Timeline({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="relative">
|
||||
{children}
|
||||
<div className="h-full absolute top-0 left-timeline-line w-[2px] bg-muted"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function TimelineEntryComponent({ te }: { te: TimelineEntry }) {
|
||||
return (
|
||||
<div className="flex m-2 gap-3">
|
||||
<div
|
||||
className={cn(
|
||||
`h-8 aspect-square flex items-center justify-center rounded-full border-2 border-background z-10`,
|
||||
GetColorFromActionType(te.actionType),
|
||||
)}
|
||||
>
|
||||
{GetIconFromAction(te.action)}
|
||||
</div>
|
||||
<div>
|
||||
{format(te.date, 'hh:mm dd.MM.yyyy')} {te.title}
|
||||
<br />
|
||||
{te.description}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<Timeline>
|
||||
{timelineEntries.map((te, i) => {
|
||||
return <TimelineEntryComponent te={te} key={i}></TimelineEntryComponent>
|
||||
})}
|
||||
</Timeline>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,17 @@ import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/equipment')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumb: 'Equipment',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/_sidebar/projects/view/$id/equipment"!</div>
|
||||
return (
|
||||
<div className="bg-emerald-500 h-full">
|
||||
Hello "/_sidebar/projects/view/$id/equipment"!
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@ import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/finance')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumb: 'Finanzen',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
|
||||
@@ -27,9 +27,19 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { useSuspenseQuery } from '@connectrpc/connect-query'
|
||||
import { getProject } from '@/gen/project/v1/project-ProjectService_connectquery'
|
||||
import { Breadcrumb } from '@/components/ui/breadcrumb'
|
||||
import { Map, MapControls, MapMarker, MapRef, MarkerContent, MarkerPopup, MarkerTooltip } from '@/components/ui/map'
|
||||
import { useRef } from 'react'
|
||||
|
||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumb: 'General',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const managers = [
|
||||
@@ -100,6 +110,21 @@ const clients = [
|
||||
},
|
||||
]
|
||||
|
||||
const locations = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'SZU',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Stadthalle',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Gasometer',
|
||||
},
|
||||
]
|
||||
|
||||
const formSchema = z.object({
|
||||
title: z
|
||||
.string()
|
||||
@@ -113,11 +138,12 @@ const formSchema = z.object({
|
||||
type: z.string(),
|
||||
status: z.string(),
|
||||
client: z.number(),
|
||||
location: z.number(),
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
const { id } = Route.useParams()
|
||||
const { data } = useProject(Number(id))
|
||||
const { data } = useSuspenseQuery(getProject, { id: Number(id) })
|
||||
const { mutate } = useProjectEdit(Number(id))
|
||||
|
||||
const form = useForm({
|
||||
@@ -128,6 +154,7 @@ function RouteComponent() {
|
||||
type: 'Tour',
|
||||
status: 'Confirmed',
|
||||
client: 1,
|
||||
location: 1,
|
||||
},
|
||||
validators: {
|
||||
onSubmit: formSchema,
|
||||
@@ -138,91 +165,222 @@ function RouteComponent() {
|
||||
name: value.title!,
|
||||
description: value.description!,
|
||||
icon: '',
|
||||
MandantID: data?.MandantID,
|
||||
MandantID: 1,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<form
|
||||
id="project-esther-graf-general-form"
|
||||
className="w-2xl"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
form.handleSubmit()
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<div className='w-full mt-2 grid grid-cols-6 lg:grid-cols-12 gap-6'>
|
||||
<div className="col-span-6 rounded-md bg-sidebar p-2 overflow-hidden">
|
||||
<form
|
||||
id="project-esther-graf-general-form"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
form.handleSubmit()
|
||||
}}
|
||||
>
|
||||
<FieldGroup>
|
||||
<FieldLegend>Projekt Info</FieldLegend>
|
||||
<FieldDescription>
|
||||
Allgemeine Infos über das Projekt
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="title"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>Projektname</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
aria-invalid={isInvalid}
|
||||
placeholder="Login button not working on mobile"
|
||||
autoComplete="off"
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="description"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>Beschreibung</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupTextarea
|
||||
<FieldLegend>Projekt Info</FieldLegend>
|
||||
<FieldDescription>
|
||||
Allgemeine Infos über das Projekt
|
||||
</FieldDescription>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="title"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>Projektname</FieldLabel>
|
||||
<Input
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
placeholder="I'm having an issue with the login button on mobile."
|
||||
rows={6}
|
||||
className="min-h-24 resize-none"
|
||||
aria-invalid={isInvalid}
|
||||
placeholder="Login button not working on mobile"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<InputGroupAddon align="block-end">
|
||||
<InputGroupText className="tabular-nums">
|
||||
{field.state.value.length}/100 characters
|
||||
</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FieldDescription>
|
||||
Allgemeine Infos zum Projekt
|
||||
</FieldDescription>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="description"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field data-invalid={isInvalid}>
|
||||
<FieldLabel htmlFor={field.name}>Beschreibung</FieldLabel>
|
||||
<InputGroup>
|
||||
<InputGroupTextarea
|
||||
id={field.name}
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onBlur={field.handleBlur}
|
||||
onChange={(e) => field.handleChange(e.target.value)}
|
||||
placeholder="I'm having an issue with the login button on mobile."
|
||||
rows={6}
|
||||
className="min-h-24 resize-none"
|
||||
aria-invalid={isInvalid}
|
||||
/>
|
||||
<InputGroupAddon align="block-end">
|
||||
<InputGroupText className="tabular-nums">
|
||||
{field.state.value.length}/100 characters
|
||||
</InputGroupText>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
<FieldDescription>
|
||||
Allgemeine Infos zum Projekt
|
||||
</FieldDescription>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="manager"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||
Manager
|
||||
</FieldLabel>
|
||||
<FieldDescription>
|
||||
Zuständiger für dieses Projekt
|
||||
</FieldDescription>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</FieldContent>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value.toString()}
|
||||
onValueChange={(v) => field.handleChange(Number(v))}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="form-tanstack-select-language"
|
||||
aria-invalid={isInvalid}
|
||||
className="min-w-[120px]"
|
||||
>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{managers.map((manager) => (
|
||||
<SelectItem
|
||||
key={manager.id}
|
||||
value={manager.id.toString()}
|
||||
>
|
||||
{manager.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="type"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||
Projekttyp
|
||||
</FieldLabel>
|
||||
<FieldDescription>
|
||||
Art des Projektes (Gibt voreinstellungen für Felder
|
||||
wie zum Beispiel: Zahlungskonditionen vor)
|
||||
</FieldDescription>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</FieldContent>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onValueChange={field.handleChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="form-tanstack-select-language"
|
||||
aria-invalid={isInvalid}
|
||||
className="min-w-[120px]"
|
||||
>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{projectTypes.map((prj) => (
|
||||
<SelectItem key={prj.name} value={prj.name}>
|
||||
{prj.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="status"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||
Status
|
||||
</FieldLabel>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</FieldContent>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onValueChange={field.handleChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="form-tanstack-select-language"
|
||||
aria-invalid={isInvalid}
|
||||
className="min-w-[120px]"
|
||||
>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{statusse.map((stat) => (
|
||||
<SelectItem key={stat.name} value={stat.name}>
|
||||
{stat.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</FieldGroup>
|
||||
</FieldGroup>
|
||||
<FieldSeparator />
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="manager"
|
||||
name="client"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
@@ -230,11 +388,8 @@ function RouteComponent() {
|
||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||
Manager
|
||||
Client
|
||||
</FieldLabel>
|
||||
<FieldDescription>
|
||||
Zuständiger für dieses Projekt
|
||||
</FieldDescription>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
@@ -252,12 +407,9 @@ function RouteComponent() {
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{managers.map((manager) => (
|
||||
<SelectItem
|
||||
key={manager.id}
|
||||
value={manager.id.toString()}
|
||||
>
|
||||
{manager.name}
|
||||
{clients.map((stat) => (
|
||||
<SelectItem key={stat.id} value={stat.id.toString()}>
|
||||
{stat.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -266,8 +418,10 @@ function RouteComponent() {
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</FieldGroup>
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="type"
|
||||
name="location"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
@@ -275,20 +429,16 @@ function RouteComponent() {
|
||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||
Projekttyp
|
||||
Location
|
||||
</FieldLabel>
|
||||
<FieldDescription>
|
||||
Art des Projektes (Gibt voreinstellungen für Felder
|
||||
wie zum Beispiel: Zahlungskonditionen vor)
|
||||
</FieldDescription>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</FieldContent>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onValueChange={field.handleChange}
|
||||
value={field.state.value.toString()}
|
||||
onValueChange={(v) => field.handleChange(Number(v))}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="form-tanstack-select-language"
|
||||
@@ -298,47 +448,8 @@ function RouteComponent() {
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{projectTypes.map((prj) => (
|
||||
<SelectItem key={prj.name} value={prj.name}>
|
||||
{prj.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<form.Field
|
||||
name="status"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||
Status
|
||||
</FieldLabel>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</FieldContent>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value}
|
||||
onValueChange={field.handleChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="form-tanstack-select-language"
|
||||
aria-invalid={isInvalid}
|
||||
className="min-w-[120px]"
|
||||
>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{statusse.map((stat) => (
|
||||
<SelectItem key={stat.name} value={stat.name}>
|
||||
{locations.map((stat) => (
|
||||
<SelectItem key={stat.id} value={stat.id.toString()}>
|
||||
{stat.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -350,58 +461,66 @@ function RouteComponent() {
|
||||
/>
|
||||
</FieldGroup>
|
||||
</FieldGroup>
|
||||
<FieldSeparator />
|
||||
<FieldGroup>
|
||||
<form.Field
|
||||
name="client"
|
||||
children={(field) => {
|
||||
const isInvalid =
|
||||
field.state.meta.isTouched && !field.state.meta.isValid
|
||||
return (
|
||||
<Field orientation="responsive" data-invalid={isInvalid}>
|
||||
<FieldContent>
|
||||
<FieldLabel htmlFor="form-tanstack-select-language">
|
||||
Client
|
||||
</FieldLabel>
|
||||
{isInvalid && (
|
||||
<FieldError errors={field.state.meta.errors} />
|
||||
)}
|
||||
</FieldContent>
|
||||
<Select
|
||||
name={field.name}
|
||||
value={field.state.value.toString()}
|
||||
onValueChange={(v) => field.handleChange(Number(v))}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="form-tanstack-select-language"
|
||||
aria-invalid={isInvalid}
|
||||
className="min-w-[120px]"
|
||||
>
|
||||
<SelectValue placeholder="Select" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="item-aligned">
|
||||
{clients.map((stat) => (
|
||||
<SelectItem key={stat.id} value={stat.id.toString()}>
|
||||
{stat.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Field>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</FieldGroup>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
<Field orientation="horizontal" className="mt-4">
|
||||
<Button type="button" variant="outline" onClick={() => form.reset()}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button type="submit" form="project-esther-graf-general-form">
|
||||
Submit
|
||||
</Button>
|
||||
</Field>
|
||||
</>
|
||||
</form>
|
||||
<Field orientation="horizontal" className="mt-4">
|
||||
<Button type="button" variant="outline" onClick={() => form.reset()}>
|
||||
Reset
|
||||
</Button>
|
||||
<Button type="submit" form="project-esther-graf-general-form">
|
||||
Submit
|
||||
</Button>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="col-span-6 rounded-md overflow-hidden h-128 max-h-128 bg-sidebar flex flex-col p-3">
|
||||
<ShowEventLocation lat={48.202373} lon={16.332889} label='Wiener Stadthalle D'/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function ShowEventLocation({lat, lon, label}: {lat: number, lon: number, label: string}) {
|
||||
const map = useRef<MapRef>(null);
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2 className='text-xl mb-1'>Location</h2>
|
||||
<div className="flex-grow rounded-md overflow-hidden">
|
||||
<Map
|
||||
center={{
|
||||
lat,
|
||||
lon,
|
||||
}}
|
||||
zoom={15}
|
||||
ref={map}
|
||||
>
|
||||
<MapControls
|
||||
position='bottom-right'
|
||||
showFullscreen
|
||||
showLocate
|
||||
showCompass
|
||||
showZoom
|
||||
/>
|
||||
<MapMarker
|
||||
latitude={lat}
|
||||
longitude={lon}
|
||||
>
|
||||
<MarkerContent>
|
||||
<div className="size-4 rounded-full bg-emerald-500 border-2 border-white shadow-lg" />
|
||||
</MarkerContent>
|
||||
<MarkerTooltip>{label}</MarkerTooltip>
|
||||
<MarkerPopup>
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-foreground">{label}</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{lat.toFixed(4)}, {lon.toFixed(4)}
|
||||
</p>
|
||||
</div>
|
||||
</MarkerPopup>
|
||||
</MapMarker>
|
||||
</Map>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,11 @@ import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/personal')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumb: 'Personal',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
|
||||
@@ -1,9 +1,34 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import TimelineTable from '@/features/Projects/components/timelinetable'
|
||||
import { Scheduler } from "@bitnoi.se/react-scheduler"
|
||||
|
||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/timeline')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumb: 'Timeline',
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/_sidebar/projects/view/$id/timeline"!</div>
|
||||
return <div className='h-full max-h-full relative'>
|
||||
<Scheduler data={[{
|
||||
id: '1',
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
startDate: new Date(2026, 0, 11, 16, 0, 0),
|
||||
endDate: new Date(2026, 0, 11, 18, 0, 0),
|
||||
title: "TEST",
|
||||
occupancy: 1
|
||||
}
|
||||
],
|
||||
label: {
|
||||
title: "Hello World",
|
||||
icon: "NZLL",
|
||||
subtitle: "TEST"
|
||||
}
|
||||
}]}/>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,9 +1,43 @@
|
||||
import TodoTable from '@/features/Projects/components/todotable'
|
||||
import { getTodo, listTodos } from '@/gen/todo/v1/todo-TodoService_connectquery'
|
||||
import {
|
||||
createInfiniteQueryOptions,
|
||||
createQueryOptions,
|
||||
} from '@connectrpc/connect-query'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
const fetchSize = 25
|
||||
export const Route = createFileRoute('/_sidebar/projects/view/$id/todos')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: () => {
|
||||
return {
|
||||
breadcrumb: 'Todos',
|
||||
}
|
||||
},
|
||||
loader: async ({ params: { id }, context }) => {
|
||||
await context.queryClient.ensureInfiniteQueryData(
|
||||
createInfiniteQueryOptions(
|
||||
listTodos,
|
||||
{
|
||||
page: 0,
|
||||
perPage: fetchSize,
|
||||
orberBy: 'id',
|
||||
asc: false,
|
||||
},
|
||||
{
|
||||
transport: context.transport,
|
||||
getNextPageParam: (_, p) => {
|
||||
console.log(p.length)
|
||||
return p.length * fetchSize
|
||||
// return ()
|
||||
},
|
||||
pageParamKey: 'page',
|
||||
},
|
||||
),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/_sidebar/projects/view/$id/todos"!</div>
|
||||
return <TodoTable />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user