import { Button, Divider, Group, Paper, Stack, Table, Text, Textarea, Title } from '@pikku/mantine/core'; import { CandidateLifecycleNotifications } from '@heygermany/sdk'; import dayjs from 'dayjs'; import { useI18n } from '@/context/i18n-provider'; import { asI18n } from '@pikku/react'; type LifecycleNotificationsCardProps = { lifecycleNotifications?: CandidateLifecycleNotifications | null analysisIssuesDraft: string onAnalysisIssuesDraftChange: (value: string) => void onSaveAnalysisIssues: () => void isSaving: boolean } type ReminderCategory = 'initial' | 'invalid' type ReminderType = 'day_1' | 'day_3' | 'day_6' type ReminderRow = { type: ReminderType sentAt: string | null mailgunMessageId: string | null errorMessage: string | null lastErrorAt: string | null } const REMINDER_TYPES: ReminderRow[] = [ { type: 'day_1', sentAt: null, mailgunMessageId: null, errorMessage: null, lastErrorAt: null }, { type: 'day_3', sentAt: null, mailgunMessageId: null, errorMessage: null, lastErrorAt: null }, { type: 'day_6', sentAt: null, mailgunMessageId: null, errorMessage: null, lastErrorAt: null }, ] const isPlainObject = (value: unknown): value is Record => typeof value === 'object' && value !== null && !Array.isArray(value) const normalizeLifecycleNotifications = (value: unknown): CandidateLifecycleNotifications | null => { if (isPlainObject(value)) { return value as CandidateLifecycleNotifications } if (typeof value !== 'string') { return null } try { const parsedValue = JSON.parse(value) return isPlainObject(parsedValue) ? parsedValue as CandidateLifecycleNotifications : null } catch { return null } } const getString = (value: unknown) => { if (typeof value !== 'string') { return null } const normalizedValue = value.trim() return normalizedValue.length > 0 ? normalizedValue : null } const normalizeMessage = (value: string | null) => { if (!value) { return null } const normalizedValue = value.replace(/\s+/g, ' ').trim() return normalizedValue.length > 0 ? normalizedValue : null } const displayValue = (value: string | null) => value || '-' const formatTimestamp = (value: string | null) => { if (!value) { return '-' } const parsedValue = dayjs(value) return parsedValue.isValid() ? parsedValue.format('YYYY-MM-DD HH:mm') : value } const getReminderKey = (category: ReminderCategory, type: ReminderType) => `${category}.${type}` const normalizeKey = (value: string) => value.toLowerCase().replace(/[_\-.]/g, '') const getReminderValue = ( lifecycleNotifications: CandidateLifecycleNotifications | null | undefined, category: ReminderCategory, type: ReminderType ) => { const directValue = lifecycleNotifications?.[getReminderKey(category, type)] if (directValue !== undefined) { return directValue } const normalizedReminderKey = normalizeKey(getReminderKey(category, type)) return Object.entries(lifecycleNotifications ?? {}).find(([key]) => ( normalizeKey(key) === normalizedReminderKey ))?.[1] } const getStringFromKeys = (record: Record, keys: string[]) => { for (const key of keys) { const value = getString(record[key]) if (value) { return value } } return null } const getReminderRows = ( lifecycleNotifications: CandidateLifecycleNotifications | null | undefined, category: ReminderCategory ) => REMINDER_TYPES.map((baseRow) => { const reminderValue = getReminderValue(lifecycleNotifications, category, baseRow.type) if (!isPlainObject(reminderValue)) { return baseRow } return { ...baseRow, sentAt: getStringFromKeys(reminderValue, ['sent_at', 'sentAt']), mailgunMessageId: getStringFromKeys(reminderValue, ['mailgun_message_id', 'mailgunMessageId']), errorMessage: normalizeMessage(getStringFromKeys(reminderValue, ['last_error', 'lastError'])), lastErrorAt: getStringFromKeys(reminderValue, ['last_error_at', 'lastErrorAt']), } }) const renderReminderSection = ( title: string, rows: ReminderRow[] ) => ( {asI18n(title)}
notification_type sent_at mailgun_message_id last_error last_error_at {rows.map((row) => ( {asI18n(row.type)} {asI18n(displayValue(row.sentAt) || '-')} {asI18n(displayValue(row.mailgunMessageId) || '-')} {asI18n(displayValue(row.errorMessage) || '-')} {asI18n(displayValue(row.lastErrorAt) || '-')} ))}
) export const LifecycleNotificationsCard: React.FunctionComponent = ({ lifecycleNotifications, analysisIssuesDraft, onAnalysisIssuesDraftChange, onSaveAnalysisIssues, isSaving, }) => { const t = useI18n() const normalizedLifecycleNotifications = normalizeLifecycleNotifications(lifecycleNotifications) const initialRows = getReminderRows(normalizedLifecycleNotifications, 'initial') const invalidRows = getReminderRows(normalizedLifecycleNotifications, 'invalid') const analysisUpdatedAt = isPlainObject(normalizedLifecycleNotifications?.analysis) ? getString(normalizedLifecycleNotifications.analysis.updated_at ?? normalizedLifecycleNotifications.analysis.updatedAt) : null return ( {t('backoffice.lifecycle.title' as any)} {t('backoffice.lifecycle.description' as any)} {renderReminderSection(t('backoffice.lifecycle.initial' as any), initialRows)} {renderReminderSection(t('backoffice.lifecycle.invalid' as any), invalidRows)} {t('backoffice.lifecycle.customissues' as any)} {analysisUpdatedAt ? ( {t('backoffice.lifecycle.updated' as any)} {asI18n(formatTimestamp(analysisUpdatedAt))} ) : null} {t('backoffice.lifecycle.helptext' as any)}