Skip to content

Commit e2275b0

Browse files
authored
fix: align country dashboard actions (#407)
1 parent 46e63c8 commit e2275b0

8 files changed

Lines changed: 144 additions & 271 deletions

File tree

Lines changed: 40 additions & 260 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,32 @@
11
<script setup lang="ts">
2-
import { Loader2, RefreshCw } from 'lucide-vue-next'
32
import { useSettingsError } from '~/composables/settings/useSettingsError'
4-
import { useSettingsTaskOverview } from '~/composables/settings/useSettingsTaskOverview'
53
import type { CountrySourceSetting, CountrySourceSettings } from '~/server/utils/country-source-settings'
64
7-
interface EnrichRunResult {
8-
crawled: number
9-
todo: number
10-
archived: number
11-
enriched: number
12-
photoExtractions: number
13-
photosTotal: number
14-
durationMs: number
15-
warning?: string | null
16-
followUpTasksStarted?: boolean
17-
}
18-
195
const { t } = useI18n()
206
const countryLabel = useCountryLabel()
217
const { normalizeSettingsError } = useSettingsError()
22-
const {
23-
llmBatchJobs,
24-
llmBatchJobsPending,
25-
llmBatchJobsError,
26-
formatBatchDate,
27-
loadLlmBatchJobs,
28-
startProgressPolling,
29-
stopProgressPolling,
30-
} = useSettingsTaskOverview()
8+
const emit = defineEmits<{ countries: [codes: string[]] }>()
319
3210
const countrySources = ref<CountrySourceSetting[]>([])
3311
const countrySourcesPending = ref(false)
3412
const countrySourcesError = ref<string | null>(null)
3513
const countrySourcesSaved = ref(false)
3614
const countrySourcesLoaded = ref(false)
37-
const countryEnrichPending = ref<string | null>(null)
38-
const countryEnrichError = ref<string | null>(null)
39-
const countryEnrichResult = ref<EnrichRunResult & { country: string } | null>(null)
40-
const forceCountryExtraction = ref(false)
4115
const enabledCountrySourceCount = computed(
4216
() => countrySources.value.filter((source) => source.enabled).length,
4317
)
4418
19+
function publishEnabledCountries(): void {
20+
emit('countries', countrySources.value.filter((source) => source.enabled).map((source) => source.code))
21+
}
22+
4523
async function loadCountrySources(): Promise<void> {
4624
try {
4725
const res = await $fetch<CountrySourceSettings>('/api/settings/countries')
4826
countrySources.value = res.countries
4927
countrySourcesLoaded.value = true
5028
countrySourcesError.value = null
29+
publishEnabledCountries()
5130
} catch (err) {
5231
countrySourcesLoaded.value = false
5332
countrySourcesError.value = normalizeSettingsError(err, t('settings.sources.loadError'))
@@ -76,248 +55,49 @@ async function saveCountrySources(): Promise<void> {
7655
})
7756
countrySources.value = res.countries
7857
countrySourcesSaved.value = true
58+
publishEnabledCountries()
7959
} catch (err) {
8060
countrySourcesError.value = normalizeSettingsError(err, t('settings.sources.saveError'))
8161
} finally {
8262
countrySourcesPending.value = false
8363
}
8464
}
8565
86-
async function enrichCountrySource(source: CountrySourceSetting): Promise<void> {
87-
if (!source.enabled || countryEnrichPending.value) return
88-
89-
countryEnrichPending.value = source.code
90-
countryEnrichError.value = null
91-
countryEnrichResult.value = null
92-
startProgressPolling()
93-
try {
94-
const res = await $fetch<{ result: EnrichRunResult }>(
95-
`/api/settings/countries/${source.code}/enrich`,
96-
{
97-
method: 'POST',
98-
body: { forceExtraction: forceCountryExtraction.value },
99-
},
100-
)
101-
countryEnrichResult.value = { ...res.result, country: source.code }
102-
await loadLlmBatchJobs()
103-
} catch (err) {
104-
countryEnrichError.value = normalizeSettingsError(err, t('settings.sources.enrichError'))
105-
} finally {
106-
countryEnrichPending.value = null
107-
}
108-
}
109-
110-
onMounted(async () => {
111-
await Promise.all([loadCountrySources(), loadLlmBatchJobs()])
112-
})
113-
onBeforeUnmount(stopProgressPolling)
66+
onMounted(loadCountrySources)
11467
</script>
11568

11669
<template>
117-
<Card>
118-
<CardHeader>
119-
<CardTitle>{{ $t('settings.sources.title') }}</CardTitle>
120-
<CardAction>
121-
<Button
122-
type="button"
123-
variant="ghost"
124-
size="icon-sm"
125-
:disabled="llmBatchJobsPending"
126-
:title="$t('settings.sources.refreshOverview')"
127-
@click="loadLlmBatchJobs"
128-
>
129-
<Loader2 v-if="llmBatchJobsPending" class="h-4 w-4 animate-spin" />
130-
<RefreshCw v-else class="h-4 w-4" />
131-
</Button>
132-
</CardAction>
133-
</CardHeader>
134-
<CardContent class="space-y-4">
135-
<p class="text-sm text-muted-foreground">
136-
{{ $t('settings.sources.description') }}
137-
</p>
138-
139-
<p v-if="countrySourcesError" class="text-sm text-destructive">{{ countrySourcesError }}</p>
140-
<p v-if="countryEnrichError" class="text-sm text-destructive">{{ countryEnrichError }}</p>
141-
<p v-if="llmBatchJobsError" class="text-sm text-destructive">{{ llmBatchJobsError }}</p>
142-
<p v-if="countrySourcesSaved" class="text-sm text-emerald-600 dark:text-emerald-500">{{ $t('settings.sources.saved') }}</p>
143-
<p v-if="countryEnrichResult" class="text-sm text-emerald-600 dark:text-emerald-500">
144-
{{ $t('settings.sources.enrichDone', {
145-
country: countryLabel(countryEnrichResult.country),
146-
archived: countryEnrichResult.archived,
147-
crawled: countryEnrichResult.crawled,
148-
photos: countryEnrichResult.photosTotal,
149-
duration: Math.round(countryEnrichResult.durationMs / 1000),
150-
}) }}
151-
</p>
152-
<SettingsMessageDetails
153-
v-if="countryEnrichResult?.warning"
154-
:text="countryEnrichResult.warning"
155-
variant="warning"
156-
role="alert"
157-
/>
158-
<p v-if="countryEnrichResult?.followUpTasksStarted" class="text-sm text-muted-foreground">
159-
{{ $t('settings.sources.followUpTasksStarted') }}
160-
</p>
161-
162-
<div v-if="llmBatchJobs?.enrichStatus" class="text-sm space-y-1">
163-
<p v-if="llmBatchJobs.enrichStatus.status === 'running'">
164-
{{ $t('settings.sources.enrichStatusRunning', { at: formatBatchDate(llmBatchJobs.enrichStatus.startedAt) }) }}
165-
</p>
166-
<p v-else-if="llmBatchJobs.enrichStatus.finishedAt" class="text-muted-foreground">
167-
{{ $t('settings.sources.enrichStatusLastRun', {
168-
at: formatBatchDate(llmBatchJobs.enrichStatus.finishedAt),
169-
archived: llmBatchJobs.enrichStatus.lastResult?.archived ?? 0,
170-
duration: Math.round((llmBatchJobs.enrichStatus.lastResult?.durationMs ?? 0) / 1000),
171-
}) }}
172-
</p>
173-
<SettingsMessageDetails
174-
v-if="llmBatchJobs.enrichStatus.lastError"
175-
:text="$t('settings.sources.enrichStatusLastError', { message: llmBatchJobs.enrichStatus.lastError })"
176-
variant="error"
177-
/>
178-
<SettingsMessageDetails
179-
v-if="llmBatchJobs.enrichStatus.lastWarning"
180-
:text="llmBatchJobs.enrichStatus.lastWarning"
181-
variant="warning"
182-
/>
183-
</div>
184-
185-
<div v-if="llmBatchJobs?.reprocessStatus" class="text-sm space-y-1">
186-
<p v-if="llmBatchJobs.reprocessStatus.status === 'running'">
187-
{{ $t('settings.sources.llmStatusRunning', { at: formatBatchDate(llmBatchJobs.reprocessStatus.startedAt) }) }}
188-
</p>
189-
<p v-else-if="llmBatchJobs.reprocessStatus.finishedAt" class="text-muted-foreground">
190-
{{ $t('settings.sources.llmStatusLastRun', {
191-
at: formatBatchDate(llmBatchJobs.reprocessStatus.finishedAt),
192-
processed: llmBatchJobs.reprocessStatus.lastResult?.processed ?? 0,
193-
llmCalls: llmBatchJobs.reprocessStatus.lastResult?.llmCalls ?? 0,
194-
duration: Math.round((llmBatchJobs.reprocessStatus.lastResult?.durationMs ?? 0) / 1000),
195-
}) }}
196-
</p>
197-
<SettingsMessageDetails
198-
v-if="llmBatchJobs.reprocessStatus.lastWarning"
199-
:text="$t('settings.sources.llmStatusLastWarning', { message: llmBatchJobs.reprocessStatus.lastWarning })"
200-
variant="warning"
201-
/>
202-
<SettingsMessageDetails
203-
v-if="llmBatchJobs.reprocessStatus.lastError"
204-
:text="$t('settings.sources.llmStatusLastError', { message: llmBatchJobs.reprocessStatus.lastError })"
205-
variant="error"
206-
/>
207-
<p v-if="llmBatchJobs.reprocessStatus.lastResult?.llmErrors" class="text-destructive">
208-
{{ $t('settings.sources.llmStatusLlmErrors', { count: llmBatchJobs.reprocessStatus.lastResult.llmErrors }) }}
209-
</p>
210-
<SettingsMessageDetails
211-
v-if="llmBatchJobs.reprocessStatus.lastLlmError"
212-
:text="$t('settings.sources.llmStatusLastLlmError', { message: llmBatchJobs.reprocessStatus.lastLlmError })"
213-
variant="error"
214-
/>
215-
</div>
216-
217-
<div v-if="llmBatchJobs?.offloadImagesStatus?.finishedAt" class="text-sm space-y-1">
218-
<p class="text-muted-foreground">
219-
{{ $t('settings.sources.offloadStatusLastRun', {
220-
at: formatBatchDate(llmBatchJobs.offloadImagesStatus.finishedAt),
221-
removed: llmBatchJobs.offloadImagesStatus.lastResult?.removed ?? 0,
222-
freedMb: Math.round((llmBatchJobs.offloadImagesStatus.lastResult?.freedBytes ?? 0) / 1024 / 1024),
223-
}) }}
224-
</p>
225-
<SettingsMessageDetails
226-
v-if="llmBatchJobs.offloadImagesStatus.lastWarning"
227-
:text="llmBatchJobs.offloadImagesStatus.lastWarning"
228-
variant="warning"
229-
role="alert"
230-
/>
231-
<SettingsMessageDetails
232-
v-if="llmBatchJobs.offloadImagesStatus.lastError"
233-
:text="$t('settings.sources.offloadStatusLastError', { message: llmBatchJobs.offloadImagesStatus.lastError })"
234-
variant="error"
235-
role="alert"
236-
/>
70+
<section class="space-y-3">
71+
<div>
72+
<h3 class="text-sm font-semibold">{{ $t('settings.sources.title') }}</h3>
73+
<p class="mt-1 text-sm text-muted-foreground">{{ $t('settings.sources.description') }}</p>
74+
</div>
75+
<p v-if="countrySourcesError" class="text-sm text-destructive">{{ countrySourcesError }}</p>
76+
<p v-if="countrySourcesSaved" class="text-sm text-emerald-600 dark:text-emerald-500">{{ $t('settings.sources.saved') }}</p>
77+
<form class="space-y-3" @submit.prevent="saveCountrySources">
78+
<div class="max-h-80 overflow-y-auto rounded-md border divide-y">
79+
<SettingsCountryActionRow v-for="source in countrySources" :key="source.code">
80+
<template #leading>
81+
<Checkbox
82+
class="mt-0.5"
83+
:model-value="source.enabled"
84+
:disabled="countrySourcesPending"
85+
@update:model-value="toggleCountrySource(source.code)"
86+
/>
87+
</template>
88+
<button type="button" class="min-w-0 w-full text-left disabled:cursor-not-allowed disabled:opacity-60" :disabled="countrySourcesPending" @click="toggleCountrySource(source.code)">
89+
<span class="block text-sm font-medium">
90+
{{ countryLabel(source.code, source.name) }}
91+
<span class="ml-1 font-mono text-xs uppercase text-muted-foreground">{{ source.code }}</span>
92+
</span>
93+
<span class="block text-xs text-muted-foreground">{{ source.platforms.map((platform) => platform.name).join(', ') }}</span>
94+
</button>
95+
</SettingsCountryActionRow>
23796
</div>
238-
239-
<div v-if="llmBatchJobs?.externalEnrichmentStatus" class="text-sm space-y-1">
240-
<p v-if="llmBatchJobs.externalEnrichmentStatus.status === 'running'">
241-
{{ $t('settings.sources.externalStatusRunning', { at: formatBatchDate(llmBatchJobs.externalEnrichmentStatus.startedAt) }) }}
242-
</p>
243-
<p v-else-if="llmBatchJobs.externalEnrichmentStatus.finishedAt" class="text-muted-foreground">
244-
{{ $t('settings.sources.externalStatusLastRun', {
245-
at: formatBatchDate(llmBatchJobs.externalEnrichmentStatus.finishedAt),
246-
processed: llmBatchJobs.externalEnrichmentStatus.lastResult?.processed ?? 0,
247-
written: llmBatchJobs.externalEnrichmentStatus.lastResult?.written ?? 0,
248-
duration: Math.round((llmBatchJobs.externalEnrichmentStatus.lastResult?.durationMs ?? 0) / 1000),
249-
}) }}
250-
</p>
251-
<SettingsMessageDetails
252-
v-if="llmBatchJobs.externalEnrichmentStatus.lastWarning"
253-
:text="llmBatchJobs.externalEnrichmentStatus.lastWarning"
254-
variant="warning"
255-
role="alert"
256-
/>
257-
<SettingsMessageDetails
258-
v-if="llmBatchJobs.externalEnrichmentStatus.lastError"
259-
:text="$t('settings.sources.externalStatusLastError', { message: llmBatchJobs.externalEnrichmentStatus.lastError })"
260-
variant="error"
261-
role="alert"
262-
/>
263-
</div>
264-
265-
<form class="space-y-3" @submit.prevent="saveCountrySources">
266-
<label class="flex items-start gap-2 rounded-md border bg-muted/20 p-3 text-sm">
267-
<Checkbox v-model="forceCountryExtraction" class="mt-0.5" :disabled="countryEnrichPending !== null" />
268-
<span class="space-y-1">
269-
<span class="block font-medium">{{ $t('settings.sources.forceExtraction') }}</span>
270-
<span class="block text-xs text-muted-foreground">{{ $t('settings.sources.forceExtractionHelp') }}</span>
271-
</span>
272-
</label>
273-
274-
<div class="max-h-80 overflow-y-auto rounded-md border divide-y">
275-
<SettingsCountryActionRow v-for="source in countrySources" :key="source.code">
276-
<template #leading>
277-
<Checkbox
278-
class="mt-0.5"
279-
:model-value="source.enabled"
280-
:disabled="countrySourcesPending"
281-
@update:model-value="toggleCountrySource(source.code)"
282-
/>
283-
</template>
284-
<button type="button" class="min-w-0 w-full text-left disabled:cursor-not-allowed disabled:opacity-60" :disabled="countrySourcesPending" @click="toggleCountrySource(source.code)">
285-
<span class="block text-sm font-medium">
286-
{{ countryLabel(source.code, source.name) }}
287-
<span class="ml-1 font-mono text-xs uppercase text-muted-foreground">{{ source.code }}</span>
288-
</span>
289-
<span class="block text-xs text-muted-foreground">
290-
{{ source.platforms.map((platform) => platform.name).join(', ') }}
291-
</span>
292-
</button>
293-
<template #action>
294-
<Button
295-
type="button"
296-
variant="outline"
297-
size="sm"
298-
:title="$t('settings.sources.enrichTitle')"
299-
:disabled="!source.enabled || countrySourcesPending || countryEnrichPending !== null"
300-
@click="enrichCountrySource(source)"
301-
>
302-
<Loader2 v-if="countryEnrichPending === source.code" class="h-4 w-4 animate-spin" />
303-
<RefreshCw v-else class="h-4 w-4" />
304-
{{ countryEnrichPending === source.code ? $t('settings.sources.enriching') : $t('settings.sources.enrich') }}
305-
</Button>
306-
</template>
307-
</SettingsCountryActionRow>
308-
</div>
309-
310-
<p class="text-xs text-muted-foreground">
311-
{{ $t('settings.sources.enabledCount', {
312-
enabled: enabledCountrySourceCount,
313-
total: countrySources.length,
314-
}) }}
315-
</p>
316-
317-
<Button type="submit" :disabled="countrySourcesPending || !countrySourcesLoaded">
318-
{{ countrySourcesPending ? $t('settings.sources.saving') : $t('settings.sources.save') }}
319-
</Button>
320-
</form>
321-
</CardContent>
322-
</Card>
97+
<p class="text-xs text-muted-foreground">{{ $t('settings.sources.enabledCount', { enabled: enabledCountrySourceCount, total: countrySources.length }) }}</p>
98+
<Button type="submit" :disabled="countrySourcesPending || !countrySourcesLoaded">
99+
{{ countrySourcesPending ? $t('settings.sources.saving') : $t('settings.sources.save') }}
100+
</Button>
101+
</form>
102+
</section>
323103
</template>

0 commit comments

Comments
 (0)