diff --git a/backend/index.js b/backend/index.js index 8f7a87f..9aabd8f 100644 --- a/backend/index.js +++ b/backend/index.js @@ -699,12 +699,12 @@ apiRouter.delete('/origins/:id', requireRole(['admin', 'owner', 'manager', 'supe }); apiRouter.post('/origins/:id/items', requireRole(['admin', 'owner', 'manager', 'super_admin']), async (req, res) => { - const { name } = req.body; + const { name, color_class } = req.body; try { const oid = `oriitm_${crypto.randomUUID().split('-')[0]}`; await pool.query( - 'INSERT INTO origin_items (id, origin_group_id, name) VALUES (?, ?, ?)', - [oid, req.params.id, name] + 'INSERT INTO origin_items (id, origin_group_id, name, color_class) VALUES (?, ?, ?, ?)', + [oid, req.params.id, name, color_class || 'bg-zinc-100 text-zinc-800 border-zinc-200'] ); res.status(201).json({ id: oid }); } catch (error) { @@ -713,12 +713,12 @@ apiRouter.post('/origins/:id/items', requireRole(['admin', 'owner', 'manager', ' }); apiRouter.put('/origin_items/:id', requireRole(['admin', 'owner', 'manager', 'super_admin']), async (req, res) => { - const { name } = req.body; + const { name, color_class } = req.body; try { const [existing] = await pool.query('SELECT * FROM origin_items WHERE id = ?', [req.params.id]); if (existing.length === 0) return res.status(404).json({ error: 'Origin item not found' }); - await pool.query('UPDATE origin_items SET name = ? WHERE id = ?', [name || existing[0].name, req.params.id]); + await pool.query('UPDATE origin_items SET name = ?, color_class = ? WHERE id = ?', [name || existing[0].name, color_class || existing[0].color_class, req.params.id]); res.json({ message: 'Origin item updated.' }); } catch (error) { res.status(500).json({ error: error.message }); @@ -1480,12 +1480,20 @@ const provisionSuperAdmin = async (retries = 10, delay = 10000) => { id varchar(36) NOT NULL, origin_group_id varchar(36) NOT NULL, name varchar(255) NOT NULL, + color_class varchar(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200', created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY origin_group_id (origin_group_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; `); + // Attempt to add color_class if table already existed without it + try { + await connection.query("ALTER TABLE origin_items ADD COLUMN color_class VARCHAR(255) DEFAULT 'bg-zinc-100 text-zinc-800 border-zinc-200'"); + } catch (err) { + if (err.code !== 'ER_DUP_FIELDNAME') console.log('Schema update note (origin_items.color_class):', err.message); + } + // Add origin_group_id to teams try { await connection.query("ALTER TABLE teams ADD COLUMN origin_group_id VARCHAR(36) DEFAULT NULL"); diff --git a/pages/Dashboard.tsx b/pages/Dashboard.tsx index 8f0473a..c03923c 100644 --- a/pages/Dashboard.tsx +++ b/pages/Dashboard.tsx @@ -179,6 +179,17 @@ export const Dashboard: React.FC = () => { })); }, [data, funnelDefs]); + const tailwindToHex: Record = { + 'zinc': '#71717a', + 'blue': '#3b82f6', + 'purple': '#a855f7', + 'green': '#22c55e', + 'red': '#ef4444', + 'pink': '#ec4899', + 'orange': '#f97316', + 'yellow': '#eab308' + }; + // --- Chart Data: Origin --- const originData = useMemo(() => { const origins = data.reduce((acc, curr) => { @@ -186,11 +197,21 @@ export const Dashboard: React.FC = () => { return acc; }, {} as Record); - // Ensure type safety for value in sort return (Object.entries(origins) as [string, number][]) - .map(([name, value]) => ({ name, value })) + .map(([name, value]) => { + const def = originDefs.find(o => o.name === name); + // Extract base color (e.g. "red" from "bg-red-100 text-red-700...") + let hexColor = ''; + if (def && def.color_class) { + const match = def.color_class.match(/bg-([a-z]+)-\d+/); + if (match && tailwindToHex[match[1]]) { + hexColor = tailwindToHex[match[1]]; + } + } + return { name, value, hexColor }; + }) .sort((a, b) => b.value - a.value); - }, [data]); + }, [data, originDefs]); // --- Table Data: Sellers Ranking --- const sellersRanking = useMemo(() => { @@ -427,12 +448,11 @@ export const Dashboard: React.FC = () => { stroke="none" > {originData.map((entry, index) => ( - - ))} - + ))} [value, 'Leads']} contentStyle={{ diff --git a/pages/Origins.tsx b/pages/Origins.tsx index c35dc62..5b98b07 100644 --- a/pages/Origins.tsx +++ b/pages/Origins.tsx @@ -11,7 +11,7 @@ export const Origins: React.FC = () => { const [loading, setLoading] = useState(true); const [isModalOpen, setIsModalOpen] = useState(false); const [editingItem, setEditingItem] = useState(null); - const [formData, setFormData] = useState({ name: '' }); + const [formData, setFormData] = useState({ name: '', color_class: '' }); const [isSaving, setIsSaving] = useState(false); // Group creation state @@ -20,6 +20,15 @@ export const Origins: React.FC = () => { const tenantId = localStorage.getItem('ctms_tenant_id') || ''; + const PRESET_COLORS = [ + { label: 'Cinza', value: 'bg-zinc-100 text-zinc-700 border-zinc-200 dark:bg-dark-input dark:text-dark-muted dark:border-dark-border' }, + { label: 'Azul', value: 'bg-blue-100 text-blue-700 border-blue-200 dark:bg-blue-900/30 dark:text-blue-400 dark:border-blue-800' }, + { label: 'Roxo', value: 'bg-purple-100 text-purple-700 border-purple-200 dark:bg-purple-900/30 dark:text-purple-400 dark:border-purple-800' }, + { label: 'Verde', value: 'bg-green-100 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-400 dark:border-green-800' }, + { label: 'Vermelho', value: 'bg-red-100 text-red-700 border-red-200 dark:bg-red-900/30 dark:text-red-400 dark:border-red-800' }, + { label: 'Rosa', value: 'bg-pink-100 text-pink-700 border-pink-200 dark:bg-pink-900/30 dark:text-pink-400 dark:border-pink-800' }, + ]; + const loadData = async () => { setLoading(true); const [fetchedGroups, fetchedTeams] = await Promise.all([ @@ -115,10 +124,10 @@ export const Origins: React.FC = () => { const openItemModal = (item?: OriginItemDef) => { if (item) { setEditingItem(item); - setFormData({ name: item.name }); + setFormData({ name: item.name, color_class: item.color_class || PRESET_COLORS[0].value }); } else { setEditingItem(null); - setFormData({ name: '' }); + setFormData({ name: '', color_class: PRESET_COLORS[0].value }); } setIsModalOpen(true); }; @@ -211,7 +220,9 @@ export const Origins: React.FC = () => {
{selectedGroup.items?.map((o) => (
-
{o.name}
+ + {o.name} +
+
+ +
+ {PRESET_COLORS.map((color, i) => ( + + ))} +
+
+