// Unified approval queue store (Supabase-backed) import { supabase } from './supabase'; import type { ApprovalType as DbApprovalType, ApprovalStatus as DbApprovalStatus, } from './database.types'; export type ApprovalType = DbApprovalType; export type ApprovalStatus = DbApprovalStatus; // UI-facing camelCase interface (mapped from DB snake_case) export interface ApprovalItem { id: string; type: ApprovalType; status: ApprovalStatus; requesterId: string | null; requesterName: string; title: string; description: string | null; payload: Record; reviewerId?: string | null; reviewerName?: string | null; reviewedAt?: string | null; rejectReason?: string | null; createdAt: string; } export interface ApprovalFilters { type?: ApprovalType; status?: ApprovalStatus; requesterId?: string; } export interface ApprovalStats { pending: number; approved: number; rejected: number; byType: Record; } // --- Mapping helpers --- function dbToItem(row: Record): ApprovalItem { return { id: row.id as string, type: row.type as ApprovalType, status: row.status as ApprovalStatus, requesterId: (row.requester_id as string) ?? null, requesterName: row.requester_name as string, title: row.title as string, description: (row.description as string) ?? null, payload: (row.payload as Record) ?? {}, reviewerId: (row.reviewer_id as string) ?? null, reviewerName: (row.reviewer_name as string) ?? null, reviewedAt: (row.reviewed_at as string) ?? null, rejectReason: (row.reject_reason as string) ?? null, createdAt: row.created_at as string, }; } // --- CRUD --- export async function getApprovalQueue(filters?: ApprovalFilters): Promise { let query = supabase .from('approval_queue') .select('*') .order('created_at', { ascending: false }); if (filters?.type) query = query.eq('type', filters.type); if (filters?.status) query = query.eq('status', filters.status); if (filters?.requesterId) query = query.eq('requester_id', filters.requesterId); const { data, error } = await query; if (error) { console.error('getApprovalQueue error:', error); return []; } return (data ?? []).map(dbToItem); } export async function getApprovalById(id: string): Promise { const { data, error } = await supabase .from('approval_queue') .select('*') .eq('id', id) .maybeSingle(); if (error) { console.error('getApprovalById error:', error); return undefined; } return data ? dbToItem(data) : undefined; } export async function createApproval( item: Omit, ): Promise { const row = { type: item.type, status: item.status, requester_id: item.requesterId, requester_name: item.requesterName, title: item.title, description: item.description, payload: item.payload, reviewer_id: item.reviewerId ?? null, reviewer_name: item.reviewerName ?? null, reviewed_at: item.reviewedAt ?? null, reject_reason: item.rejectReason ?? null, }; const { data, error } = await supabase .from('approval_queue') .insert(row) .select() .single(); if (error) { console.error('createApproval error:', error); return null; } return dbToItem(data); } export async function updateApprovalStatus( id: string, status: ApprovalStatus, reviewerId?: string, reviewerName?: string, rejectReason?: string, ): Promise { const updates: Record = { status, reviewer_id: reviewerId ?? null, reviewer_name: reviewerName ?? null, reviewed_at: new Date().toISOString(), }; if (status === 'rejected' && rejectReason) { updates.reject_reason = rejectReason; } const { data, error } = await supabase .from('approval_queue') .update(updates) .eq('id', id) .select() .single(); if (error) { console.error('updateApprovalStatus error:', error); return null; } return dbToItem(data); } export async function getApprovalsByRequester(requesterId: string): Promise { return getApprovalQueue({ requesterId }); } export async function getApprovalStats(): Promise { const { data, error } = await supabase .from('approval_queue') .select('type, status'); if (error) { console.error('getApprovalStats error:', error); return { pending: 0, approved: 0, rejected: 0, byType: { campaign_review: 0, sme_application: 0, sme_selection: 0, trade_request: 0, point_conversion: 0, finance_registration: 0, }, }; } const items = (data ?? []) as { type: ApprovalType; status: ApprovalStatus }[]; const allTypes: ApprovalType[] = [ 'campaign_review', 'sme_application', 'sme_selection', 'trade_request', 'point_conversion', 'finance_registration', ]; const byType = Object.fromEntries( allTypes.map((t) => [t, items.filter((i) => i.type === t && i.status === 'pending').length]), ) as Record; return { pending: items.filter((i) => i.status === 'pending').length, approved: items.filter((i) => i.status === 'approved').length, rejected: items.filter((i) => i.status === 'rejected').length, byType, }; } // --- Type labels (한국어) --- export const approvalTypeLabels: Record = { campaign_review: '캠페인 승인', sme_application: 'SME 수혜 신청', sme_selection: 'SME 선정', trade_request: '거래 승인', point_conversion: '포인트 전환', finance_registration: '금융사 가입', }; export const approvalStatusLabels: Record = { pending: '대기', approved: '승인', rejected: '반려', cancelled: '취소', };