mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:43:06 +00:00
- Substitui loadActivityOptions (pagination: false) por AsyncSearchCombobox com busca server-backed filtrada por município (searchActivityRelationOptions). - Pagina card Demandas vinculadas no detalhe da atividade (10/página) com soma de custo em query separada e link Ver todas na lista filtrada. - Adiciona filtro ?activity= na lista /campanha/demandas. Co-authored-by: Francisco Solla <fsolla@users.noreply.github.com>
85 lines
2.4 KiB
TypeScript
85 lines
2.4 KiB
TypeScript
import 'server-only'
|
|
|
|
import type { Payload } from 'payload'
|
|
|
|
import { relationshipId } from '@/lib/relationship'
|
|
import type { CampaignUser } from '@/payload-types'
|
|
|
|
export type ActivityRelationOption = {
|
|
id: number
|
|
label: string
|
|
municipalityId: number
|
|
}
|
|
|
|
export const ACTIVITY_RELATION_SEARCH_LIMIT = 20
|
|
|
|
const MIN_ACTIVITY_TITLE_QUERY_LENGTH = 2
|
|
|
|
const isActivityTitleQueryReady = (query: string) =>
|
|
query.trim().length === 0 || query.trim().length >= MIN_ACTIVITY_TITLE_QUERY_LENGTH
|
|
|
|
const toActivityRelationOption = (activity: {
|
|
id: number
|
|
title: string
|
|
municipality: unknown
|
|
}): ActivityRelationOption | null => {
|
|
const municipalityId = relationshipId(activity.municipality)
|
|
if (!municipalityId) return null
|
|
return { id: activity.id, label: activity.title, municipalityId }
|
|
}
|
|
|
|
export const loadActivityRelationOptionById = async (
|
|
payload: Pick<Payload, 'find'>,
|
|
user: CampaignUser,
|
|
activityId: number,
|
|
): Promise<ActivityRelationOption | null> => {
|
|
const result = await payload.find({
|
|
collection: 'activity',
|
|
where: { id: { equals: activityId } },
|
|
depth: 0,
|
|
limit: 1,
|
|
pagination: false,
|
|
select: { title: true, municipality: true },
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
|
|
const activity = result.docs[0]
|
|
return activity ? toActivityRelationOption(activity) : null
|
|
}
|
|
|
|
/**
|
|
* Server-backed activity picker for demand forms — scoped to one municipality when set.
|
|
* Empty query returns recent activities in that municipality; typed query filters by title.
|
|
*/
|
|
export const searchActivityRelationOptions = async (
|
|
payload: Pick<Payload, 'find'>,
|
|
user: CampaignUser,
|
|
query: string,
|
|
municipalityId: number | null,
|
|
): Promise<ActivityRelationOption[]> => {
|
|
if (!municipalityId || !isActivityTitleQueryReady(query)) return []
|
|
|
|
const trimmed = query.trim().slice(0, 120)
|
|
|
|
const result = await payload.find({
|
|
collection: 'activity',
|
|
where: {
|
|
and: [
|
|
{ municipality: { equals: municipalityId } },
|
|
...(trimmed ? [{ title: { contains: trimmed } }] : []),
|
|
],
|
|
},
|
|
depth: 0,
|
|
limit: ACTIVITY_RELATION_SEARCH_LIMIT,
|
|
page: 1,
|
|
sort: 'title',
|
|
select: { title: true, municipality: true },
|
|
user,
|
|
overrideAccess: false,
|
|
})
|
|
|
|
return result.docs
|
|
.map((activity) => toActivityRelationOption(activity))
|
|
.filter((option): option is ActivityRelationOption => option !== null)
|
|
}
|