Files

12 KiB

Phase 13 — Direct Messages Between Collaborators

Implementation Date: 2026-04-23
Status: COMPLETE AND TESTED
Build Status: Production Build Passing


🎯 What Was Built

Phase 13 implements direct messaging (DM) between team members in the Inbox Interna interface. This enables peer-to-peer communication outside of protocol-bound escalations and human API requests.


📦 Backend Implementation

Database Layer

New Table: direct_messages

CREATE TABLE direct_messages (
  id            SERIAL PRIMARY KEY,
  tenant_id     INT NOT NULL REFERENCES tenants(id),
  from_account  INT NOT NULL REFERENCES accounts(id) ON DELETE SET NULL,
  to_account    INT NOT NULL REFERENCES accounts(id) ON DELETE SET NULL,
  content       TEXT NOT NULL,
  read_at       TIMESTAMPTZ,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Indexes (for optimal query performance):

  • (tenant_id, from_account, to_account, created_at DESC) — conversation history
  • (tenant_id, to_account, read_at) — unread messages
  • (created_at DESC) — chronological ordering

File: /home/deploy/projetos/alemaoconveniencias.local/sistema-nuvem/database.js (lines 688-705)

Model Layer

File: models/DirectMessage.js (6 methods)

Method Purpose
send(tenantId, fromId, toId, content) Insert message, return with metadata
getConversation(tenantId, id1, id2, limit) Fetch conversation history between two accounts
getLastMessagesByAccount(tenantId, accountId) Latest message per contact (for preview list)
getUnreadCount(tenantId, accountId) Total unread for an account
markAsRead(tenantId, accountId, fromAccountId) Mark messages from sender as read
getUnreadCountByContact(tenantId, accountId) Map of unread count per sender

Controller Layer

File: controllers/directMessageController.js (5 endpoints)

Endpoint Method Purpose
/api/admin/inbox/direct-message POST Send a DM to a colleague
/api/admin/inbox/direct/:otherAccountId GET Fetch conversation history + mark as read
/api/admin/inbox/direct-messages GET List last message per contact (enriched with account data)
/api/admin/inbox/colleagues GET List all active colleagues with status + unread count
/api/admin/inbox/direct/:messageId/read PATCH Manually mark single message as read

Key Features:

  • All endpoints use verifyAnyToken middleware (works for admin + team accounts)
  • Automatic marking as read when conversation is opened
  • Unread count badges per contact
  • Availability status (online/busy/away/offline) from accounts.availability
  • Multi-tenant isolation via tenant_id

File Location: server.js (lines 272-276)

API Service Layer

File: admin-app/src/services/api.ts (DirectMessageService export)

export const DirectMessageService = {
  send: (toAccountId, content)  Promise
  getConversation: (otherAccountId, limit)  Promise<Message[]>
  getLastMessages: ()  Promise<EnrichedMessage[]>
  getColleagues: ()  Promise<Colleague[]>
  markAsRead: (messageId)  Promise
}

🎨 Frontend Implementation

1. CollaboratorsPanel Component

File: admin-app/src/pages/CollaboratorsPanel.tsx

Features:

  • 📋 Searchable list of colleagues (by name/email)
  • 🟢🟡 Availability status color indicators
  • 🔔 Unread message badges (max "9+")
  • 👤 Avatar with fallback initial
  • 🔄 30-second auto-refresh interval
  • Selection state tracking

Props:

  • selected: number | null — currently selected colleague ID
  • onSelect: (accountId: number) => void — callback when selecting colleague
  • onStartDM: (accountId: number) => void — callback when clicking message button

Layout: Vertical list with search at top, refresh button at bottom

2. DMConversationView Component

File: admin-app/src/pages/DMConversationView.tsx

Features:

  • 💬 Chat-style message bubbles (sent vs received)
  • ⌨️ Textarea input with Ctrl+Enter to send
  • 📜 Auto-scroll to newest message
  • 🔄 Manual refresh button
  • Timestamp on each message
  • 👤 Avatar display in header
  • 📊 Availability status display (Online/Ocupado/Ausente/Offline)

Data Flow:

  1. Opens → fetches conversation history
  2. Auto-marks unread messages as read
  3. Input → send → refresh conversation
  4. Back button returns to colleagues list

3. InboxInternaTab Integration

File: admin-app/src/pages/InboxInternaTab.tsx (modified)

New Two-Column Layout:

┌──────────────────┬─────────────────────────────────────┐
│ LEFT: List       │ RIGHT: Detail                       │
├──────────────────┼─────────────────────────────────────┤
│ Tabs:            │                                      │
│ ├─ Inbox (old)   │ If no selection → empty state       │
│ └─ Colaboradores │ If thread selected → ThreadPanel    │
│    (new)         │ If colleague selected → DM view     │
│                  │                                      │
│ Inbox Tab:       │                                      │
│ ├─ Threads list  │                                      │
│ ├─ Filters       │                                      │
│ └─ Help button   │                                      │
│                  │                                      │
│ Colaboradores:   │                                      │
│ ├─ Colleagues    │                                      │
│ ├─ Search        │                                      │
│ └─ Status dots   │                                      │
└──────────────────┴─────────────────────────────────────┘

Key Implementation Details:

  • View mode state: 'inbox' | 'colleagues'
  • Selected colleague state with full object (not just ID)
  • Tab switching clears opposite view's selection
  • Left column: w-80 (fixed) when detail open, w-full (full width) when collapsed
  • Right column: flex-1 (flex to fill remaining space)
  • Responsive two-column layout with proper flexbox constraints

🧪 Testing Checklist

Build & Compilation

  • TypeScript compilation with zero errors
  • Vite production build successful
  • No unused imports or type issues
  • All components properly typed

Backend Verification

  • Server starts without errors
  • Database migration "Fase 13 — Direct Messages" logged as
  • All 5 endpoints registered and responding
  • Multi-tenant isolation via tenant_id enforced
  • Auth via verifyAnyToken middleware active

Frontend Features

  • CollaboratorsPanel renders colleague list
  • Search/filter functionality for colleagues
  • Availability status colors display correctly
  • Unread badges show for colleagues with messages
  • Tab switching between Inbox and Colaboradores works
  • DMConversationView opens when selecting colleague
  • Message bubbles display with correct styling (sent vs received)
  • Auto-scroll to bottom on new messages
  • Message input sends via Enter key
  • Back button returns to list

📊 Performance

  • Chunk size warnings noted (normal for large apps)
  • 30-second polling intervals for colleague updates
  • Database indexes optimize query performance
  • Component re-renders optimized with useCallback

📝 Code Quality

TypeScript

  • All components strongly typed with interfaces
  • No implicit any types
  • Proper callback type signatures
  • Interface exports for reusability

React Patterns

  • Functional components with hooks
  • useCallback for memoization
  • useEffect for data fetching with cleanup
  • Proper dependency arrays
  • Refs for auto-scroll functionality

Database

  • Parameterized queries (SQL injection prevention)
  • Proper foreign key relationships
  • Tenant isolation on all queries
  • Efficient indexes for common queries

Error Handling

  • Try-catch blocks in all async functions
  • Fallback UI states (loading, empty, error)
  • User-friendly error messages
  • Console logging for debugging

🚀 What Users Can Do Now

Collaborators Can:

  1. Open Inbox Interna → click "Colaboradores" tab
  2. Search for colleagues by name/email
  3. See availability status (online/busy/away/offline)
  4. View unread message count per colleague
  5. Click a colleague to open private DM chat
  6. Send messages with Ctrl+Enter
  7. Auto-scroll to latest messages
  8. Messages marked as read when conversation opens
  9. Switch back to Inbox tab to view escalations/human API requests
  10. Auto-refresh colleague list every 30 seconds

🔄 Next Steps (Phase 2)

These features are NOT yet implemented but are planned:

Feature Purpose Complexity
SSE Real-time Notifications Push DM notifications without polling Medium
Group Chat by Sector Team communication for Financeiro, Secretária, etc. Medium
Broadcast Messages Send to all collaborators at once Low
Message Search Find old conversations Low
Message Pinning Mark important messages Low
@Mentions Notify specific people Medium

📂 Files Modified/Created

Created:

  • admin-app/src/pages/CollaboratorsPanel.tsx — Colleague list component
  • admin-app/src/pages/DMConversationView.tsx — DM conversation component
  • models/DirectMessage.js — Model with 6 methods
  • controllers/directMessageController.js — 5 endpoint handlers
  • This completion summary document

Modified:

  • admin-app/src/pages/InboxInternaTab.tsx — Added two-column layout + tabs
  • admin-app/src/services/api.ts — Exported DirectMessageService
  • sistema-nuvem/database.js — Added direct_messages table + indexes
  • sistema-nuvem/server.js — Registered 5 new routes
  • inbox-interna.md — Updated roadmap with completion status

🧭 Architecture Notes

Design Decisions

  1. Separate direct_messages Table

    • Not extending internal_messages to maintain clear separation
    • Internal messages = context-bound (linked to protocols)
    • Direct messages = peer-to-peer communication (free-form)
  2. Auto-mark-as-read on Open

    • Improves UX by auto-clearing unread badges
    • Database marked on getConversation() endpoint
    • Can be overridden by manual markAsRead() PATCH if needed
  3. Two-Column Layout

    • Left = always shows list (threads or colleagues)
    • Right = only shows detail when selection exists
    • Responsive: collapses to full-width on narrow screens (with w-80 to w-full)
  4. Availability Status

    • Reused existing accounts.availability field
    • No new status tracking needed
    • Color-coded: green=online, yellow=busy, gray=away, dark=offline

Summary

Fase 13 successfully implements Phase 1 of the Inbox Interna v2 roadmap, enabling direct peer-to-peer communication between team members. The implementation is production-ready with:

  • Zero TypeScript errors
  • Successful production build
  • Fully functional backend (5 endpoints)
  • Fully functional frontend (2 new components + InboxInternaTab integration)
  • Multi-tenant support
  • Proper error handling and loading states
  • Clean, maintainable code following React/Node best practices

Next phase (v2.2) will add group chats by sector and broadcast messaging once this foundation is validated in production.


Implementation completed by: Claude
Date: 2026-04-23
Status: Ready for production deployment