import { useEffect, useMemo, useState } from 'react'; import { useSearchParams } from 'react-router'; import { Controller } from 'react-hook-form'; import { Button, Dropdown, Form } from 'react-bootstrap'; import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from '@dnd-kit/core'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { signedGet, isSuccess } from '@/api/client'; const DEFAULT_HIERARCHY_LEVELS = ['Country', 'Zone', 'Region', 'Area', 'Territory']; const MANAGER_LEVELS = [ ['l1', 'L1'], ['l2', 'L2'], ['l3', 'L3'], ]; const MANAGER_KEYS = [ ['name', 'Name'], ['email', 'Email'], ['employee_code', 'Employee Code'], ['position_name', 'Position Name'], ]; function emptyDesigner() { return { fields: [] }; } function asDesigner(raw) { let value = raw; if (typeof raw === 'string' && raw.trim()) { try { value = JSON.parse(raw); } catch { value = null; } } const fields = Array.isArray(value?.fields) ? value.fields : []; return { fields: fields .filter((row) => row && row.id && row.source && row.key) .map((row) => ({ id: String(row.id), source: String(row.source), key: String(row.key), label: row.label || String(row.key), level: row.level || undefined, show_in_export: row.show_in_export !== false, show_in_view: Boolean(row.show_in_view), })), }; } export function normalizeDataDesigner(raw) { return asDesigner(raw); } const SOURCE_LABELS = { user: 'User attributes', hierarchy: 'Hierarchy', manager: 'Manager info', question: 'Form questions', manual: 'Manual', }; function managerStoredId(level, key) { const number = String(level || '').replace('l', ''); const suffix = key === 'employee_code' ? 'code' : key === 'position_name' ? 'position' : key; return `manager${number}_${suffix}`; } function option(id, source, key, label, extra = {}) { return { value: id, label, row: { id, source, key, label, show_in_export: true, show_in_view: false, ...extra } }; } function CheckDropdown({ title, count, options, taken, onToggle, emptyText, disabled, disabledText }) { return ( {disabled ? disabledText : `${title} (${count})`} {options.length ? options.map((item) => ( onToggle(item, event.target.checked)} /> )) :
{emptyText}
}
); } function DesignerRow({ row, onPatch, onRemove }) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: row.id }); const style = { transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.6 : 1, }; return (
{row.label}
{SOURCE_LABELS[row.source] || row.source}
onPatch(row.id, { show_in_export: event.target.checked })} /> onPatch(row.id, { show_in_view: event.target.checked })} />
); } function DataDesignerEditor({ value, onChange, userAttributes = {} }) { const [searchParams] = useSearchParams(); const formId = searchParams.get('id') || ''; const [open, setOpen] = useState(false); const [questions, setQuestions] = useState([]); const [hierarchyLevels, setHierarchyLevels] = useState([]); const [manualOpen, setManualOpen] = useState(false); const [manualKey, setManualKey] = useState(''); const [manualLabel, setManualLabel] = useState(''); const designer = asDesigner(value); const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } })); useEffect(() => { if (!formId) { setQuestions([]); return undefined; } let cancelled = false; signedGet('form', 'formquestion/get_all', { entity_id: formId }).then((response) => { if (cancelled || !isSuccess(response)) return; const items = response.data?.items || []; setQuestions(Array.isArray(items) ? items : Object.values(items)); }); return () => { cancelled = true; }; }, [formId]); useEffect(() => { let cancelled = false; signedGet('ums', 'domain/get_one').then((response) => { if (cancelled) return; const types = isSuccess(response) ? response.data?.item?.territory_types : []; const levels = Array.isArray(types) ? types.map((type) => String(type || '').trim()).filter(Boolean) : []; setHierarchyLevels(levels.length ? levels : DEFAULT_HIERARCHY_LEVELS); }); return () => { cancelled = true; }; }, []); const taken = useMemo(() => new Set(designer.fields.map((row) => row.id)), [designer.fields]); const userOptions = useMemo(() => { const fixed = [ ['name', 'Name'], ['email', 'Email'], ['employee_code', 'Employee Code'], ]; const attributes = Object.entries(userAttributes); const labelKey = (label) => String(label || '').trim().toLowerCase(); const usedCodes = new Set(); const usedLabels = new Set(); const rows = []; const addRow = (key, label) => { const normalized = labelKey(label); if (!key || usedCodes.has(key) || usedLabels.has(normalized)) return; usedCodes.add(key); usedLabels.add(normalized); rows.push(option(key, 'user', key, label)); }; fixed.forEach(([key, label]) => { const match = attributes.find(([code, attrLabel]) => code === key || labelKey(attrLabel) === labelKey(label)); if (match) addRow(match[0], match[1]); else addRow(key, label); }); attributes.forEach(([code, label]) => addRow(code, label)); return rows; }, [userAttributes]); const hierarchyOptions = hierarchyLevels.map((level) => option(String(level).trim().toLowerCase(), 'hierarchy', level, level)); const managerOptions = MANAGER_LEVELS.flatMap(([level, title]) => MANAGER_KEYS.map(([key, name]) => option(managerStoredId(level, key), 'manager', key, `${title} Manager ${name}`, { level }), ), ); const questionOptions = [ ...questions .filter((question) => question?.id && question.status !== 'disabled') .map((question) => option(question.code || String(question.id), 'question', question.code || String(question.id), question.question || question.code || question.id), ), option('submission_id', 'question', 'submission_id', 'Submission ID'), option('rec_date', 'question', 'rec_date', 'Record Date'), option('created_date', 'question', 'created_date', 'Submitted Date'), option('updated_date', 'question', 'updated_date', 'Last Updated Date'), option('workflow_status', 'question', 'workflow_status', 'Workflow Status'), ]; const appendRows = (picked) => { const rows = (picked || []).map((item) => item.row).filter((row) => row && !taken.has(row.id)); if (!rows.length) return; onChange({ fields: [...designer.fields, ...rows] }); }; const patchRow = (id, patch) => { onChange({ fields: designer.fields.map((row) => (row.id === id ? { ...row, ...patch } : row)), }); }; const removeRow = (id) => { onChange({ fields: designer.fields.filter((row) => row.id !== id) }); }; const onDragEnd = (event) => { const { active, over } = event; if (!over || active.id === over.id) return; const from = designer.fields.findIndex((row) => row.id === active.id); const to = designer.fields.findIndex((row) => row.id === over.id); if (from < 0 || to < 0) return; onChange({ fields: arrayMove(designer.fields, from, to) }); }; const toggleOption = (item, checked) => { if (checked) appendRows([item]); else removeRow(item.value); }; const countFor = (source) => designer.fields.filter((row) => row.source === source).length; const addManual = () => { const key = manualKey.trim(); const label = manualLabel.trim(); if (!key || !label) return; const id = key; if (taken.has(id)) return; onChange({ fields: [...designer.fields, { id, source: 'manual', key, label, show_in_export: true, show_in_view: false }], }); setManualKey(''); setManualLabel(''); }; return (
Data Designer
{open ? (
{manualOpen ? (
Field name setManualKey(event.target.value)} placeholder="erdostyne_capsule_value" /> Column title setManualLabel(event.target.value)} placeholder="ERDOSTYNE CAPSULE 10s Value" />
) : null}
) : null} row.id)} strategy={verticalListSortingStrategy}> {designer.fields.map((row) => ( ))} {!designer.fields.length ?

No fields selected.

: null}
); } export function DataDesignerField({ field, control }) { return ( ( )} /> ); }