/* Custom Column Builder (formula builder) =========================== */
const METRIC_TOKENS = [
{ val:'spend', label:'Amount Spent', kind:'$' },
{ val:'impressions', label:'Impressions', kind:'#' },
{ val:'clicks', label:'Clicks', kind:'#' },
{ val:'conversions', label:'Conversions', kind:'#' },
{ val:'revenue', label:'Revenue', kind:'$' },
{ val:'followers_ig', label:'IG Followers', kind:'#' },
{ val:'subscribers', label:'Subscribers', kind:'#' },
{ val:'video_views', label:'Video Views', kind:'#' },
{ val:'addtocart', label:'Add-to-Cart', kind:'#' },
{ val:'leads', label:'Leads', kind:'#' },
];
const OPS = ['+','−','×','÷','(',')'];
const FORMAT_OPTS = [
{ val:'int', label:'1,234' },
{ val:'currency', label:'$1,234' },
{ val:'currency2', label:'$1,234.56' },
{ val:'pct', label:'12.34%' },
{ val:'x', label:'1.23×' },
];
function TokenChip({ tok, onRemove }){
const isMetric = tok.type === 'metric';
const isOp = tok.type === 'op';
const isNum = tok.type === 'num';
return (
{isMetric && (METRIC_TOKENS.find(m=>m.val===tok.val)?.label || tok.val)}
{isOp && {tok.val}}
{isNum && {tok.val}}
{onRemove && (
)}
);
}
function CustomColumnBuilder({ open, onClose, onSave, initial }){
const [name, setName] = useState(initial?.name || '');
const [formula, setFormula] = useState(initial?.formula || []);
const [format, setFormat] = useState(initial?.format || 'currency2');
const [numInput, setNumInput] = useState('');
useEffect(()=>{
if (open) {
setName(initial?.name || '');
setFormula(initial?.formula || []);
setFormat(initial?.format || 'currency2');
setNumInput('');
}
}, [open, initial]);
const add = (tok)=> setFormula(f => [...f, tok]);
const remove = (i)=> setFormula(f => f.filter((_,idx)=>idx!==i));
const clear = ()=> setFormula([]);
/* Preview a stylized formula string */
const preview = formula.length === 0 ? 'Build your formula below…' :
formula.map(t => t.type==='metric' ? `[${(METRIC_TOKENS.find(m=>m.val===t.val)?.label||t.val)}]` : t.val).join(' ');
/* Sample value preview (fake) */
const sampleValue = useMemo(()=>{
const r = formula.length ? (Math.random()*42+1) : 0;
return formula.length ? fmt(+r.toFixed(2), format) : '—';
}, [formula, format]);
return (
Create custom column
Define a metric using existing fields and operators.
{/* Name + format */}
{/* Formula canvas */}
{formula.length === 0 && (
Tap metrics and operators below to build →
)}
{formula.map((t,i)=>(remove(i)}/>))}
{/* Preview line */}
{preview}
{sampleValue}
{/* Token palette */}
Metrics
{METRIC_TOKENS.map(m => (
))}
Operators
{OPS.map(op => (
))}
Constant
setNumInput(e.target.value)}
placeholder="123"
className="w-full h-8 rounded-lg border border-ink-200 dark:border-white/10 bg-white dark:bg-ink-900/40 px-2 text-[12.5px] font-mono"/>
Saved columns are pinned to your view.
Cancel
{ if(!name.trim() || formula.length===0) return; onSave({ id:'cc_'+Date.now(), name, formula, format }); onClose(); }}>
Save column
);
}
Object.assign(window, { CustomColumnBuilder });