'use client'; import { useState, useEffect, useCallback } from 'react'; import { Trash2, Plus, Save } from 'lucide-react'; import { PageHeader } from '@/components/shared/page-header'; import { ConfirmationDialog } from '@/components/shared/confirmation-dialog'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Switch } from '@/components/ui/switch'; import { Textarea } from '@/components/ui/textarea'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Separator } from '@/components/ui/separator'; import { apiFetch } from '@/lib/api/client'; interface Setting { key: string; value: unknown; portId: string | null; updatedBy: string | null; updatedAt: string; } /** Well-known settings with their display metadata */ const KNOWN_SETTINGS: Array<{ key: string; label: string; description: string; type: 'boolean' | 'number' | 'json' | 'string'; defaultValue: unknown; }> = [ { key: 'client_portal_enabled', label: 'Client Portal', description: 'Allow clients of this port to sign in and manage their account through the client portal.', type: 'boolean', defaultValue: true, }, { key: 'ai_interest_scoring', label: 'AI Interest Scoring', description: 'Enable AI-powered interest scoring based on engagement signals', type: 'boolean', defaultValue: false, }, { key: 'ai_email_drafts', label: 'AI Email Drafts', description: 'Enable AI-assisted email draft generation', type: 'boolean', defaultValue: false, }, { key: 'invoice_net10_discount', label: 'Net-10 Invoice Discount (%)', description: 'Discount percentage applied when payment terms are net-10', type: 'number', defaultValue: 2, }, { key: 'pipeline_weights', label: 'Pipeline Stage Weights', description: 'Probability weights for revenue forecast by pipeline stage (JSON)', type: 'json', defaultValue: { open: 0.05, details_sent: 0.1, in_communication: 0.2, eoi_sent: 0.4, eoi_signed: 0.6, deposit_10pct: 0.75, contract_sent: 0.85, contract_signed: 0.95, completed: 1.0, }, }, { key: 'berth_rules', label: 'Berth Status Rules', description: 'Auto/suggest/off rules for berth status transitions (JSON)', type: 'json', defaultValue: [], }, { key: 'inquiry_contact_email', label: 'Inquiry Contact Email', description: 'Reply-to email shown in client confirmation emails when a new interest is registered', type: 'string', defaultValue: 'sales@portnimara.com', }, { key: 'inquiry_notification_recipients', label: 'External Notification Recipients', description: 'Additional email addresses that receive sales notifications for new interests (JSON array)', type: 'json', defaultValue: [], }, { key: 'residential_notification_recipients', label: 'Residential Notification Recipients', description: 'Email addresses (JSON array) that receive sales alerts for new residential inquiries. Falls back to Inquiry Contact Email when empty.', type: 'json', defaultValue: [], }, { key: 'eoi_signers', label: 'EOI Signers', description: 'Internal staff who countersign every EOI. JSON object with `developer` (signs after the client) and `approver` (final approval). Both fields take `{ name, email }`.', type: 'json', defaultValue: { developer: { name: 'David Mizrahi', email: 'dm@portnimara.com' }, approver: { name: 'Abbie May', email: 'sales@portnimara.com' }, }, }, ]; export function SettingsManager() { const [portSettings, setPortSettings] = useState([]); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(null); const [values, setValues] = useState>({}); const [customKey, setCustomKey] = useState(''); const [customValue, setCustomValue] = useState(''); const fetchSettings = useCallback(async () => { setLoading(true); try { const res = await apiFetch<{ data: { portSettings: Setting[]; globalSettings: Setting[] } }>( '/api/v1/admin/settings', ); setPortSettings(res.data.portSettings); // Build values map from existing settings const vals: Record = {}; for (const s of res.data.portSettings) { vals[s.key] = s.value; } setValues(vals); } finally { setLoading(false); } }, []); useEffect(() => { void fetchSettings(); }, [fetchSettings]); async function saveSetting(key: string, value: unknown) { setSaving(key); try { await apiFetch('/api/v1/admin/settings', { method: 'PUT', body: { key, value }, }); await fetchSettings(); } finally { setSaving(null); } } async function handleDeleteSetting(key: string) { await apiFetch('/api/v1/admin/settings', { method: 'DELETE', body: { key }, }); await fetchSettings(); } async function handleAddCustom() { if (!customKey.trim()) return; let parsed: unknown; try { parsed = JSON.parse(customValue); } catch { parsed = customValue; } await saveSetting(customKey, parsed); setCustomKey(''); setCustomValue(''); } function getEffectiveValue(key: string, defaultValue: unknown): unknown { return values[key] ?? defaultValue; } if (loading) { return (
Loading...
); } // Custom settings = port settings that aren't in KNOWN_SETTINGS const knownKeys = new Set(KNOWN_SETTINGS.map((s) => s.key)); const customSettings = portSettings.filter((s) => !knownKeys.has(s.key)); return (
{/* Feature Flags */} Feature Flags Enable or disable optional features {KNOWN_SETTINGS.filter((s) => s.type === 'boolean').map((setting) => (

{setting.description}

saveSetting(setting.key, checked)} />
))}
{/* String Settings */} {KNOWN_SETTINGS.some((s) => s.type === 'string') && ( Inquiry Settings Configure inquiry notification behavior {KNOWN_SETTINGS.filter((s) => s.type === 'string').map((setting) => (

{setting.description}

setValues((prev) => ({ ...prev, [setting.key]: e.target.value, })) } />
))}
)} {/* Numeric Settings */} Business Rules Configure financial and operational parameters {KNOWN_SETTINGS.filter((s) => s.type === 'number').map((setting) => (

{setting.description}

setValues((prev) => ({ ...prev, [setting.key]: parseFloat(e.target.value) || 0, })) } />
))}
{/* JSON Settings */} Advanced Configuration JSON-based settings for pipeline weights and berth rules {KNOWN_SETTINGS.filter((s) => s.type === 'json').map((setting) => { const currentValue = getEffectiveValue(setting.key, setting.defaultValue); const jsonStr = values[`${setting.key}_edit`] !== undefined ? String(values[`${setting.key}_edit`]) : JSON.stringify(currentValue, null, 2); return (

{setting.description}