323 lines
12 KiB
Markdown
323 lines
12 KiB
Markdown
# 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`**
|
|
```sql
|
|
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)
|
|
|
|
```typescript
|
|
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
|
|
- [x] TypeScript compilation with zero errors
|
|
- [x] Vite production build successful
|
|
- [x] No unused imports or type issues
|
|
- [x] All components properly typed
|
|
|
|
### ✅ Backend Verification
|
|
- [x] Server starts without errors
|
|
- [x] Database migration "Fase 13 — Direct Messages" logged as ✅
|
|
- [x] All 5 endpoints registered and responding
|
|
- [x] Multi-tenant isolation via tenant_id enforced
|
|
- [x] Auth via `verifyAnyToken` middleware active
|
|
|
|
### ✅ Frontend Features
|
|
- [x] CollaboratorsPanel renders colleague list
|
|
- [x] Search/filter functionality for colleagues
|
|
- [x] Availability status colors display correctly
|
|
- [x] Unread badges show for colleagues with messages
|
|
- [x] Tab switching between Inbox and Colaboradores works
|
|
- [x] DMConversationView opens when selecting colleague
|
|
- [x] Message bubbles display with correct styling (sent vs received)
|
|
- [x] Auto-scroll to bottom on new messages
|
|
- [x] Message input sends via Enter key
|
|
- [x] Back button returns to list
|
|
|
|
### 📊 Performance
|
|
- [x] Chunk size warnings noted (normal for large apps)
|
|
- [x] 30-second polling intervals for colleague updates
|
|
- [x] Database indexes optimize query performance
|
|
- [x] 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
|