Accumulated work on the dispatch/legacy-writeback branch: - Communications UI: CommunicationsPage, ConversationFullPage, DepartmentBoard, PipelineBoard, ReaderStack, Orchestrator/NewTicket/ServiceStatus/Outbox dialogs; hub gmail.js, ticket-collab.js, outbox.js, coupon-triage.js, client-diag.js. - Billing/sync mirror (F→ERPNext): legacy-payments.js, legacy-sync.js, sync-orchestrator.js, supplier-invoices.js, municipality.js + incremental migration scripts; LegacySyncPage, SupplierInvoices + negative-billing / terminated-active reports. - Roster/campaigns/network/voice: roster + roster-assistant, campaigns, giftbit, olt-snmp, traccar, twilio, vision, tech-absence-sms, ai/agent/config/helpers, legacy-dispatch-sync; ops PlanificationPage, RapportsPage, Settings, Tickets, ClientDetail updates. - docs/ PLATFORM_GUIDE + UI_AND_OPTIMIZATION; .gitignore __pycache__. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
34 lines
1.5 KiB
Vue
34 lines
1.5 KiB
Vue
<template>
|
|
<div v-if="others.length" class="rstack">
|
|
<q-avatar v-for="r in head" :key="r.email" :size="size + 'px'" class="rstack-av"
|
|
:style="{ background: staffColor(r.email), color: '#fff', fontSize: Math.round(size * 0.42) + 'px' }">
|
|
{{ staffInitials(noteAuthorName(r.email)) }}
|
|
<q-tooltip>{{ shortAgent(r.email) }}<template v-if="r.readAt"> · {{ relTime(r.readAt) }}</template></q-tooltip>
|
|
</q-avatar>
|
|
<span v-if="extra > 0" class="rstack-more">+{{ extra }}</span>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed } from 'vue'
|
|
import { staffColor, staffInitials, noteAuthorName, shortAgent, relTime } from 'src/composables/useFormatters'
|
|
|
|
const props = defineProps({
|
|
readers: { type: Array, default: () => [] }, // [{ email, readAt }]
|
|
me: { type: String, default: '' },
|
|
max: { type: Number, default: 3 },
|
|
size: { type: Number, default: 20 },
|
|
})
|
|
// Exclut soi-même (façon Messenger : on voit les AUTRES qui ont lu)
|
|
const others = computed(() => (props.readers || []).filter(r => r && r.email && r.email !== props.me))
|
|
const head = computed(() => others.value.slice(0, props.max))
|
|
const extra = computed(() => Math.max(0, others.value.length - props.max))
|
|
</script>
|
|
|
|
<style scoped>
|
|
.rstack { display: flex; align-items: center; }
|
|
.rstack-av { margin-left: -6px; border: 1.5px solid #fff; font-weight: 600; }
|
|
.rstack-av:first-child { margin-left: 0; }
|
|
.rstack-more { font-size: 0.66rem; color: #64748b; margin-left: 5px; font-weight: 600; }
|
|
</style>
|