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
verifyAnyTokenmiddleware (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 IDonSelect: (accountId: number) => void— callback when selecting colleagueonStartDM: (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:
- Opens → fetches conversation history
- Auto-marks unread messages as read
- Input → send → refresh conversation
- 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
verifyAnyTokenmiddleware 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
anytypes - ✅ 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:
- ✅ Open Inbox Interna → click "Colaboradores" tab
- ✅ Search for colleagues by name/email
- ✅ See availability status (online/busy/away/offline)
- ✅ View unread message count per colleague
- ✅ Click a colleague to open private DM chat
- ✅ Send messages with Ctrl+Enter
- ✅ Auto-scroll to latest messages
- ✅ Messages marked as read when conversation opens
- ✅ Switch back to Inbox tab to view escalations/human API requests
- ✅ 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 componentadmin-app/src/pages/DMConversationView.tsx— DM conversation componentmodels/DirectMessage.js— Model with 6 methodscontrollers/directMessageController.js— 5 endpoint handlers- This completion summary document
Modified:
admin-app/src/pages/InboxInternaTab.tsx— Added two-column layout + tabsadmin-app/src/services/api.ts— Exported DirectMessageServicesistema-nuvem/database.js— Added direct_messages table + indexessistema-nuvem/server.js— Registered 5 new routesinbox-interna.md— Updated roadmap with completion status
🧭 Architecture Notes
Design Decisions
-
Separate
direct_messagesTable- Not extending
internal_messagesto maintain clear separation - Internal messages = context-bound (linked to protocols)
- Direct messages = peer-to-peer communication (free-form)
- Not extending
-
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
-
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-80tow-full)
-
Availability Status
- Reused existing
accounts.availabilityfield - No new status tracking needed
- Color-coded: green=online, yellow=busy, gray=away, dark=offline
- Reused existing
✨ 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