doxa-maps-cdn/app/pages/research/[slug]/index.vue

330 lines
13 KiB
Vue

<script setup lang="ts">
// Port of marketing-theme/template-uupg-detail.php.
// Fetches the people group by slug from
// `{prayBaseUrl}/api/people-groups/detail/{slug}?lang={locale}` and renders
// the detail page. Matches the PHP markup verbatim (classes, structure,
// copy, and icons).
import type { Uupg, ValueLabel } from '~/types/uupg'
interface PictureCredit {
link: string | null
text: string
}
interface UupgDetail extends Omit<Uupg, 'picture_credit'> {
picture_credit?: PictureCredit[] | null
engagement_status: ValueLabel
adopted_by_names?: string[]
people_committed: number
latitude?: string | number | null
longitude?: string | number | null
primary_language?: ValueLabel
imb_alternate_name?: string
imb_people_description?: string | null
imb_bible_available?: boolean
imb_bible_stories_available?: boolean
imb_jesus_film_available?: boolean
imb_radio_broadcast_available?: boolean
imb_gospel_recordings_available?: boolean
imb_audio_scripture_available?: boolean
workers_long_term?: boolean | null
work_in_local_language?: boolean | null
disciple_and_church_multiplication?: boolean | null
error?: string
}
const { t, locale } = useI18n()
const localePath = useLocalePath()
const route = useRoute()
const config = useRuntimeConfig()
const slug = computed(() => String(route.params.slug || ''))
useTextHighlight()
const { data: uupg, error: uupgError } = await useAsyncData<UupgDetail | null>(
`uupg-detail-${slug.value}-${locale.value}`,
async () => {
if (!slug.value) return null
const url = `${config.public.prayBaseUrl}/api/people-groups/detail/${encodeURIComponent(slug.value)}?lang=${locale.value}`
try {
return await $fetch<UupgDetail>(url)
} catch {
return { error: 'not_found' } as UupgDetail
}
}
)
const notFound = computed(() => !uupg.value || 'error' in (uupg.value as UupgDetail))
// Mirror the PHP template: build the pray URL with locale prefix and
// ?source=doxalife tracker.
const prayUrl = computed(() => {
const base = config.public.prayBaseUrl
const s = slug.value
const path = locale.value !== 'en' ? `/${locale.value}/${s}` : `/${s}`
return `${base}${path}?source=doxalife`
})
// Adoption URL: /adopt/{slug} via translationUrl
const adoptUrl = computed(() => {
const base = localePath('/adopt')
return `${base.endsWith('/') ? base : base + '/'}${slug.value}`
})
// Updates form on pray.doxa.life, keyed by people group slug.
const updatesUrl = computed(() => `${config.public.prayBaseUrl}/updates?id=${encodeURIComponent(slug.value)}`)
const infoOpen = ref(false)
const FULL_PRAYER_COVERAGE_COUNT = 100
type StatusIcon = 'done' | 'partial' | 'not-done'
type StatusItem = {
label: string
icon: StatusIcon
}
function getPrayerStatusIcon(peopleCommitted: number | null | undefined): StatusIcon {
const committed = Number(peopleCommitted ?? 0)
if (committed >= FULL_PRAYER_COVERAGE_COUNT) return 'done'
if (committed > 0) return 'partial'
return 'not-done'
}
function getBinaryStatusIcon(done: boolean): StatusIcon {
return done ? 'done' : 'not-done'
}
const isEngaged = computed(() => {
const status = (uupg.value as UupgDetail | null)?.engagement_status?.value
return String(status || '').toLowerCase() === 'engaged'
})
const statusItems = computed(() => {
const u = uupg.value as UupgDetail
const leadingItems: StatusItem[] = [
{ label: t('Prayer Status'), icon: getPrayerStatusIcon(u.people_committed) },
{ label: t('Adoption Status'), icon: getBinaryStatusIcon((u.adopted_by_churches ?? 0) > 0) }
]
if (isEngaged.value) {
return [
...leadingItems,
{ label: t('Engaged!'), icon: 'done' }
]
}
return [
...leadingItems,
{ label: t('Cross-cultural workers present'), icon: getBinaryStatusIcon(!!u.workers_long_term) },
{ label: t('Work in local language & culture'), icon: getBinaryStatusIcon(!!u.work_in_local_language) },
{ label: t('Disciple & church multiplication'), icon: getBinaryStatusIcon(!!u.disciple_and_church_multiplication) }
]
})
const progressItems = computed(() => {
const u = uupg.value as UupgDetail
return [
{ label: t('Bible Translation'), done: !!u.imb_bible_available },
{ label: t('Bible Stories'), done: !!u.imb_bible_stories_available },
{ label: t('Jesus Film'), done: !!u.imb_jesus_film_available },
{ label: t('Radio Broadcasts'), done: !!u.imb_radio_broadcast_available },
{ label: t('Gospel Recordings'), done: !!u.imb_gospel_recordings_available },
{ label: t('Audio Scripture'), done: !!u.imb_audio_scripture_available }
]
})
const prayerCoveragePercent = computed(() => {
const committed = (uupg.value as UupgDetail)?.people_committed ?? 0
return Math.min(100, (Number(committed) / FULL_PRAYER_COVERAGE_COUNT) * 100)
})
const mapSrc = computed(() => {
const u = uupg.value as UupgDetail
if (!u?.latitude || !u?.longitude) return ''
const lat = Number(u.latitude)
const lng = Number(u.longitude)
return `https://www.openstreetmap.org/export/embed.html?bbox=${lng - 10},${lat - 10},${lng + 10},${lat + 10}&layer=mapnik&marker=${lat},${lng}`
})
</script>
<template>
<div v-if="notFound || uupgError" class="container page-content uupg-detail-page">
<div class="stack stack--lg">
<h1>{{ t('People Group Not Found') }}</h1>
<p>{{ t('The people group you are looking for could not be found. Please try again.') }}</p>
<NuxtLink :to="localePath('/research')" class="button font-size-lg">
<span class="sr-only">{{ t('Back') }}</span>
<svg class="icon | rotate-270" viewBox="0 0 489.67 289.877">
<path d="M439.017,211.678L263.258,35.919c-3.9-3.9-8.635-6.454-13.63-7.665-9.539-2.376-20.051.161-27.509,7.619L46.361,211.632c-11.311,11.311-11.311,29.65,0,40.961h0c11.311,11.311,29.65,11.311,40.961,0L242.667,97.248l155.39,155.39c11.311,11.311,29.65,11.311,40.961,0h0c11.311-11.311,11.311-29.65,0-40.961Z" />
</svg>
{{ t('Back') }}
</NuxtLink>
</div>
</div>
<div v-else-if="uupg" class="container page-content uupg-detail-page">
<div class="stack stack--lg">
<div class="stack stack--2xl">
<div class="card switcher" padding-small>
<div class="center | grow-none">
<div class="position-relative">
<img class="uupg__image" data-size="medium" :src="uupg.image_url" :alt="uupg.name || 'People Group Photo'">
<template v-if="Array.isArray(uupg.picture_credit) && uupg.picture_credit.length">
<button
id="info-button"
class="info__button"
type="button"
aria-haspopup="dialog"
:aria-expanded="infoOpen ? 'true' : 'false'"
:data-state="infoOpen ? 'open' : 'closed'"
aria-label="Photo credit"
@click="infoOpen = !infoOpen"
>
<span class="info__icon" aria-hidden="true" />
<div class="info__content">
<template v-for="(credit, i) in uupg.picture_credit" :key="i">
<a
v-if="credit.link"
class="light-link"
:href="credit.link"
target="_blank"
>{{ credit.text }}</a>
<span v-else>{{ credit.text }}</span>
</template>
</div>
</button>
</template>
</div>
<div
class="engaged-stamp"
:data-engaged="isEngaged ? 'true' : 'false'"
>
<span v-if="isEngaged">{{ t('Engaged') }}</span>
<span v-else>{{ t('Not Engaged') }}</span>
</div>
</div>
<div class="stack stack--xs | uupg__header">
<h4 class="font-base font-weight-medium">{{ uupg.name }}</h4>
<p class="font-weight-medium font-size-lg">
{{ uupg.country_code?.label }} ({{ uupg.rop1?.label }})
</p>
<p>{{ uupg.imb_people_description ?? uupg.location_description }}</p>
<a
v-if="locale === 'en'"
:href="`/research/${slug}/resources/`"
class="button compact | resources-button"
>{{ t('View Resources') }}</a>
</div>
</div>
<div id="engagement-status" class="card stack stack--2xs" padding-small>
<h2 class="text-center">{{ t('Engagement Status') }}</h2>
<div class="cluster justify-center">
<div class="cluster justify-center align-start" data-width="md">
<div
v-for="(item, i) in statusItems"
:key="i"
class="status-item"
>
<img
v-if="item.icon === 'done'"
src="/assets/icons/Check-GreenCircle.png"
:alt="t('Done')"
>
<img
v-else-if="item.icon === 'partial'"
src="/assets/icons/Check-YellowCircle.png"
:alt="t('Has prayer coverage')"
>
<img
v-else
src="/assets/icons/RedX-Circle.png"
:alt="t('Not Done')"
>
<p>{{ item.label }}</p>
</div>
</div>
</div>
</div>
<div class="switcher" data-width="xl">
<div class="stack stack--xl | card | text-center lh-0" data-variant="secondary">
<h2>{{ t('Prayer Status') }}</h2>
<p class="font-size-4xl font-weight-medium">{{ uupg.people_committed }}</p>
<p class="font-size-lg">{{ t('People committed to praying') }}</p>
<div class="stack stack--sm">
<div class="progress-bar" data-size="md">
<div class="progress-bar__slider" :style="{ width: `${prayerCoveragePercent}%` }" />
</div>
<p class="font-size-lg font-weight-medium">{{ t('Daily Prayer Coverage') }}</p>
</div>
<a
class="button fit-content mx-auto stack-spacing-4xl clamp-padding"
:href="prayUrl"
>{{ t('Sign up to pray') }}</a>
</div>
<div class="stack stack--xl | card | text-center lh-0" data-variant="primary">
<h2>{{ t('Adoption Status') }}</h2>
<p class="font-size-4xl font-weight-medium">{{ uupg.adopted_by_churches || 0 }}</p>
<p class="font-size-lg margin-bottom-md">{{ t('churches / individuals have adopted this people group') }}</p>
<ul v-if="uupg.adopted_by_names && uupg.adopted_by_names.length">
<li v-for="(name, i) in uupg.adopted_by_names" :key="i">{{ name }}</li>
</ul>
<NuxtLink
class="button fit-content mx-auto mt-auto clamp-padding"
:to="adoptUrl"
>{{ t('Adopt people group') }}</NuxtLink>
</div>
</div>
<div v-if="uupg.latitude && uupg.longitude" class="map-card">
<iframe class="map" :src="mapSrc" loading="lazy" />
<div class="overlay" />
</div>
<div class="switcher" data-width="xl">
<div class="card" data-variant="primary">
<div class="stack">
<h2 class="color-primary">{{ t('Overview') }}</h2>
<p><strong>{{ t('Country') }}:</strong> {{ uupg.country_code?.label }}</p>
<p v-if="uupg.imb_alternate_name">
<strong>{{ t('Alternate Names') }}:</strong> {{ uupg.imb_alternate_name }}
</p>
<p><strong>{{ t('Population') }}:</strong> ~{{ uupg.population }}</p>
<p><strong>{{ t('Primary Language') }}:</strong> {{ uupg.primary_language?.label }}</p>
<p><strong>{{ t('Primary Religion') }}:</strong> {{ uupg.religion?.label }}</p>
<p><strong>{{ t('Religious Practices') }}:</strong> <br>{{ uupg.religion?.description }}</p>
</div>
</div>
<div class="stack | card" data-variant="primary">
<h2 class="color-primary">{{ t('Progress') }}</h2>
<p v-for="(item, i) in progressItems" :key="i" class="progress-item">
<img
v-if="item.done"
src="/assets/icons/Check-GreenCircle.png"
:alt="t('Done')"
>
<img
v-else
src="/assets/icons/RedX-Circle.png"
:alt="t('Not Done')"
>
<strong>{{ item.label }}:</strong> {{ item.done ? t('Yes') : t('No') }}
</p>
</div>
</div>
<a
class="button mx-auto"
:href="updatesUrl"
>{{ t('Submit Updates') }}</a>
</div>
</div>
</div>
</template>