feat: implement relational lead origins with team assignments
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m51s

- Dropped simple origins table in favor of origin_groups and origin_items to match the Funnels architecture.

- Added origin_group_id to teams table to assign specific origins to specific teams.

- Updated /admin/origins page to support creating origin groups, adding origin items to them, and assigning teams to groups.

- Updated Dashboard and UserDetail pages to dynamically load the exact origin items belonging to the active team/user.
This commit is contained in:
Cauê Faleiros
2026-03-18 11:18:30 -03:00
parent 64c4ca8fb5
commit 1d3315a1d0
8 changed files with 641 additions and 27 deletions

View File

@@ -5,9 +5,9 @@ import {
import {
Users, Clock, Phone, TrendingUp, Filter
} from 'lucide-react';
import { getAttendances, getUsers, getTeams, getUserById, getFunnels } from '../services/dataService';
import { getAttendances, getUsers, getTeams, getUserById, getFunnels, getOrigins } from '../services/dataService';
import { COLORS } from '../constants';
import { Attendance, DashboardFilter, FunnelStage, User, FunnelStageDef } from '../types';
import { Attendance, DashboardFilter, FunnelStage, User, FunnelStageDef, OriginItemDef } from '../types';
import { KPICard } from '../components/KPICard';
import { DateRangePicker } from '../components/DateRangePicker';
import { SellersTable } from '../components/SellersTable';
@@ -29,6 +29,7 @@ export const Dashboard: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [teams, setTeams] = useState<any[]>([]);
const [funnelDefs, setFunnelDefs] = useState<FunnelStageDef[]>([]);
const [originDefs, setOriginDefs] = useState<OriginItemDef[]>([]);
const [currentUser, setCurrentUser] = useState<User | null>(null);
const [filters, setFilters] = useState<DashboardFilter>({
@@ -57,12 +58,13 @@ export const Dashboard: React.FC = () => {
const prevFilters = { ...filters, dateRange: { start: prevStart, end: prevEnd } };
// Fetch users, attendances, teams, funnels and current user in parallel
const [fetchedUsers, fetchedData, prevFetchedData, fetchedTeams, fetchedFunnels, me] = await Promise.all([
const [fetchedUsers, fetchedData, prevFetchedData, fetchedTeams, fetchedFunnels, fetchedOrigins, me] = await Promise.all([
getUsers(tenantId),
getAttendances(tenantId, filters),
getAttendances(tenantId, prevFilters),
getTeams(tenantId),
getFunnels(tenantId),
getOrigins(tenantId),
storedUserId ? getUserById(storedUserId) : null
]);
@@ -70,6 +72,7 @@ export const Dashboard: React.FC = () => {
setData(fetchedData);
setPrevData(prevFetchedData);
setTeams(fetchedTeams);
setOriginDefs(fetchedOrigins);
if (me) setCurrentUser(me);
// Determine which funnel to display
@@ -82,6 +85,14 @@ export const Dashboard: React.FC = () => {
setFunnelDefs(activeFunnel && activeFunnel.stages ? activeFunnel.stages.sort((a: any, b: any) => a.order_index - b.order_index) : []);
// Determine which origins to display
let activeOriginGroup = fetchedOrigins[0];
if (targetTeamId) {
const matchedOrigin = fetchedOrigins.find(o => o.teamIds?.includes(targetTeamId));
if (matchedOrigin) activeOriginGroup = matchedOrigin;
}
setOriginDefs(activeOriginGroup && activeOriginGroup.items ? activeOriginGroup.items : []);
} catch (error) {
console.error("Error loading dashboard data:", error);
} finally {
@@ -299,19 +310,18 @@ export const Dashboard: React.FC = () => {
<option key={stage} value={stage}>{stage}</option>
))}
</select>
<select
<select
className="bg-zinc-50 dark:bg-dark-bg border border-zinc-200 dark:border-dark-border px-3 py-2 rounded-lg text-sm text-zinc-700 dark:text-zinc-200 outline-none focus:ring-2 focus:ring-brand-yellow/20 cursor-pointer hover:border-zinc-300 dark:hover:border-dark-border transition-all"
value={filters.origin}
onChange={(e) => handleFilterChange('origin', e.target.value)}
>
<option value="all">Todas Origens</option>
<option value="WhatsApp">WhatsApp</option>
<option value="Instagram">Instagram</option>
<option value="Website">Website</option>
<option value="LinkedIn">LinkedIn</option>
<option value="Indicação">Indicação</option>
</select>
</div>
{originDefs.length > 0 ? originDefs.map(o => (
<option key={o.id} value={o.name}>{o.name}</option>
)) : ['WhatsApp', 'Instagram', 'Website', 'LinkedIn', 'Indicação'].map(o => (
<option key={o} value={o}>{o}</option>
))}
</select> </div>
</div>
</div>