teqo/src/utilities/actionPlanViewModels.ts
Francisco Ignácio Fontoura Solla e995aefbd4 Add action-plan agenda vertical for /campanha/planos.
Ship C3 mobilization events with the actionPlan collection, list/detail/forms UI, upcoming-event blocks, access control, and migration.
2026-07-18 20:56:20 -03:00

287 lines
7.8 KiB
TypeScript

import type { ActionPlan, CampaignUser, Contact } from '@/payload-types'
import { isPopulatedRelationship, relationshipId } from '@/utilities/relationship'
const asStringArray = (value: string[] | null | undefined): string[] =>
Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
export const formatActionPlanTerritoryLabel = ({
neighborhoods = [],
locality,
cities = [],
regions = [],
}: {
neighborhoods?: string[] | null
locality?: string | null
cities?: string[] | null
regions?: string[] | null
}): string =>
[
(neighborhoods ?? []).join(', '),
locality,
(cities ?? []).join(', '),
(regions ?? []).join(', '),
]
.filter(Boolean)
.join(' · ')
export const actionPlanListSelect = {
title: true,
slug: true,
kind: true,
status: true,
startAt: true,
endAt: true,
regions: true,
cities: true,
neighborhoods: true,
locality: true,
responsible: true,
tasks: { done: true },
} as const
export type ActionPlanListViewModel = {
id: number
title: string
slug: string
kind: ActionPlan['kind']
status: ActionPlan['status']
startAt: string | null
endAt: string | null
territoryLabel: string
city: string | null
responsibleName: string | null
taskProgress: { done: number; total: number }
}
const relationshipName = (
relationship: number | Contact | CampaignUser | null | undefined,
): string | null =>
isPopulatedRelationship<Contact | CampaignUser>(relationship) ? relationship.name : null
export const toActionPlanListViewModel = (plan: ActionPlan): ActionPlanListViewModel => {
const cities = asStringArray(plan.cities)
const tasks = plan.tasks ?? []
return {
id: plan.id,
title: plan.title,
slug: plan.slug,
kind: plan.kind,
status: plan.status,
startAt: plan.startAt ?? null,
endAt: plan.endAt ?? null,
territoryLabel: formatActionPlanTerritoryLabel(plan),
city: cities[0] ?? null,
responsibleName: relationshipName(plan.responsible),
taskProgress: {
done: tasks.filter((task) => Boolean(task.done)).length,
total: tasks.length,
},
}
}
export const actionPlanFormSelect = {
title: true,
slug: true,
kind: true,
status: true,
description: true,
startAt: true,
endAt: true,
deadline: true,
regions: true,
cities: true,
neighborhoods: true,
locality: true,
territoryNotes: true,
coordinators: true,
responsible: true,
leadership: true,
tasks: true,
} as const
export type ActionPlanFormTaskViewModel = {
id: string | null
title: string
responsible: { id: number; name: string; phone: string } | null
due: string | null
done: boolean
}
export type ActionPlanFormViewModel = {
id: number
title: string
slug: string
kind: ActionPlan['kind']
status: ActionPlan['status']
description: string | null
startAt: string | null
endAt: string | null
deadline: string | null
regions: string[]
cities: string[]
neighborhoods: string[]
locality: string | null
territoryNotes: string | null
coordinators: Array<{ id: number; name: string }>
responsible: { id: number; name: string; phone: string } | null
leadership: { id: number; label: string } | null
tasks: ActionPlanFormTaskViewModel[]
}
export const toActionPlanFormViewModel = (plan: ActionPlan): ActionPlanFormViewModel => ({
id: plan.id,
title: plan.title,
slug: plan.slug,
kind: plan.kind,
status: plan.status,
description: plan.description ?? null,
startAt: plan.startAt ?? null,
endAt: plan.endAt ?? null,
deadline: plan.deadline ?? null,
regions: asStringArray(plan.regions),
cities: asStringArray(plan.cities),
neighborhoods: asStringArray(plan.neighborhoods),
locality: plan.locality ?? null,
territoryNotes: plan.territoryNotes ?? null,
coordinators:
plan.coordinators
?.filter((coordinator): coordinator is CampaignUser =>
isPopulatedRelationship<CampaignUser>(coordinator),
)
.map(({ id, name }) => ({ id, name })) ?? [],
responsible: isPopulatedRelationship<Contact>(plan.responsible)
? { id: plan.responsible.id, name: plan.responsible.name, phone: plan.responsible.phone }
: null,
leadership: (() => {
const leadershipId = relationshipId(plan.leadership)
if (!leadershipId) return null
if (isPopulatedRelationship(plan.leadership)) {
const contact = plan.leadership.contact
const contactName = isPopulatedRelationship<Contact>(contact)
? contact.name
: `Liderança #${leadershipId}`
return { id: leadershipId, label: contactName }
}
return { id: leadershipId, label: `Liderança #${leadershipId}` }
})(),
tasks: (plan.tasks ?? []).map((task) => ({
id: task.id ?? null,
title: task.title,
responsible: isPopulatedRelationship<Contact>(task.responsible)
? { id: task.responsible.id, name: task.responsible.name, phone: task.responsible.phone }
: null,
due: task.due ?? null,
done: Boolean(task.done),
})),
})
export const actionPlanDetailSelect = {
title: true,
slug: true,
kind: true,
status: true,
description: true,
startAt: true,
endAt: true,
deadline: true,
regions: true,
cities: true,
neighborhoods: true,
locality: true,
territoryNotes: true,
coordinators: true,
responsible: true,
tasks: true,
updates: true,
createdBy: true,
updatedAt: true,
createdAt: true,
} as const
export type ActionPlanTaskViewModel = {
id: string | null
title: string
responsibleId: number | null
responsibleName: string | null
due: string | null
done: boolean
doneAt: string | null
}
export type ActionPlanUpdateViewModel = {
id: string | null
body: string
authorName: string | null
createdAt: string | null
}
export type ActionPlanDetailViewModel = {
id: number
title: string
slug: string
kind: ActionPlan['kind']
status: ActionPlan['status']
description: string | null
startAt: string | null
endAt: string | null
deadline: string | null
regions: string[]
cities: string[]
neighborhoods: string[]
locality: string | null
territoryNotes: string | null
territoryLabel: string
coordinators: Array<{ id: number; name: string }>
responsibleId: number | null
responsibleName: string | null
tasks: ActionPlanTaskViewModel[]
updates: ActionPlanUpdateViewModel[]
createdByName: string | null
updatedAt: string
createdAt: string
}
export const toActionPlanDetailViewModel = (plan: ActionPlan): ActionPlanDetailViewModel => ({
id: plan.id,
title: plan.title,
slug: plan.slug,
kind: plan.kind,
status: plan.status,
description: plan.description ?? null,
startAt: plan.startAt ?? null,
endAt: plan.endAt ?? null,
deadline: plan.deadline ?? null,
regions: asStringArray(plan.regions),
cities: asStringArray(plan.cities),
neighborhoods: asStringArray(plan.neighborhoods),
locality: plan.locality ?? null,
territoryNotes: plan.territoryNotes ?? null,
territoryLabel: formatActionPlanTerritoryLabel(plan),
coordinators:
plan.coordinators
?.filter((coordinator): coordinator is CampaignUser =>
isPopulatedRelationship<CampaignUser>(coordinator),
)
.map(({ id, name }) => ({ id, name })) ?? [],
responsibleId: relationshipId(plan.responsible),
responsibleName: relationshipName(plan.responsible),
tasks: (plan.tasks ?? []).map((task) => ({
id: task.id ?? null,
title: task.title,
responsibleId: relationshipId(task.responsible),
responsibleName: relationshipName(task.responsible),
due: task.due ?? null,
done: Boolean(task.done),
doneAt: task.doneAt ?? null,
})),
updates: (plan.updates ?? []).map((update) => ({
id: update.id ?? null,
body: update.body,
authorName: relationshipName(update.author),
createdAt: update.createdAt ?? null,
})),
createdByName: relationshipName(plan.createdBy),
updatedAt: plan.updatedAt,
createdAt: plan.createdAt,
})