feat(secretaria): agente/cérebro por número na aba Números #2
@@ -722,9 +722,10 @@ const ROLE_OPTIONS: { value: NumberRole; label: string }[] = [
|
||||
interface BaileysInstance { id: string; name: string; status: string }
|
||||
|
||||
function NumbersPanel({
|
||||
numbers, loading, onCreate, onUpdate, onDelete,
|
||||
numbers, agents, loading, onCreate, onUpdate, onDelete,
|
||||
}: {
|
||||
numbers: SecNumber[]
|
||||
agents: SecAgent[]
|
||||
loading: boolean
|
||||
onCreate: (d: Partial<SecNumber>) => void
|
||||
onUpdate: (id: string, d: Partial<SecNumber>) => void
|
||||
@@ -734,7 +735,8 @@ function NumbersPanel({
|
||||
const [loadingInst, setLoadingInst] = useState(false)
|
||||
const [rolePickerFor, setRolePickerFor] = useState<string | null>(null) // instance_id
|
||||
const [editingId, setEditingId] = useState<string | null>(null) // sec_number.id
|
||||
const [editForm, setEditForm] = useState<{ area: string; priority: number; notes: string }>({ area: '', priority: 10, notes: '' })
|
||||
const [editForm, setEditForm] = useState<{ area: string; priority: number; notes: string; agent_id: string | null }>({ area: '', priority: 10, notes: '', agent_id: null })
|
||||
const agentNameOf = (id: string | null | undefined) => agents.find((a) => a.id === id)?.name ?? null
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingInst(true)
|
||||
@@ -825,6 +827,14 @@ function NumbersPanel({
|
||||
)}
|
||||
{num?.area && <span className="text-[10px] text-gray-400">· {num.area}</span>}
|
||||
</div>
|
||||
{num && (
|
||||
<div className="flex items-center gap-1 mt-0.5">
|
||||
<Brain size={9} className="text-gray-400 shrink-0" />
|
||||
<span className={`text-[10px] truncate ${num.agent_id ? 'text-brand-600 font-semibold' : 'text-gray-400 italic'}`}>
|
||||
{agentNameOf(num.agent_id) ?? 'Cérebro padrão (1º agente ativo)'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
@@ -843,6 +853,8 @@ function NumbersPanel({
|
||||
{/* Role picker dropdown */}
|
||||
<AnimatePresence>
|
||||
{rolePickerFor === inst.id && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-40" onClick={() => setRolePickerFor(null)} />
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -4 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
@@ -877,6 +889,7 @@ function NumbersPanel({
|
||||
)
|
||||
})}
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
@@ -891,7 +904,7 @@ function NumbersPanel({
|
||||
|
||||
{/* Editar detalhes (área, prioridade) */}
|
||||
{num && !isEditing && (
|
||||
<button onClick={() => { setEditingId(num.id); setEditForm({ area: num.area ?? '', priority: num.priority, notes: num.notes ?? '' }) }}
|
||||
<button onClick={() => { setEditingId(num.id); setEditForm({ area: num.area ?? '', priority: num.priority, notes: num.notes ?? '', agent_id: num.agent_id ?? null }) }}
|
||||
className="w-5 h-5 flex items-center justify-center text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<Pencil size={11} />
|
||||
@@ -914,9 +927,23 @@ function NumbersPanel({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Inline edit: área + prioridade */}
|
||||
{/* Inline edit: agente/cérebro + área + prioridade */}
|
||||
{isEditing && (
|
||||
<div className="px-3 pb-3 pt-1 border-t border-gray-100 grid grid-cols-2 gap-2">
|
||||
<div className="col-span-2">
|
||||
<p className="text-[10px] text-gray-500 mb-0.5">Agente / Cérebro deste número</p>
|
||||
<select
|
||||
value={editForm.agent_id ?? ''}
|
||||
onChange={(e) => setEditForm((f) => ({ ...f, agent_id: e.target.value || null }))}
|
||||
className="w-full text-xs bg-gray-50 text-gray-800 border border-gray-200 rounded-lg px-2 py-1.5 focus:outline-none focus:border-brand-500/40"
|
||||
>
|
||||
<option value="">Cérebro padrão (1º agente ativo)</option>
|
||||
{agents.map((a) => (
|
||||
<option key={a.id} value={a.id}>{a.name}{a.active ? '' : ' (inativo)'}</option>
|
||||
))}
|
||||
</select>
|
||||
<p className="text-[9px] text-gray-400 mt-0.5 leading-tight">Cada número responde com o cérebro (persona, regras, conhecimento, agenda) do agente escolhido aqui.</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[10px] text-gray-500 mb-0.5">Área / Especialidade</p>
|
||||
<input
|
||||
@@ -1688,6 +1715,7 @@ export function SecretariaView(props: { onNavigate?: (view: string) => void } =
|
||||
{activeTab === 'numbers' && (
|
||||
<NumbersPanel
|
||||
numbers={store.numbers}
|
||||
agents={store.agents}
|
||||
loading={store.loadingNumbers}
|
||||
onCreate={(d) => store.createNumber(d)}
|
||||
onUpdate={(id, d) => store.updateNumber(id, d)}
|
||||
|
||||
@@ -75,6 +75,7 @@ export interface CalendarSlot {
|
||||
date: string
|
||||
time_start: string
|
||||
time_end: string
|
||||
instance_id?: string | null // número/sessão dona do slot (null = global)
|
||||
attendee_name: string | null
|
||||
attendee_phone: string | null
|
||||
status: 'available' | 'booked' | 'cancelled'
|
||||
@@ -132,6 +133,8 @@ export type NumberRole =
|
||||
export interface SecNumber {
|
||||
id: string
|
||||
instance_id: string
|
||||
clinica_id?: string | null
|
||||
agent_id: string | null // agente/cérebro deste número (separação por sessão)
|
||||
label: string
|
||||
role: NumberRole
|
||||
area: string | null
|
||||
@@ -153,7 +156,7 @@ export const secNumberApi = {
|
||||
// ─── Calendar ─────────────────────────────────────────────────────────────────
|
||||
|
||||
export const secCalApi = {
|
||||
list: (params?: { from?: string; to?: string; status?: string }) =>
|
||||
list: (params?: { from?: string; to?: string; status?: string; instance_id?: string }) =>
|
||||
api.get<CalendarSlot[]>(`${BASE}/calendar`, { params }).then((r) => r.data),
|
||||
create: (d: Partial<CalendarSlot>) => api.post<CalendarSlot>(`${BASE}/calendar`, d).then((r) => r.data),
|
||||
update: (id: string, d: Partial<CalendarSlot>) => api.put<CalendarSlot>(`${BASE}/calendar/${id}`, d).then((r) => r.data),
|
||||
|
||||
Reference in New Issue
Block a user