> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quinnsambal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Team chat

<style>
  {`
    [class*="max-w-none"][class*="table"] {
      display: block !important;
      overflow-x: auto !important;
      max-width: 100% !important;
      width: 100% !important;
      flex-grow: 0 !important;
    }
    [class*="max-w-none"][class*="table"] > table {
      width: 100% !important;
      max-width: 100% !important;
      table-layout: fixed !important;
    }
    .mermaid {
      max-width: 100% !important;
      overflow-x: auto !important;
    }
    article svg[role="img"] {
      max-width: 100% !important;
      height: auto !important;
    }
    article img, .prose img {
      max-width: 100% !important;
      height: auto !important;
    }
    img[src*="LOGO"], img[src*="logo"] {
      max-width: 120px !important;
      max-height: 40px !important;
      width: auto !important;
      height: auto !important;
      object-fit: contain !important;
    }
    `}
</style>

***

title: "Team Chat"
description: "Real-time team chat dengan Pusher WebSocket, channel management, direct message, file sharing via Cloudinary, dan reply threads di SNISHOP ERP."
--------------------------------------------------------------------------------------------------------------------------------------------------------------

# Team Chat

<img src="https://mintcdn.com/quinnofspicy/e4f_upKhVWcjsUmM/docs/mintlify/screenshots/communication/team-chat.png?fit=max&auto=format&n=e4f_upKhVWcjsUmM&q=85&s=0522cbeb54eb4e8077c04df63e588e6b" alt="Team Chat" width="1920" height="1080" data-path="docs/mintlify/screenshots/communication/team-chat.png" />

Team Chat adalah sistem komunikasi internal terpusat untuk tim kamu. Dibangun di atas `TeamChat.jsx` (568 baris) dengan arsitektur **Pusher WebSocket** untuk messaging real-time, mendukung channel group, direct message, reply threads, pin message, dan file sharing via Cloudinary.

Semua percakapan tersimpan di base44 backend dan di-scope per company, memastikan data tidak bocor antar perusahaan. Fitur ini mengurangi ketergantungan pada aplikasi chat eksternal dan menjaga semua komunikasi kerja tetap terdokumentasi dalam satu platform.

***

## Fitur Utama

| Fitur | Deskripsi |
| - | - |
| **Channel Messaging** | Komunikasi berbasis channel (public, private, direct) dengan dukungan real-time via Pusher WebSocket |
| **Direct Message (DM)** | Percakapan 1-to-1 antar anggota tim dengan polling fallback setiap 4 detik |
| **Reply Threads** | Balas pesan tertentu untuk menjaga konteks percakapan tetap terstruktur |
| **Pin Message** | Tandai pesan penting agar muncul di atas channel sebagai referensi cepat |
| **File Sharing** | Kirim gambar dan dokumen melalui Cloudinary CDN dengan kompresi otomatis |
| **Mentions** | Tag anggota tim spesifik dalam pesan menggunakan array `mentions` |
| **Read Receipts** | Lacak siapa saja yang sudah membaca pesan melalui field `read_by` |
| **Message Editing** | Edit pesan yang sudah terkirim dengan timestamp edit tersimpan |
| **Soft Delete** | Hapus pesan tanpa menghilangkan data — ditandai dengan flag `is_deleted` |
| **Company Scope** | Semua data chat di-isolasi per perusahaan untuk keamanan data |
| **Search** | Cari pesan lama dalam channel berdasarkan kata kunci |
| **Unread Badge** | Indikator jumlah pesan belum dibaca per channel |

***

## Arsitektur Komponen

```mermaid theme={null}
graph TD
    A[TeamChat.jsx<br/>568 lines] --> B[ERPAccessGuard]
    A --> C[Channels Sidebar]
    A --> D[Chat Area]
    A --> E[DirectMessageInbox<br/>345 lines]
    C --> F[Channel List<br/>with search]
    C --> G[Create Channel Form]
    C --> H[Unread Badge]
    D --> I[Message Display<br/>date grouped]
    D --> J[Message Input]
    D --> K[Reply Thread]
    D --> L[Pin/Unpin]
    D --> M[File/Image Upload]
    M --> N[compressImage]
    N --> O[Cloudinary CDN]
    A --> P[Pusher Client<br/>pusherClient.js]
    P --> Q[ap1 cluster]
    E --> R[InternalMessage Entity]
    E --> S[4s Polling]
```

### Diagram Konteks Sistem

```mermaid theme={null}
graph LR
    subgraph "Frontend"
        TC[TeamChat.jsx]
        DMI[DirectMessageInbox.jsx]
        PC[pusherClient.js]
    end

    subgraph "Backend (base44)"
        API[base44 API]
        DB[(base44 Database)]
        PT[pusherTrigger Function]
    end

    subgraph "External Services"
        PUSHER[Pusher WebSocket<br/>ap1 cluster]
        CLOUD[Cloudinary CDN]
    end

    TC --> API
    TC --> PC
    DMI --> API
    PC --> PUSHER
    API --> DB
    API --> PT
    PT --> PUSHER
    TC --> CLOUD
```

***

## Entity Relationship Diagram

```mermaid theme={null}
erDiagram
    Company ||--o{ TeamChat : "memiliki"
    Company ||--o{ ChatChannel : "memiliki"
    Company ||--o{ InternalMessage : "memiliki"
    Company ||--o{ Notification : "memiliki"

    ChatChannel ||--o{ TeamChat : "memuat pesan"
    ChatChannel }o--o{ User : "memiliki anggota"

    User ||--o{ TeamChat : "mengirim"
    User ||--o{ InternalMessage : "mengirim"
    User ||--o{ InternalMessage : "menerima"
    User ||--o{ Notification : "menerima"
    User ||--o{ Conversation : "memiliki"

    TeamChat }o--o| TeamChat : "reply_to"
    TeamChat }o--|| ChatChannel : "channel_id"

    InternalMessage }o--o| InternalMessage : "related_entity"
    InternalMessage }o--o| Notification : "memicu notifikasi"

    Company {
        string id PK
        string company_name
    }

    User {
        string id PK
        string email
        string name
        string avatar_url
    }

    ChatChannel {
        string id PK
        string company_id FK
        string channel_name
        string channel_type "public | private | direct"
        string description
        array members
        array admins
        string created_by FK
        boolean is_archived
        string last_message
        datetime last_message_at
        number unread_count
    }

    TeamChat {
        string id PK
        string company_id FK
        string channel_id FK
        string channel_name
        string message
        string description
        string sender_id FK
        string sender_name
        string sender_avatar
        string message_type "text | image | file | system"
        array attachments
        array mentions
        string reply_to FK
        boolean is_edited
        datetime edited_at
        boolean is_deleted
        array read_by
    }

    InternalMessage {
        string id PK
        string company_id FK
        string sender_id FK
        string sender_name
        string sender_avatar
        string recipient_id FK
        string message_type "direct | announcement | workflow_alert | system"
        string subject
        string content
        boolean is_read
        string attachment_url
        string attachment_name
        string related_entity_type
        string related_entity_id
    }

    Conversation {
        string id PK
        string user_id FK
        string title
        string description
        array messages
        boolean pinned
    }

    Notification {
        string id PK
        string user_id FK
        string title
        string message
        string type
        string priority "low | normal | high"
        boolean is_read
        string company_id FK
        string source_entity
        string source_id
    }
```

***

## Entity Schema

### TeamChat (Chat Message)

Entitas utama untuk menyimpan setiap pesan dalam channel chat. Setiap pesan terasosiasi dengan channel tertentu dan di-scope oleh `company_id`.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | `string` (FK → Company) | **Ya** | ID perusahaan untuk scope isolasi data |
| `channel_id` | `string` (FK → ChatChannel) | **Ya** | ID channel/room tujuan pesan |
| `channel_name` | `string` | Tidak | Nama channel (denormalized untuk performa query) |
| `message` | `string` | **Ya** | Isi pesan yang dikirim |
| `description` | `string` | Tidak | Konteks atau keterangan tambahan mengenai channel atau pesan, maksimal 1000 karakter |
| `sender_id` | `string` (FK → User) | **Ya** | ID/email pengirim pesan |
| `sender_name` | `string` | Tidak | Nama lengkap pengirim (denormalized) |
| `sender_avatar` | `string` | Tidak | URL avatar pengirim |
| `message_type` | `enum` | Tidak | Tipe pesan: `text`, `image`, `file`, `system`. Default: `text` |
| `attachments` | `array<object>` | Tidak | Daftar file lampiran, setiap item memiliki `url`, `filename`, `filesize`, `filetype` |
| `mentions` | `array<string>` | Tidak | Array email user yang di-mention dalam pesan |
| `reply_to` | `string` (FK → TeamChat) | Tidak | ID pesan yang dibalas (reply thread) |
| `is_edited` | `boolean` | Tidak | Flag apakah pesan sudah diedit. Default: `false` |
| `edited_at` | `datetime` | Tidak | Timestamp terakhir pesan diedit |
| `is_deleted` | `boolean` | Tidak | Flag soft delete — pesan ditandai dihapus tanpa benar-benar dihapus. Default: `false` |
| `read_by` | `array<object>` | Tidak | Daftar user yang sudah membaca, setiap item memiliki `user_id` dan `read_at` |

#### Struktur Sub-field: `attachments`

| Field | Tipe | Deskripsi |
| - | - | - |
| `url` | `string` | URL file di Cloudinary CDN |
| `filename` | `string` | Nama file asli |
| `filesize` | `number` | Ukuran file dalam byte |
| `filetype` | `string` | MIME type file (mis. `image/png`, `application/pdf`) |

#### Struktur Sub-field: `read_by`

| Field | Tipe | Deskripsi |
| - | - | - |
| `user_id` | `string` | ID/email user yang sudah membaca |
| `read_at` | `datetime` | Timestamp kapan pesan dibaca |

***

### ChatChannel

Entitas yang merepresentasikan channel atau room untuk komunikasi grup. Mendukung tiga tipe channel: public, private, dan direct.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | `string` (FK → Company) | **Ya** | ID perusahaan untuk scope isolasi data |
| `channel_name` | `string` | **Ya** | Nama channel yang unik dalam scope perusahaan |
| `channel_type` | `enum` | **Ya** | Tipe channel: `public`, `private`, `direct`. Default: `public` |
| `description` | `string` | Tidak | Deskripsi channel untuk menjelaskan tujuan channel |
| `members` | `array<string>` | Tidak | Array email anggota channel |
| `admins` | `array<string>` | Tidak | Array email admin channel yang memiliki hak kelola |
| `created_by` | `string` (FK → User) | Tidak | ID/email user yang membuat channel |
| `is_archived` | `boolean` | Tidak | Flag channel diarsipkan (tidak aktif). Default: `false` |
| `last_message` | `string` | Tidak | Cuplikan pesan terakhir untuk preview di sidebar |
| `last_message_at` | `datetime` | Tidak | Timestamp pesan terakhir dikirim |
| `unread_count` | `number` | Tidak | Jumlah pesan belum dibaca oleh user saat ini. Default: `0` |

***

### InternalMessage (Direct Message)

Entitas untuk pesan langsung 1-to-1, announcement, dan notifikasi workflow. Digunakan oleh `DirectMessageInbox` untuk percakapan privat antar anggota tim.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `company_id` | `string` (FK → Company) | Tidak | ID perusahaan (null = personal/direct) |
| `sender_id` | `string` (FK → User) | **Ya** | Email pengirim |
| `sender_name` | `string` | Tidak | Nama lengkap pengirim |
| `sender_avatar` | `string` | Tidak | URL avatar pengirim |
| `recipient_id` | `string` (FK → User) | **Ya** | Email penerima (untuk DM) |
| `message_type` | `enum` | Tidak | Tipe pesan: `direct`, `announcement`, `workflow_alert`, `system`. Default: `direct` |
| `subject` | `string` | Tidak | Subjek pesan (khusus untuk announcement) |
| `content` | `string` | **Ya** | Isi pesan |
| `is_read` | `boolean` | Tidak | Status apakah pesan sudah dibaca. Default: `false` |
| `attachment_url` | `string` | Tidak | URL file lampiran di Cloudinary |
| `attachment_name` | `string` | Tidak | Nama file asli lampiran |
| `related_entity_type` | `string` | Tidak | Entitas terkait (mis. `task`, `invoice`, `production_batch`) |
| `related_entity_id` | `string` | Tidak | ID record entitas terkait |

***

### Conversation (AI Chat)

Entitas untuk percakapan AI assistant dalam platform. Berbeda dengan TeamChat — ini menyimpan riwayat percakapan user dengan AI.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `user_id` | `string` (FK → User) | **Ya** | ID pengguna yang memulai percakapan |
| `title` | `string` | **Ya** | Judul percakapan, dibuat otomatis dari pesan pertama |
| `description` | `string` | Tidak | Ringkasan atau konteks singkat percakapan, maksimal 1000 karakter |
| `messages` | `array<object>` | **Ya** | Seluruh riwayat pesan dalam percakapan |
| `pinned` | `boolean` | Tidak | Flag percakapan di-pin agar mudah diakses. Default: `false` |

#### Struktur Sub-field: `messages`

| Field | Tipe | Deskripsi |
| - | - | - |
| `role` | `enum` | Role pengirim pesan: `user` atau `assistant` |
| `content` | `string` | Isi pesan percakapan |

***

### Notification

Entitas notifikasi yang terintegrasi dengan sistem chat. Dipicu otomatis saat mengirim DM atau terjadi event penting lainnya.

| Field | Tipe | Required | Deskripsi |
| - | - | - | - |
| `user_id` | `string` (FK → User) | **Ya** | Email pengguna yang menerima notifikasi |
| `title` | `string` | **Ya** | Judul notifikasi |
| `message` | `string` | **Ya** | Pesan notifikasi |
| `description` | `string` | Tidak | Detail tambahan atau konteks notifikasi, maksimal 1000 karakter |
| `url` | `string` | Tidak | URL tujuan saat notifikasi diklik |
| `type` | `enum` | Tidak | Tipe notifikasi untuk menentukan render aksi. Default: `SYSTEM` |
| `metadata` | `object` | Tidak | Payload tambahan (mis. `invitationId`, `companyId`, `companyName`) |
| `priority` | `enum` | Tidak | Prioritas notifikasi: `low`, `normal`, `high`. Default: `normal` |
| `is_read` | `boolean` | Tidak | Status apakah notifikasi sudah dibaca. Default: `false` |
| `event_id` | `string` | Tidak | Idempotency key — notifikasi duplikat dengan `event_id` sama diabaikan |
| `company_id` | `string` (FK → Company) | Tidak | ID perusahaan untuk scope filter |
| `location_id` | `string` | Tidak | ID lokasi untuk scope filter (opsional) |
| `source_entity` | `string` | Tidak | Nama entity sumber (mis. `POSTransaction`, `Invoice`) |
| `source_id` | `string` | Tidak | ID record sumber |
| `correlation_id` | `string` | Tidak | Correlation ID dari request kritis untuk observability |

***

## Enum Reference Tables

### `TeamChat.message_type`

| Nilai | Deskripsi | Handling di UI |
| - | - | - |
| `text` | Pesan teks biasa | Render sebagai teks standar |
| `image` | Gambar | Upload ke Cloudinary, tampilkan inline dengan preview |
| `file` | File dokumen/lainnya | Upload ke Cloudinary, tampilkan sebagai link download |
| `system` | Pesan sistem otomatis | Render dengan styling berbeda (abu-abu, italic) |

### `ChatChannel.channel_type`

| Nilai | Deskripsi | Akses |
| - | - | - |
| `public` | Channel terbuka — semua anggota perusahaan bisa bergabung | Bebas bergabung |
| `private` | Channel tertutup — hanya anggota yang diundang bisa akses | Perlu di-invite oleh admin |
| `direct` | Channel direct message 1-to-1 | Otomatis untuk kedua peserta |

### `InternalMessage.message_type`

| Nilai | Deskripsi | Kasus Penggunaan |
| - | - | - |
| `direct` | Pesan langsung antar user | Komunikasi privat 1-to-1 |
| `announcement` | Pengumuman resmi | Broadcast informasi penting dengan `subject` |
| `workflow_alert` | Alert dari workflow otomatis | Notifikasi otomatis dari automasi bisnis |
| `system` | Pesan sistem | Notifikasi dari sistem (maintenance, update, dll) |

### `Conversation.messages[].role`

| Nilai | Deskripsi |
| - | - |
| `user` | Pesan dari pengguna |
| `assistant` | Pesan dari AI assistant |

### `Notification.type`

| Nilai | Deskripsi |
| - | - |
| `SYSTEM` | Notifikasi sistem umum |
| `INVITATION` | Undangan bergabung perusahaan — menampilkan tombol Terima/Tolak |
| `TASK` | Notifikasi terkait task |
| `STOCK` | Notifikasi stok barang |
| `INVOICE` | Notifikasi invoice |
| `WORKFLOW` | Notifikasi dari workflow automation |
| `ANNOUNCEMENT` | Pengumuman resmi |
| `ORDER` | Notifikasi pesanan |
| `PAYMENT` | Notifikasi pembayaran |
| `LOW_STOCK` | Peringatan stok rendah |
| `DISCREPANCY` | Notifikasi ketidaksesuaian data |
| `EXPIRY` | Peringatan masa kedaluwarsa |
| `BACKUP` | Notifikasi backup data |
| `BTT_PENDING` | Notifikasi BTT menunggu persetujuan |
| `PAYMENT_DUE` | Peringatan jatuh tempo pembayaran |
| `RECALL` | Notifikasi recall produk |

### `Notification.priority`

| Nilai | Deskripsi | Visual |
| - | - | - |
| `low` | Prioritas rendah — tidak mendesak | Warna netral |
| `normal` | Prioritas standar | Warna default |
| `high` | Prioritas tinggi — perlu perhatian segera | Warna menonjol / badge merah |

***

## State Diagrams

### Siklus Hidup Pesan (TeamChat)

```mermaid theme={null}
stateDiagram-v2
    [*] --> Draft: User mulai mengetik
    Draft --> Sending: User klik kirim
    Sending --> Sent: Base44 save + Pusher trigger
    Sending --> Failed: Error saat kirim
    Failed --> Sending: Retry
    Sent --> Read: Penerima membaca pesan
    Sent --> Edited: User edit pesan
    Edited --> Read: Penerima membaca setelah edit
    Read --> Deleted: User hapus pesan
    Sent --> Deleted: User hapus pesan
    Deleted --> [*]: is_deleted = true (soft delete)
    Sent --> Pinned: Admin pin pesan
    Pinned --> Sent: Admin unpin pesan
```

### Siklus Hidup Channel (ChatChannel)

```mermaid theme={null}
stateDiagram-v2
    [*] --> Creating: User klik "Create Channel"
    Creating --> Active: Channel berhasil dibuat
    Active --> Active: Pesan baru masuk
    Active --> Archived: Admin arsip channel
    Archived --> Active: Admin restore channel
    Archived --> [*]: Channel dihapus permanen
    Active --> [*]: Channel dihapus permanen

    state Active {
        [*] --> ReceivingMessages
        ReceivingMessages --> UnreadUpdate: Pesan baru dari member
        UnreadUpdate --> ReceivingMessages
    }
```

### Siklus Hidup Direct Message (InternalMessage)

```mermaid theme={null}
stateDiagram-v2
    [*] --> Composing: User buka DM
    Composing --> Sending: User klik kirim
    Sending --> Delivered: Pesan tersimpan di base44
    Sending --> Failed: Error koneksi
    Failed --> Sending: Retry kirim
    Delivered --> Read: Penerima buka pesan
    Read --> [*]: Percakapan selesai
    Delivered --> NotificationCreated: Trigger notifikasi
    NotificationCreated --> Read: Penerima buka dari notifikasi
```

***

## Sequence Diagrams

### Flow 1: Mengirim Pesan Channel

```mermaid theme={null}
sequenceDiagram
    participant UserA as User A (Pengirim)
    participant TC as TeamChat.jsx
    participant API as base44 API
    participant DB as base44 Database
    participant PT as pusherTrigger
    participant PUSHER as Pusher (ap1)
    participant UserB as User B (Penerima)

    UserA->>TC: Ketik pesan + klik kirim
    TC->>TC: Validasi input (message, channel_id)
    TC->>API: POST /api/TeamChat (create entity)
    API->>DB: INSERT INTO TeamChat
    DB-->>API: Return created record
    API-->>TC: Return TeamChat entity

    TC->>API: invoke('pusherTrigger', channel, event, data)
    API->>PT: Execute pusherTrigger function
    PT->>PUSHER: Trigger event 'new-message' on 'chat-{channelId}'

    Note over UserB: User B subscribed ke 'chat-{channelId}'
    PUSHER->>UserB: Event 'new-message' received
    UserB->>UserB: Append pesan baru ke UI
    UserB->>API: Mark as read (update read_by)
```

### Flow 2: Membuat Channel Baru

```mermaid theme={null}
sequenceDiagram
    participant User as User (Admin)
    participant TC as TeamChat.jsx
    participant API as base44 API
    participant DB as base44 Database
    participant PUSHER as Pusher (ap1)

    User->>TC: Buka form "Create Channel"
    TC->>TC: Input nama channel + tipe + deskripsi
    User->>TC: Submit form
    TC->>TC: Validasi nama unik dalam company
    TC->>API: POST /api/ChatChannel
    API->>DB: INSERT INTO ChatChannel
    Note over DB: Set default: unread_count = 0, is_archived = false
    DB-->>API: Return created channel
    API-->>TC: Return ChatChannel entity

    TC->>TC: Tambahkan channel ke sidebar list
    TC->>PUSHER: Subscribe 'chat-{newChannelId}'
    PUSHER-->>TC: Subscription confirmed

    Note over TC: Channel siap menerima pesan
```

### Flow 3: File Sharing (Upload Gambar)

```mermaid theme={null}
sequenceDiagram
    participant User as User
    participant TC as TeamChat.jsx
    participant IMG as compressImage()
    participant CLOUD as Cloudinary CDN
    participant API as base44 API
    participant DB as base44 Database
    participant PUSHER as Pusher (ap1)

    User->>TC: Klik ikon attachment + pilih gambar
    TC->>IMG: compressImage(file, max 1200px, quality 80%)
    IMG-->>TC: Return compressed blob

    TC->>CLOUD: uploadToCloudinary(compressedBlob)
    Note over CLOUD: Upload path: snishop_erp/chat
    CLOUD-->>TC: Return { url, filename, filesize, filetype }

    TC->>API: POST /api/TeamChat
    Note over API: message_type = 'image'<br/>attachments = [{url, filename, filesize, filetype}]
    API->>DB: INSERT INTO TeamChat
    DB-->>API: Return created record

    TC->>API: invoke('pusherTrigger')
    API->>PUSHER: Event 'new-message' with attachment data
    PUSHER-->>TC: Broadcast ke semua subscriber
```

### Flow 4: Direct Message (DM)

```mermaid theme={null}
sequenceDiagram
    participant UserA as User A (Pengirim)
    participant DMI as DirectMessageInbox.jsx
    participant API as base44 API
    participant DB as base44 Database
    participant NOTIF as Notification Service
    participant UserB as User B (Penerima)

    UserA->>DMI: Klik avatar User B
    DMI->>DMI: Open full-screen DM dialog
    DMI->>API: GET /api/InternalMessage?filter(sender, recipient)
    API->>DB: SELECTInternalMessage WHERE sender/recipient match
    DB-->>API: Return message history
    API-->>DMI: Display conversation

    UserA->>DMI: Ketik pesan + kirim
    DMI->>API: POST /api/InternalMessage
    API->>DB: INSERT INTO InternalMessage
    DB-->>API: Return created message

    DMI->>API: POST /api/Notification
    Note over API: type = 'ANNOUNCEMENT'<br/>priority = 'normal'
    API->>DB: INSERT INTO Notification (untuk User B)

    Note over DMI: Start polling setiap 4 detik
    loop Every 4 seconds
        DMI->>API: GET /api/InternalMessage (poll)
        API->>DB: SELECT new messages
        DB-->>API: Return unread messages
        API-->>DMI: Update UI jika ada pesan baru
    end
```

### Flow 5: Reply Thread

```mermaid theme={null}
sequenceDiagram
    participant User as User
    participant TC as TeamChat.jsx
    participant API as base44 API
    participant DB as base44 Database
    participant PUSHER as Pusher (ap1)

    User->>TC: Klik "Reply" pada pesan target
    TC->>TC: Tampilkan reply input dengan preview pesan asli
    Note over TC: reply_to = originalMessage.id<br/>reply_to_text = cuplikan pesan asli

    User->>TC: Ketik balasan + kirim
    TC->>API: POST /api/TeamChat (dengan reply_to)
    API->>DB: INSERT INTO TeamChat (reply_to = original_id)
    DB-->>API: Return created record

    TC->>API: invoke('pusherTrigger')
    API->>PUSHER: Event 'new-message' with reply context
    PUSHER-->>TC: Broadcast ke subscriber

    Note over TC: Reply ditampilkan inline di bawah pesan asli
```

***

## Real-Time Architecture

```mermaid theme={null}
sequenceDiagram
    participant UserA
    participant TeamChat
    participant Base44
    participant Pusher
    participant UserB

    UserA->>TeamChat: Kirim pesan
    TeamChat->>Base44: Save TeamChat entity
    TeamChat->>Base44: invoke('pusherTrigger')
    Base44->>Pusher: Event 'new-message'
    Pusher->>TeamChat: Event received (UserB)
    TeamChat->>TeamChat: Append message to UI

    Note over TeamChat: Channel subscription
    TeamChat->>Pusher: Subscribe 'chat-{channelId}'
    Pusher-->>TeamChat: Connection established
```

### Pusher Configuration

| Setting | Value |
| - | - |
| Cluster | `ap1` (Asia Pacific) |
| Channel pattern | `chat-{channelId}` |
| Events | `new-message`, `message-updated` |
| Client | Singleton di `pusherClient.js` (22 baris) |

### Direct Message Fallback

DM menggunakan **polling setiap 4 detik** sebagai fallback karena Pusher tidak selalu tersedia untuk 1-to-1 messaging:

```
setInterval → load InternalMessage → filter by sender/recipient → update UI
```

***

## Message Type Handling

| Type | Deskripsi | Handling |
| - | - | - |
| `text` | Pesan teks biasa | Render sebagai text |
| `image` | Gambar | Upload ke Cloudinary, tampilkan inline |
| `file` | File lainnya | Upload ke Cloudinary, tampilkan sebagai link download |
| `system` | Pesan sistem otomatis | Render dengan styling berbeda (gray, italic) |

### Image Upload Pipeline

```mermaid theme={null}
flowchart LR
    A[User pilih file] --> B[compressImage<br/>max 1200px, quality 80%]
    B --> C[uploadToCloudinary<br/>path: snishop_erp/chat]
    C --> D[attachment_url returned]
    D --> E[Save TeamChat entity<br/>message_type = 'image']
```

### Attachment Structure

Setiap attachment dalam field `attachments` memiliki struktur sebagai berikut:

| Field | Tipe | Deskripsi |
| - | - | - |
| `url` | `string` | URL file di Cloudinary CDN |
| `filename` | `string` | Nama file asli saat di-upload |
| `filesize` | `number` | Ukuran file dalam satuan byte |
| `filetype` | `string` | MIME type file (contoh: `image/png`, `application/pdf`, `image/jpeg`) |

***

## Date Grouping

Pesan dikelompokkan berdasarkan tanggal dengan label otomatis:

| Kondisi | Label |
| - | - |
| Tanggal = hari ini | "Hari Ini" |
| Tanggal = kemarin | "Kemarin" |
| Lainnya | Format tanggal Indonesia (dd MMMM yyyy) |

Implementasi menggunakan library `date-fns` dengan locale Indonesia untuk formatting yang konsisten.

***

## Direct Message (DM)

DirectMessageInbox (345 baris) menyediakan messaging 1-to-1 dalam full-screen dialog:

```mermaid theme={null}
flowchart TD
    A[Open DM Dialog] --> B[Load InternalMessage]
    B --> C[Group by conversation]
    C --> D[Display conversation list]
    D --> E{User pilih conversation}
    E -->|Ya| F[Load message history]
    F --> G[Display messages]
    G --> H[Send new message]
    H --> I[Create Notification]
    H --> J[4s polling for replies]
```

### Fitur DM

* Grouping percakapan per kontak
* Tracking read/unread status
* Auto-create **Notification** entity saat mengirim pesan
* Polling 4 detik untuk pesan baru
* Dukungan attachment via `attachment_url` dan `attachment_name`
* Link ke entitas terkait melalui `related_entity_type` dan `related_entity_id`

### Tipe Pesan InternalMessage

| Tipe | Deskripsi | Kapan Digunakan |
| - | - | - |
| `direct` | Pesan langsung privat | Komunikasi 1-to-1 antar anggota tim |
| `announcement` | Pengumuman resmi | Broadcast info penting dengan subjek |
| `workflow_alert` | Alert dari workflow | Notifikasi otomatis dari automasi |
| `system` | Pesan sistem | Maintenance, update, info sistem |

***

## Channel Management

| Aksi | Deskripsi |
| - | - |
| Create | Buat channel baru dengan nama unik |
| Search | Filter channel berdasarkan nama |
| Archive | Arsipkan channel yang tidak aktif |
| Unread Badge | Jumlah pesan belum dibaca per channel |

### Tipe Channel

| Tipe | Deskripsi | Visibilitas |
| - | - | - |
| `public` | Channel terbuka, semua anggota perusahaan bisa bergabung | Semua user bisa lihat dan join |
| `private` | Channel tertutup, hanya anggota yang di-invite | Hanya members dan admins |
| `direct` | Channel DM 1-to-1 | Hanya dua peserta |

### Manajemen Anggota Channel

| Role | Hak Akses |
| - | - |
| **Admin** (`admins` array) | Kelola channel, invite/remove member, archive channel, pin message |
| **Member** (`members` array) | Kirim pesan, baca pesan, reply, pin message |
| **Non-member** | Hanya bisa lihat channel public, tidak bisa kirim pesan |

***

## Message Actions

| Aksi | Deskripsi |
| - | - |
| **Reply** | Balas pesan tertentu — menampilkan reply thread |
| **Pin** | Pin pesan penting — muncul di atas channel |
| **Delete** | Soft delete — pesan ditandai `is_deleted = true` |
| **Edit** | Edit pesan — ditandai `is_edited = true` dengan timestamp `edited_at` |
| **Search** | Cari pesan dalam conversation berdasarkan kata kunci |
| **Mention** | Tag user spesifik — array email disimpan di field `mentions` |

***

## Read Receipts

Sistem read receipt memungkinkan pengirim mengetahui siapa saja yang sudah membaca pesannya:

| Field | Deskripsi |
| - | - |
| `read_by[].user_id` | ID user yang sudah membaca |
| `read_by[].read_at` | Timestamp kapan pesan dibaca |

Read receipt di-update saat user membuka channel atau DM yang berisi pesan tersebut.

***

## Scroll Behavior

Sistem mendeteksi posisi scroll dan menampilkan tombol "scroll down" jika user berada >100px dari bawah. Ini mencegah user kehilangan konteks saat pesan baru masuk.

```mermaid theme={null}
flowchart TD
    A[Pesan baru masuk] --> B{User scroll position}
    B -->|"< 100px dari bawah"| C[Auto-scroll ke bawah]
    B -->|"> 100px dari bawah"| T[Tampilkan tombol 'Scroll Down']
    T --> U[User klik tombol]
    U --> C
    C --> V[Scroll ke pesan terbaru]
```

***

## Company Scope Security

`DirectMessageInbox.companyScopePolicy.test.js` (16 baris) memverifikasi bahwa semua query DM menyertakan filter `company_id`, memastikan data tidak bocor lintas perusahaan.

### Isolasi Data per Entitas

| Entitas | Scope Field | Mekanisme |
| - | - | - |
| `TeamChat` | `company_id` | Semua query wajib filter by company |
| `ChatChannel` | `company_id` | Channel hanya visible dalam scope perusahaan |
| `InternalMessage` | `company_id` | DM di-isolasi per company (null = personal) |
| `Notification` | `company_id` | Notifikasi di-filter per company + optional `location_id` |

***

## RBAC Permission Matrix

Berikut adalah matriks hak akses berdasarkan role dalam konteks Team Chat:

| Aksi | Super Admin | Company Admin | Channel Admin | Member | Non-member |
| - | - | - | - | - | - |
| Buat channel | ✅ | ✅ | ✅ | ✅ (public only) | ❌ |
| Hapus channel | ✅ | ✅ | ✅ (own channels) | ❌ | ❌ |
| Archive channel | ✅ | ✅ | ✅ | ❌ | ❌ |
| Invite member (private) | ✅ | ✅ | ✅ | ❌ | ❌ |
| Kirim pesan | ✅ | ✅ | ✅ | ✅ | ❌ |
| Edit pesan sendiri | ✅ | ✅ | ✅ | ✅ | ❌ |
| Hapus pesan sendiri | ✅ | ✅ | ✅ | ✅ | ❌ |
| Pin pesan | ✅ | ✅ | ✅ | ✅ | ❌ |
| Baca pesan | ✅ | ✅ | ✅ | ✅ | ✅ (public) |
| Lihat DM | ✅ | ✅ | ❌ | ✅ (own) | ❌ |
| Kelola notifikasi | ✅ | ✅ | ❌ | ❌ | ❌ |

***

## Integrasi Eksternal

| Layanan | Fungsi |
| - | - |
| **Pusher** | WebSocket real-time messaging |
| **Cloudinary** | Upload gambar dan file attachment |
| **base44 functions** | `pusherTrigger` untuk broadcast event |
| **date-fns** | Format tanggal dengan locale Indonesia |
| **framer-motion** | Stagger animations untuk pesan baru |

### Detail Integrasi Cloudinary

| Setting | Value |
| - | - |
| Upload path | `snishop_erp/chat` |
| Image compression | Max 1200px, quality 80% |
| Supported formats | Image (PNG, JPG, GIF, WebP), dokumen (PDF, DOCX, XLSX) |
| Delivery | Via CDN global Cloudinary |

***

## Cara Akses

Dari sidebar, klik menu **Communication** > **Team Chat**.

***

## Flow Penggunaan

1. Buka Team Chat dari sidebar — pilih channel yang sudah ada atau buat baru
2. Ketik pesan di input box dan kirim — pesan langsung muncul di semua client via Pusher
3. Untuk kirim gambar, klik ikon attachment — gambar dikompresi otomatis sebelum upload
4. Balas pesan tertentu dengan klik **Reply** — reply thread ditampilkan inline
5. Pin pesan penting dengan klik **Pin** — muncul di atas channel untuk referensi cepat
6. Untuk direct message, klik avatar user — DirectMessageInbox terbuka
7. Gunakan search bar untuk mencari pesan lama dalam channel
8. Edit pesan yang sudah terkirim — ditandai dengan label "edited" dan timestamp `edited_at`
9. Mention anggota tim spesifik untuk menarik perhatian mereka

***

## Tips

* Buat channel per proyek atau divisi supaya diskusi tetap terfokus dan tidak tercampur
* Gunakan reply thread untuk menjaga konteks percakapan — hindari pesan yang melenceng dari topik
* Pin pesan yang berisi keputusan penting atau informasi yang sering dirujuk
* Untuk diskusi yang perlu keputusan formal, gunakan meeting langsung — chat cocok untuk koordinasi cepat
* Manfaatkan fitur `mentions` untuk menotifikasi anggota tim spesifik dalam pesan channel
* Gunakan channel `private` untuk diskusi sensitif yang tidak boleh dilihat semua anggota
* Kompresi gambar otomatis memastikan upload cepat bahkan dengan koneksi lambat
* Untuk pesan yang berkaitan dengan entitas bisnis tertentu (task, invoice), gunakan `InternalMessage` dengan `related_entity_type` agar mudah ditelusuri

***

## Troubleshooting

| Masalah | Kemungkinan Penyebab | Solusi |
| - | - | - |
| Pesan tidak muncul real-time | Koneksi Pusher terputus | Periksa koneksi internet, cek console untuk error WebSocket |
| DM tidak update | Polling gagal | Periksa koneksi API, refresh halaman |
| Upload gambar gagal | Ukuran file terlalu besar | Gunakan format yang didukung, pastikan file \< max size Cloudinary |
| Channel tidak muncul | Filter company\_id salah | Logout dan login kembali untuk refresh session |
| Notifikasi tidak muncul | Notification entity tidak terbuat | Periksa apakah DM berhasil terkirim |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.