New: Sidebar and Mandantenwechsel

This commit is contained in:
2025-08-20 00:27:06 +02:00
parent bcb5ed4768
commit eb66f4f394
32 changed files with 3093 additions and 63 deletions

View File

@ -0,0 +1,23 @@
import { useAnsprechpartner, useAnsprechpartnerEditMutation } from '../queries'
function AnsprechpartnerDetail({ id }: { id: number }) {
const { data } = useAnsprechpartner(id)
const { mutate } = useAnsprechpartnerEditMutation()
return (
<div>
{JSON.stringify(data)}
<button
onClick={(e) => {
const newAnsprechparter = data!
newAnsprechparter.first_name += '1'
mutate(newAnsprechparter)
}}
>
Edit Ansprechpartner
</button>
</div>
)
}
export default AnsprechpartnerDetail

View File

@ -0,0 +1,73 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
const ansprechpartnerKeys = {
all: ['ansprechpartner'] as const,
lists: () => [...ansprechpartnerKeys.all, 'list'] as const,
details: () => [...ansprechpartnerKeys.all, 'detail'] as const,
detail: (id: number) => [...ansprechpartnerKeys.details(), id] as const,
}
type Ansprechpartner = {
ID: number
CreatedAt: Date
UpdatedAt: Date | undefined
DeletedAt: Date | undefined
image_url: string
title: string
first_name: string
last_name: string
last_title: string
e_mail: string
phone: string
mobile: string
notes: string
description: string
active: boolean
gender: number
}
export function useAllAnsprechpartners() {
return useQuery<Ansprechpartner>({
queryKey: ansprechpartnerKeys.lists(),
queryFn: async () => {
const data = await fetch('http://localhost:3000/v1/ansprechpartner/all')
return await data.json()
},
})
}
export function useAnsprechpartner(id: number) {
return useQuery<Ansprechpartner>({
queryKey: ansprechpartnerKeys.detail(id),
queryFn: async () => {
const data = await fetch('http://localhost:3000/v1/ansprechpartner/' + id)
return await data.json()
},
})
}
export function useAnsprechpartnerEditMutation() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (ansprechpartner: Ansprechpartner) => {
const res = await fetch(
'http://localhost:3000/v1/ansprechpartner/' + ansprechpartner.ID,
{
headers: {
'content-type': 'application/json',
},
method: 'PUT',
body: JSON.stringify(ansprechpartner),
},
)
queryClient.invalidateQueries({
queryKey: [
ansprechpartnerKeys.detail(ansprechpartner.ID),
ansprechpartnerKeys.lists(),
],
})
},
})
}

View File

@ -0,0 +1,123 @@
import { useEffect } from 'react'
import { ChevronDown, Plus } from 'lucide-react'
import {
useAllMandanten,
useCurrentMandant,
useCurrentMandantMutation,
} from '../queries'
import type { Mandant } from '../queries'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from '@/components/ui/sidebar'
export function TeamSwitcher() {
const { data: currentMandant } = useCurrentMandant()
const { data: mandanten } = useAllMandanten()
const { mutate } = useCurrentMandantMutation()
useEffect(() => {
console.log('AddEvent')
const down = (e: KeyboardEvent) => {
console.log('Keydown: ' + e.key)
let numKey = Number(e.key)
if ((e.metaKey || e.ctrlKey) && !Number.isNaN(numKey)) {
console.log('CMD | META-before & ' + numKey)
numKey -= 1
console.log('CMD | META & ' + numKey)
if (mandanten) {
mutate(mandanten[numKey])
console.log('MUTATED ' + mandanten[numKey])
}
}
}
document.addEventListener('keydown', down)
return () => document.removeEventListener('keydown', down)
}, [mandanten])
if (!currentMandant || !mandanten) return <p>Loading...</p>
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton className="w-full h-full">
<div
className="text-sidebar-primary-foreground flex aspect-square size-8 rounded-lg items-center justify-center"
style={{ backgroundColor: currentMandant.color }}
>
<img className="size-4" src={currentMandant.logo} />
</div>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">
{currentMandant.name}
</span>
<span className="truncate text-xs">{currentMandant.plan}</span>
</div>
<ChevronDown className="ml-auto opacity-50" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-64 rounded-lg"
align="start"
side="bottom"
sideOffset={4}
>
<DropdownMenuLabel className="text-muted-foreground text-xs">
Teams
</DropdownMenuLabel>
{mandanten.map((mandant, index) => {
return <MandantDMI mandant={mandant} index={index} />
})}
<DropdownMenuSeparator />
<DropdownMenuItem className="gap-2 p-2">
<div className="bg-background flex size-6 items-center justify-center rounded-md border">
<Plus className="size-4" />
</div>
<div className="text-muted-foreground font-medium">Add team</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
}
function MandantDMI({ mandant, index }: { mandant: Mandant; index: number }) {
const { mutate } = useCurrentMandantMutation()
return (
<DropdownMenuItem
key={mandant.name}
onClick={() => mutate(mandant)}
className="gap-2 p-2"
>
<div
className="flex size-6 items-center justify-center rounded-sm border"
style={{ backgroundColor: mandant.color }}
>
<img className="size-4 shrink-0" src={mandant.logo} />
</div>
{mandant.name}
<DropdownMenuShortcut>{index + 1}</DropdownMenuShortcut>
</DropdownMenuItem>
)
}

View File

@ -0,0 +1,56 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
const mandantKeys = {
all: ['mandant'] as const,
lists: () => [...mandantKeys.all, 'list'] as const,
current: () => [...mandantKeys.all, 'current'] as const,
}
export type Mandant = {
id: string
name: string
logo: string
plan: string
color: string
}
export function useCurrentMandant() {
return useQuery<Mandant>({
queryKey: mandantKeys.current(),
queryFn: async () => {
const data = await fetch('http://localhost:3000/v1/mandant/current')
return await data.json()
},
})
}
export function useAllMandanten() {
return useQuery<Array<Mandant>>({
queryKey: mandantKeys.lists(),
queryFn: async () => {
const data = await fetch('http://localhost:3000/v1/mandant/all')
return await data.json()
},
})
}
export function useCurrentMandantMutation() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (mandant: Mandant) => {
const res = await fetch('http://localhost:3000/v1/mandant/current', {
headers: {
'content-type': 'application/json',
},
method: 'PUT',
body: JSON.stringify(mandant),
})
const newCurrentMandant = await res.json()
queryClient.setQueryData(
mandantKeys.current(),
(_: Mandant) => newCurrentMandant,
)
},
})
}