feat: implement relational lead origins with team assignments
All checks were successful
Build and Deploy / build-and-push (push) Successful in 1m51s
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:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user