Skip to content

Commit cc70ebb

Browse files
committed
fix readonly views
1 parent cd191e9 commit cc70ebb

11 files changed

Lines changed: 444 additions & 182 deletions

‎backend/api/inputs.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ class CreateSavedViewInput:
206206
filter_definition: str
207207
sort_definition: str
208208
parameters: str
209-
visibility: SavedViewVisibility = SavedViewVisibility.PRIVATE
209+
visibility: SavedViewVisibility = SavedViewVisibility.LINK_SHARED
210210

211211

212212
@strawberry.input
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import type { FilterValue } from '@helpwave/hightide'
2+
import { Chip } from '@helpwave/hightide'
3+
import { User } from 'lucide-react'
4+
import { useMemo } from 'react'
5+
import { useUsers } from '@/data'
6+
import { useTasksTranslation } from '@/i18n/useTasksTranslation'
7+
import { FilterPreviewAvatar } from '@/components/tables/FilterPreviewMedia'
8+
9+
export function AssigneeFilterActiveLabel({ value }: { value: FilterValue }) {
10+
const translation = useTasksTranslation()
11+
const { data } = useUsers()
12+
const users = data?.users
13+
14+
const content = useMemo(() => {
15+
const param = value?.parameter ?? {}
16+
const op = value?.operator ?? 'equals'
17+
if (op === 'contains') {
18+
const ids = (param.uuidValues as string[] | undefined) ?? []
19+
if (ids.length === 0) {
20+
return <span className="text-sm">{translation('selectAssignee')}</span>
21+
}
22+
if (ids.length <= 2) {
23+
return (
24+
<span className="flex flex-wrap items-center gap-1">
25+
{ids.map(id => {
26+
const user = users?.find(u => u.id === id)
27+
const title = user?.name ?? id
28+
return (
29+
<Chip key={id} size="sm" color="neutral" className="max-w-[11rem] py-0.5">
30+
<span className="flex items-center gap-1 min-w-0">
31+
{user ? (
32+
<FilterPreviewAvatar name={user.name} avatarUrl={user.avatarUrl} />
33+
) : (
34+
<User className="size-3 shrink-0 scale-90" />
35+
)}
36+
<span className="truncate text-sm">{title}</span>
37+
</span>
38+
</Chip>
39+
)
40+
})}
41+
</span>
42+
)
43+
}
44+
return (
45+
<Chip size="sm" color="neutral" className="py-0.5">
46+
<span className="flex items-center gap-1.5 min-w-0">
47+
<span className="flex items-center -space-x-1 shrink-0">
48+
{ids.slice(0, 3).map(id => {
49+
const user = users?.find(u => u.id === id)
50+
return user ? (
51+
<FilterPreviewAvatar key={id} name={user.name} avatarUrl={user.avatarUrl} />
52+
) : (
53+
<User key={id} className="size-3 shrink-0 scale-90 opacity-70" />
54+
)
55+
})}
56+
</span>
57+
<span className="truncate text-sm">
58+
{ids.length} {translation('users')}
59+
</span>
60+
</span>
61+
</Chip>
62+
)
63+
}
64+
const uid = param.uuidValue != null ? String(param.uuidValue) : ''
65+
if (!uid) {
66+
return <span className="text-sm">{translation('selectAssignee')}</span>
67+
}
68+
const user = users?.find(u => u.id === uid)
69+
const title = user?.name ?? uid
70+
return (
71+
<Chip size="sm" color="neutral" className="max-w-[15rem] py-0.5">
72+
<span className="flex items-center gap-1 min-w-0">
73+
{user ? (
74+
<FilterPreviewAvatar name={user.name} avatarUrl={user.avatarUrl} />
75+
) : (
76+
<User className="size-3 shrink-0 scale-90" />
77+
)}
78+
<span className="truncate text-sm">{title}</span>
79+
</span>
80+
</Chip>
81+
)
82+
}, [users, translation, value])
83+
84+
return content
85+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { LocationType } from '@/api/gql/generated'
2+
import { LocationChips } from '@/components/locations/LocationChips'
3+
import { Avatar } from '@helpwave/hightide'
4+
import clsx from 'clsx'
5+
6+
export type FilterPreviewLocationItem = {
7+
id: string,
8+
title: string,
9+
kind?: LocationType,
10+
}
11+
12+
export function FilterPreviewAvatar({
13+
name,
14+
avatarUrl,
15+
className,
16+
}: {
17+
name: string,
18+
avatarUrl?: string | null,
19+
className?: string,
20+
}) {
21+
return (
22+
<span
23+
className={clsx(
24+
'inline-flex shrink-0 origin-center scale-[0.82] rounded-full ring-1 ring-border/50 overflow-hidden align-middle',
25+
className
26+
)}
27+
>
28+
<Avatar
29+
size="xs"
30+
image={avatarUrl ? { avatarUrl, alt: name } : undefined}
31+
/>
32+
</span>
33+
)
34+
}
35+
36+
export function FilterPreviewLocationChips({
37+
locations,
38+
className,
39+
}: {
40+
locations: FilterPreviewLocationItem[],
41+
className?: string,
42+
}) {
43+
return (
44+
<span className={clsx('inline-flex min-w-0 max-w-full origin-left scale-[0.82]', className)}>
45+
<LocationChips locations={locations} small disableLink className="max-w-full" />
46+
</span>
47+
)
48+
}

‎web/components/tables/LocationFilterActiveLabel.tsx‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ import { MapPin } from 'lucide-react'
44
import { useMemo } from 'react'
55
import { useLocations } from '@/data'
66
import { useTasksTranslation } from '@/i18n/useTasksTranslation'
7+
import { FilterPreviewLocationChips } from '@/components/tables/FilterPreviewMedia'
8+
import type { LocationType } from '@/api/gql/generated'
9+
10+
function nodeToPreviewLocation(node: { id: string, title: string, kind: LocationType }) {
11+
return { id: node.id, title: node.title, kind: node.kind }
12+
}
713

814
export function LocationFilterActiveLabel({ value }: { value: FilterValue }) {
915
const translation = useTasksTranslation()
@@ -24,11 +30,17 @@ export function LocationFilterActiveLabel({ value }: { value: FilterValue }) {
2430
{ids.map(id => {
2531
const node = nodes?.find(n => n.id === id)
2632
const title = node?.title ?? id
27-
return (
33+
return node ? (
34+
<FilterPreviewLocationChips
35+
key={id}
36+
className="max-w-[min(100%,11rem)]"
37+
locations={[nodeToPreviewLocation(node)]}
38+
/>
39+
) : (
2840
<Chip key={id} size="sm" color="neutral" className="max-w-[10rem]">
2941
<span className="flex items-center gap-1 min-w-0">
30-
<MapPin className="size-3 shrink-0" />
31-
<span className="truncate">{title}</span>
42+
<MapPin className="size-3 shrink-0 scale-90" />
43+
<span className="truncate text-sm">{title}</span>
3244
</span>
3345
</Chip>
3446
)
@@ -51,11 +63,16 @@ export function LocationFilterActiveLabel({ value }: { value: FilterValue }) {
5163
}
5264
const node = nodes?.find(n => n.id === uid)
5365
const title = node?.title ?? uid
54-
return (
66+
return node ? (
67+
<FilterPreviewLocationChips
68+
className="max-w-[min(100%,16rem)]"
69+
locations={[nodeToPreviewLocation(node)]}
70+
/>
71+
) : (
5572
<Chip size="sm" color="neutral" className="max-w-[14rem]">
5673
<span className="flex items-center gap-1 min-w-0">
5774
<MapPin className="size-3 shrink-0" />
58-
<span className="truncate">{title}</span>
75+
<span className="truncate text-sm">{title}</span>
5976
</span>
6077
</Chip>
6178
)

‎web/components/tables/LocationSubtreeFilterPopUp.tsx‎

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { useTasksTranslation } from '@/i18n/useTasksTranslation'
22
import { Button, FilterBasePopUp, type FilterListPopUpBuilderProps } from '@helpwave/hightide'
3-
import { useId, useMemo, useState } from 'react'
3+
import { useId, useMemo, useState, type ReactNode } from 'react'
44
import { MapPin } from 'lucide-react'
5-
import type { LocationNodeType } from '@/api/gql/generated'
5+
import type { LocationNodeType, LocationType } from '@/api/gql/generated'
66
import { LocationSelectionDialog } from '@/components/locations/LocationSelectionDialog'
77
import { useLocations } from '@/data'
8+
import { FilterPreviewLocationChips } from '@/components/tables/FilterPreviewMedia'
9+
10+
function nodeToPreviewLocation(node: { id: string, title: string, kind: LocationType }) {
11+
return { id: node.id, title: node.title, kind: node.kind }
12+
}
813

914
export const LocationSubtreeFilterPopUp = ({ value, onValueChange, onRemove, name }: FilterListPopUpBuilderProps) => {
1015
const translation = useTasksTranslation()
@@ -51,20 +56,72 @@ export const LocationSubtreeFilterPopUp = ({ value, onValueChange, onRemove, nam
5156
setDialogOpen(false)
5257
}
5358

54-
const summary = useMemo(() => {
55-
const nodes = locationsData?.locationNodes
59+
const summaryContent = useMemo((): ReactNode => {
60+
const locationNodes = locationsData?.locationNodes
5661
if (isMulti) {
57-
const n = (uuidValues as string[] | undefined)?.length ?? 0
58-
return n === 0 ? translation('selectLocation') : `${n} ${translation('location')}`
62+
const ids = (uuidValues as string[] | undefined) ?? []
63+
const n = ids.length
64+
if (n === 0) {
65+
return (
66+
<>
67+
<MapPin className="size-4 shrink-0 opacity-60" />
68+
<span className="truncate">{translation('selectLocation')}</span>
69+
</>
70+
)
71+
}
72+
if (n > 2) {
73+
return (
74+
<>
75+
<MapPin className="size-4 shrink-0 opacity-60" />
76+
<span className="truncate text-sm">
77+
{n} {translation('location')}
78+
</span>
79+
</>
80+
)
81+
}
82+
return (
83+
<span className="flex flex-wrap items-center gap-1 min-w-0">
84+
{ids.map(locId => {
85+
const node = locationNodes?.find(x => x.id === locId)
86+
return node ? (
87+
<FilterPreviewLocationChips
88+
key={locId}
89+
className="max-w-[min(100%,9.5rem)]"
90+
locations={[nodeToPreviewLocation(node)]}
91+
/>
92+
) : (
93+
<span key={locId} className="text-sm truncate">
94+
{locId}
95+
</span>
96+
)
97+
})}
98+
</span>
99+
)
59100
}
60101
const uid = uuidValue != null && String(uuidValue) !== ''
61102
? String(uuidValue)
62103
: undefined
63104
if (!uid) {
64-
return translation('selectLocation')
105+
return (
106+
<>
107+
<MapPin className="size-4 shrink-0 opacity-60" />
108+
<span className="truncate">{translation('selectLocation')}</span>
109+
</>
110+
)
65111
}
66-
const node = nodes?.find(n => n.id === uid)
67-
return node?.title ?? translation('selectLocation')
112+
const node = locationNodes?.find(n => n.id === uid)
113+
const label = node?.title ?? translation('selectLocation')
114+
return node ? (
115+
<FilterPreviewLocationChips
116+
className="max-w-[min(100%,14rem)]"
117+
locations={[nodeToPreviewLocation(node)]}
118+
/>
119+
) : (
120+
<>
121+
<MapPin className="size-4 shrink-0 opacity-60" />
122+
<span className="truncate text-sm">{label}</span>
123+
</>
124+
)
68125
}, [isMulti, locationsData?.locationNodes, uuidValue, uuidValues, translation])
69126

70127
return (
@@ -109,11 +166,10 @@ export const LocationSubtreeFilterPopUp = ({ value, onValueChange, onRemove, nam
109166
type="button"
110167
color="neutral"
111168
coloringStyle="outline"
112-
className="inline-flex items-center gap-2"
169+
className="inline-flex items-center gap-2 min-w-0 max-w-full h-auto py-1.5"
113170
onClick={() => setDialogOpen(true)}
114171
>
115-
<MapPin className="size-4" />
116-
{summary}
172+
{summaryContent}
117173
</Button>
118174
</div>
119175
</div>

0 commit comments

Comments
 (0)