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 })}
/>
onRemove(row.id)}>
Remove
);
}
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
setOpen((current) => !current)}>
Add
{open ? (
setManualOpen((current) => !current)}>
Manual ({countFor('manual')})
{manualOpen ? (
Field name
setManualKey(event.target.value)} placeholder="erdostyne_capsule_value" />
Column title
setManualLabel(event.target.value)} placeholder="ERDOSTYNE CAPSULE 10s Value" />
Add manual field
) : null}
) : null}
row.id)} strategy={verticalListSortingStrategy}>
{designer.fields.map((row) => (
))}
{!designer.fields.length ?
No fields selected.
: null}
);
}
export function DataDesignerField({ field, control }) {
return (
(
)}
/>
);
}