Add church-registered flag + fix country search
- Pastors: churchRegistered boolean; the registration code field now shows only when the church is registered (admin condition), and is cleared when the flag is turned off - Frontend modal: "Iglesia registrada" toggle reveals a conditional "Código de registro" input; two new toggleable table columns - Search: country is stored as an ISO code, so a country typed by name (e.g. "venezuela", accent-insensitive) now resolves to its code(s) and matches Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
efeee14752
commit
ba08fc087b
|
|
@ -18,6 +18,8 @@ export type PastorRow = {
|
|||
countryName: string | null
|
||||
countryFlag: string
|
||||
impactedPeople: number | null
|
||||
churchRegistered: boolean
|
||||
registrationNumber: string | null
|
||||
inTelegram: boolean
|
||||
notes: string | null
|
||||
letterUrl: string | null
|
||||
|
|
@ -36,6 +38,8 @@ type ColumnKey =
|
|||
| 'state'
|
||||
| 'country'
|
||||
| 'impactedPeople'
|
||||
| 'churchRegistered'
|
||||
| 'registrationNumber'
|
||||
| 'inTelegram'
|
||||
| 'letter'
|
||||
|
||||
|
|
@ -122,6 +126,22 @@ export function PastorsTable({ rows }: { rows: PastorRow[] }) {
|
|||
cell: (p) =>
|
||||
p.impactedPeople === null ? dash : p.impactedPeople.toLocaleString('es'),
|
||||
},
|
||||
{
|
||||
key: 'churchRegistered',
|
||||
label: 'Iglesia registrada',
|
||||
align: 'center',
|
||||
cell: (p) =>
|
||||
p.churchRegistered ? (
|
||||
<span className="badge badge-success badge-sm">Sí</span>
|
||||
) : (
|
||||
<span className="badge badge-ghost badge-sm">No</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'registrationNumber',
|
||||
label: 'Código de registro',
|
||||
cell: (p) => <span className="whitespace-nowrap">{p.registrationNumber || dash}</span>,
|
||||
},
|
||||
{
|
||||
key: 'inTelegram',
|
||||
label: 'Telegram',
|
||||
|
|
@ -343,6 +363,8 @@ function PastorModal({ pastor, onClose }: { pastor: PastorRow | null; onClose: (
|
|||
const [impactedPeople, setImpactedPeople] = useState(
|
||||
pastor?.impactedPeople != null ? String(pastor.impactedPeople) : '',
|
||||
)
|
||||
const [churchRegistered, setChurchRegistered] = useState(pastor?.churchRegistered ?? false)
|
||||
const [registrationNumber, setRegistrationNumber] = useState(pastor?.registrationNumber ?? '')
|
||||
const [inTelegram, setInTelegram] = useState(pastor?.inTelegram ?? false)
|
||||
const [notes, setNotes] = useState(pastor?.notes ?? '')
|
||||
|
||||
|
|
@ -365,6 +387,8 @@ function PastorModal({ pastor, onClose }: { pastor: PastorRow | null; onClose: (
|
|||
state,
|
||||
country,
|
||||
impactedPeople: impactedPeople.trim() === '' ? null : Number(impactedPeople),
|
||||
churchRegistered,
|
||||
registrationNumber: churchRegistered ? registrationNumber : null,
|
||||
inTelegram,
|
||||
notes,
|
||||
}
|
||||
|
|
@ -510,6 +534,32 @@ function PastorModal({ pastor, onClose }: { pastor: PastorRow | null; onClose: (
|
|||
onChange={(e) => setImpactedPeople(e.target.value)}
|
||||
/>
|
||||
|
||||
<span className="text-sm font-medium sm:text-right sm:pr-1">Iglesia registrada</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
className="toggle toggle-success"
|
||||
checked={churchRegistered}
|
||||
onChange={(e) => setChurchRegistered(e.target.checked)}
|
||||
/>
|
||||
|
||||
{churchRegistered && (
|
||||
<>
|
||||
<label
|
||||
htmlFor="f-regcode"
|
||||
className="text-sm font-medium sm:text-right sm:pr-1"
|
||||
>
|
||||
Código de registro
|
||||
</label>
|
||||
<input
|
||||
id="f-regcode"
|
||||
className="input input-bordered w-full"
|
||||
value={registrationNumber}
|
||||
placeholder="Código oficial de registro"
|
||||
onChange={(e) => setRegistrationNumber(e.target.value)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<span className="text-sm font-medium sm:text-right sm:pr-1">En Telegram</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ export type PastorUpdateInput = {
|
|||
state?: string | null
|
||||
country?: string | null
|
||||
impactedPeople?: number | null
|
||||
churchRegistered?: boolean
|
||||
registrationNumber?: string | null
|
||||
inTelegram?: boolean
|
||||
notes?: string | null
|
||||
}
|
||||
|
|
@ -42,6 +44,9 @@ export async function createPastor(data: PastorUpdateInput) {
|
|||
data.impactedPeople === null || data.impactedPeople === undefined
|
||||
? undefined
|
||||
: Number(data.impactedPeople),
|
||||
churchRegistered: Boolean(data.churchRegistered),
|
||||
// Clear the code when the church isn't marked as registered.
|
||||
registrationNumber: data.churchRegistered ? data.registrationNumber || undefined : null,
|
||||
inTelegram: Boolean(data.inTelegram),
|
||||
notes: data.notes || undefined,
|
||||
},
|
||||
|
|
@ -71,6 +76,9 @@ export async function updatePastor(id: number, data: PastorUpdateInput) {
|
|||
data.impactedPeople === null || data.impactedPeople === undefined
|
||||
? undefined
|
||||
: Number(data.impactedPeople),
|
||||
churchRegistered: Boolean(data.churchRegistered),
|
||||
// Clear the code when the church isn't marked as registered.
|
||||
registrationNumber: data.churchRegistered ? data.registrationNumber || undefined : null,
|
||||
inTelegram: Boolean(data.inTelegram),
|
||||
notes: data.notes || undefined,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -30,6 +30,32 @@ const flag = (code?: string | null): string => {
|
|||
return String.fromCodePoint(base + (cc.charCodeAt(0) - 65), base + (cc.charCodeAt(1) - 65))
|
||||
}
|
||||
|
||||
// Accent/case-insensitive normalizer for search matching ("méxico" ~ "mexico").
|
||||
const normalize = (s: string): string =>
|
||||
s
|
||||
.normalize('NFD')
|
||||
.replace(/\p{Diacritic}/gu, '')
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
|
||||
// All ISO 3166-1 alpha-2 codes, so a country typed by name resolves to its code
|
||||
// (country is stored as the ISO code, e.g. "MX" for México).
|
||||
// prettier-ignore
|
||||
const ALPHA2 = ['AD','AE','AF','AG','AI','AL','AM','AO','AQ','AR','AS','AT','AU','AW','AX','AZ','BA','BB','BD','BE','BF','BG','BH','BI','BJ','BL','BM','BN','BO','BQ','BR','BS','BT','BV','BW','BY','BZ','CA','CC','CD','CF','CG','CH','CI','CK','CL','CM','CN','CO','CR','CU','CV','CW','CX','CY','CZ','DE','DJ','DK','DM','DO','DZ','EC','EE','EG','EH','ER','ES','ET','FI','FJ','FK','FM','FO','FR','GA','GB','GD','GE','GF','GG','GH','GI','GL','GM','GN','GP','GQ','GR','GS','GT','GU','GW','GY','HK','HM','HN','HR','HT','HU','ID','IE','IL','IM','IN','IO','IQ','IR','IS','IT','JE','JM','JO','JP','KE','KG','KH','KI','KM','KN','KP','KR','KW','KY','KZ','LA','LB','LC','LI','LK','LR','LS','LT','LU','LV','LY','MA','MC','MD','ME','MF','MG','MH','MK','ML','MM','MN','MO','MP','MQ','MR','MS','MT','MU','MV','MW','MX','MY','MZ','NA','NC','NE','NF','NG','NI','NL','NO','NP','NR','NU','NZ','OM','PA','PE','PF','PG','PH','PK','PL','PM','PN','PR','PS','PT','PW','PY','QA','RE','RO','RS','RU','RW','SA','SB','SC','SD','SE','SG','SH','SI','SJ','SK','SL','SM','SN','SO','SR','SS','ST','SV','SX','SY','SZ','TC','TD','TF','TG','TH','TJ','TK','TL','TM','TN','TO','TR','TT','TV','TW','TZ','UA','UG','UM','US','UY','UZ','VA','VC','VE','VG','VI','VN','VU','WF','WS','YE','YT','ZA','ZM','ZW']
|
||||
|
||||
// Precomputed [normalizedName, code] pairs in the display locale.
|
||||
const COUNTRY_NAME_INDEX: [string, string][] = ALPHA2.map((code) => [
|
||||
normalize(localizedCountry(code) ?? code),
|
||||
code,
|
||||
])
|
||||
|
||||
// Country codes whose localized name contains the search term.
|
||||
const countryCodesMatching = (term: string): string[] => {
|
||||
const t = normalize(term)
|
||||
if (!t) return []
|
||||
return COUNTRY_NAME_INDEX.filter(([name]) => name.includes(t)).map(([, code]) => code)
|
||||
}
|
||||
|
||||
export default async function HomePage({
|
||||
searchParams,
|
||||
}: {
|
||||
|
|
@ -41,6 +67,9 @@ export default async function HomePage({
|
|||
|
||||
const payload = await getPayload({ config: await config })
|
||||
|
||||
// Country is stored as an ISO code, so resolve any country *name* in the
|
||||
// search term to its code(s) and match those too.
|
||||
const countryCodes = countryCodesMatching(search)
|
||||
const where: Where = search
|
||||
? {
|
||||
or: [
|
||||
|
|
@ -48,6 +77,7 @@ export default async function HomePage({
|
|||
{ email: { like: search } },
|
||||
{ city: { like: search } },
|
||||
{ country: { like: search } },
|
||||
...(countryCodes.length ? [{ country: { in: countryCodes } }] : []),
|
||||
],
|
||||
}
|
||||
: {}
|
||||
|
|
@ -87,6 +117,8 @@ export default async function HomePage({
|
|||
countryName: localizedCountry(p.country),
|
||||
countryFlag: flag(p.country),
|
||||
impactedPeople: p.impactedPeople ?? null,
|
||||
churchRegistered: Boolean(p.churchRegistered),
|
||||
registrationNumber: p.registrationNumber ?? null,
|
||||
inTelegram: Boolean(p.inTelegram),
|
||||
notes: p.notes ?? null,
|
||||
letterUrl: letter?.url ?? null,
|
||||
|
|
|
|||
|
|
@ -45,10 +45,21 @@ export const Pastors: CollectionConfig = {
|
|||
type: 'number',
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: 'churchRegistered',
|
||||
label: 'Church Registered',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
},
|
||||
{
|
||||
name: 'registrationNumber',
|
||||
label: 'Registration Number',
|
||||
label: 'Registration Code',
|
||||
type: 'text',
|
||||
admin: {
|
||||
// Only relevant when the church is registered.
|
||||
condition: (data) => Boolean(data?.churchRegistered),
|
||||
description: 'Official registration code for the church.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'inTelegram',
|
||||
|
|
|
|||
|
|
@ -232,6 +232,10 @@ export interface Pastor {
|
|||
*/
|
||||
country?: string | null;
|
||||
impactedPeople?: number | null;
|
||||
churchRegistered?: boolean | null;
|
||||
/**
|
||||
* Official registration code for the church.
|
||||
*/
|
||||
registrationNumber?: string | null;
|
||||
inTelegram?: boolean | null;
|
||||
/**
|
||||
|
|
@ -434,6 +438,7 @@ export interface PastorsSelect<T extends boolean = true> {
|
|||
state?: T;
|
||||
country?: T;
|
||||
impactedPeople?: T;
|
||||
churchRegistered?: T;
|
||||
registrationNumber?: T;
|
||||
inTelegram?: T;
|
||||
telephone?: T;
|
||||
|
|
|
|||
Loading…
Reference in New Issue