forked from RiseUP/riseup_squad_03
modified: src/App.jsx
modified: src/components/AppShell.jsx modified: src/config/api.js modified: src/config/permissions.js modified: src/data/mockData.js modified: src/hooks/useAgenda.js modified: src/hooks/useAuth.js modified: src/mappers/appointmentMapper.js modified: src/pages/AgendaPage.jsx modified: src/pages/AuthPages.jsx modified: src/pages/HomePage.jsx modified: src/pages/MedicalRecordsPage.jsx modified: src/pages/MessagesPage.jsx modified: src/pages/NotFoundPage.jsx modified: src/pages/PatientsPage.jsx modified: src/pages/ReportsPage.jsx modified: src/pages/TeamPage.jsx modified: src/pages/UsersPage.jsx modified: src/pages/VisitsPage.jsx modified: src/repositories/authRepository.js new file: src/repositories/availabilityRepository.js modified: src/repositories/communicationRepository.js modified: src/repositories/patientRepository.js modified: src/repositories/professionalRepository.js modified: src/repositories/profileRepository.js modified: src/repositories/reportRepository.js modified: src/repositories/repositoryUtils.js modified: src/repositories/settingsRepository.js modified: src/repositories/userRepository.js modified: src/repositories/visitRepository.js
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
|
||||
import { normalizeRole } from '../config/permissions.js'
|
||||
import { FeatureCallout } from '../components/FeatureState.jsx'
|
||||
import { featurePanelClass } from '../components/featureStateStyles.js'
|
||||
import { communicationRepository } from '../repositories/communicationRepository.js'
|
||||
@@ -40,7 +41,13 @@ const textareaClass =
|
||||
'min-h-28 w-full resize-y rounded-sm border border-[#404040] bg-[#171717] px-3 py-2 text-sm leading-6 text-[#e5e5e5] outline-none transition placeholder:text-[#a3a3a3] focus:border-[#3b82f6] focus:ring-2 focus:ring-[#3b82f6]/20'
|
||||
const labelClass = 'text-xs font-semibold uppercase tracking-[0.08em] text-[#a3a3a3]'
|
||||
|
||||
export function MessagesPage() {
|
||||
export function MessagesPage({ role }) {
|
||||
const normalizedRole = normalizeRole(role)
|
||||
const isSecretary = normalizedRole === 'secretaria'
|
||||
const allowedChannelKeys = useMemo(
|
||||
() => (isSecretary ? ['whatsapp', 'sms'] : Object.keys(channels)),
|
||||
[isSecretary],
|
||||
)
|
||||
const campaigns = communicationRepository.getCampaigns()
|
||||
const [messages, setMessages] = useState(() => communicationRepository.getInitialMessages())
|
||||
const [templates, setTemplates] = useState(() => communicationRepository.getInitialTemplates())
|
||||
@@ -51,10 +58,15 @@ export function MessagesPage() {
|
||||
const [templateEditorOpen, setTemplateEditorOpen] = useState(false)
|
||||
const [composer, setComposer] = useState(emptyMessage)
|
||||
const [templateDraft, setTemplateDraft] = useState(emptyTemplate)
|
||||
const availableTemplates = useMemo(
|
||||
() => templates.filter((template) => allowedChannelKeys.includes(template.channel)),
|
||||
[allowedChannelKeys, templates],
|
||||
)
|
||||
|
||||
const filteredMessages = useMemo(
|
||||
() =>
|
||||
messages.filter((message) => {
|
||||
const isAllowedChannel = allowedChannelKeys.includes(message.channel)
|
||||
const matchesChannel = channelFilter === 'todos' || message.channel === channelFilter
|
||||
const query = search.trim().toLowerCase()
|
||||
const matchesSearch =
|
||||
@@ -64,22 +76,24 @@ export function MessagesPage() {
|
||||
.toLowerCase()
|
||||
.includes(query)
|
||||
|
||||
return matchesChannel && matchesSearch
|
||||
return isAllowedChannel && matchesChannel && matchesSearch
|
||||
}),
|
||||
[channelFilter, messages, search],
|
||||
[allowedChannelKeys, channelFilter, messages, search],
|
||||
)
|
||||
|
||||
const stats = useMemo(
|
||||
() => ({
|
||||
total: messages.length,
|
||||
delivered: messages.filter((message) => message.status === 'entregue' || message.status === 'lida').length,
|
||||
read: messages.filter((message) => message.status === 'lida').length,
|
||||
failed: messages.filter((message) => message.status === 'falha').length,
|
||||
total: messages.filter((message) => allowedChannelKeys.includes(message.channel)).length,
|
||||
delivered: messages.filter((message) => allowedChannelKeys.includes(message.channel) && (message.status === 'entregue' || message.status === 'lida')).length,
|
||||
read: messages.filter((message) => allowedChannelKeys.includes(message.channel) && message.status === 'lida').length,
|
||||
failed: messages.filter((message) => allowedChannelKeys.includes(message.channel) && message.status === 'falha').length,
|
||||
}),
|
||||
[messages],
|
||||
[allowedChannelKeys, messages],
|
||||
)
|
||||
|
||||
function openTemplate(template) {
|
||||
if (!allowedChannelKeys.includes(template.channel)) return
|
||||
|
||||
setComposer({
|
||||
patient: '',
|
||||
phone: '',
|
||||
@@ -97,6 +111,11 @@ export function MessagesPage() {
|
||||
return
|
||||
}
|
||||
|
||||
if (!allowedChannelKeys.includes(composer.channel)) {
|
||||
alert('Canal indisponivel para o seu perfil.')
|
||||
return
|
||||
}
|
||||
|
||||
let smsSent = false
|
||||
|
||||
if (composer.channel === 'sms') {
|
||||
@@ -158,26 +177,28 @@ export function MessagesPage() {
|
||||
return (
|
||||
<div className="mx-auto max-w-7xl space-y-6">
|
||||
<FeatureCallout
|
||||
description="Envio de SMS usa API. Histórico, templates e campanhas ainda são dados locais de demonstração."
|
||||
description={isSecretary ? 'Perfil Secretária limitado a comunicação básica por WhatsApp e SMS.' : 'Envio de SMS usa API. Histórico, templates e campanhas ainda são dados locais de demonstração.'}
|
||||
status="partial"
|
||||
title="Mensageria híbrida"
|
||||
title={isSecretary ? 'Comunicação basica' : 'Mensageria hibrida'}
|
||||
/>
|
||||
|
||||
<div className="flex flex-col items-start justify-between gap-4 md:flex-row md:items-center">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-[#f5f5f5]">Comunicação</h1>
|
||||
<p className="mt-1 text-sm text-[#b8b8b8]">WhatsApp, E-mail e SMS - histórico e campanhas</p>
|
||||
<p className="mt-1 text-sm text-[#b8b8b8]">{isSecretary ? 'WhatsApp e SMS para contato operacional com pacientes' : 'WhatsApp, E-mail e SMS - historico e campanhas'}</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
className="inline-flex h-12 items-center gap-2 rounded-sm border border-[#404040] bg-[#262626] px-4 text-sm font-semibold text-[#e5e5e5] transition hover:bg-[#303030]"
|
||||
onClick={() => setActiveTab('campanha')}
|
||||
type="button"
|
||||
>
|
||||
<CommIcon className="size-4" name="send" />
|
||||
Envio em Massa
|
||||
</button>
|
||||
{!isSecretary ? (
|
||||
<button
|
||||
className="inline-flex h-12 items-center gap-2 rounded-sm border border-[#404040] bg-[#262626] px-4 text-sm font-semibold text-[#e5e5e5] transition hover:bg-[#303030]"
|
||||
onClick={() => setActiveTab('campanha')}
|
||||
type="button"
|
||||
>
|
||||
<CommIcon className="size-4" name="send" />
|
||||
Envio em Massa
|
||||
</button>
|
||||
) : null}
|
||||
<button
|
||||
className="inline-flex h-12 items-center gap-2 rounded-sm bg-[#3b82f6] px-4 text-sm font-semibold text-white transition hover:bg-[#2563eb]"
|
||||
onClick={() => setComposerOpen(true)}
|
||||
@@ -199,8 +220,7 @@ export function MessagesPage() {
|
||||
<div className="flex gap-4 border-b border-[#404040]">
|
||||
{[
|
||||
['historico', 'Histórico'],
|
||||
['templates', 'Templates'],
|
||||
['campanha', 'Campanhas'],
|
||||
...(!isSecretary ? [['templates', 'Templates'], ['campanha', 'Campanhas']] : []),
|
||||
].map(([key, label]) => (
|
||||
<button
|
||||
className={`border-b-2 px-2 pb-3 text-sm font-semibold transition ${
|
||||
@@ -238,9 +258,7 @@ export function MessagesPage() {
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{[
|
||||
['todos', 'Todos'],
|
||||
['whatsapp', 'Whatsapp'],
|
||||
['email', 'E-mail'],
|
||||
['sms', 'Sms'],
|
||||
...allowedChannelKeys.map((key) => [key, channels[key].label]),
|
||||
].map(([key, label]) => (
|
||||
<button
|
||||
className={`h-12 rounded-sm border px-4 text-xs font-semibold transition ${
|
||||
@@ -300,7 +318,7 @@ export function MessagesPage() {
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
|
||||
{templates.map((template) => (
|
||||
{availableTemplates.map((template) => (
|
||||
<TemplateCard key={template.id} onUse={openTemplate} template={template} />
|
||||
))}
|
||||
</div>
|
||||
@@ -363,6 +381,7 @@ export function MessagesPage() {
|
||||
|
||||
{composerOpen ? (
|
||||
<MessageComposer
|
||||
allowedChannelKeys={allowedChannelKeys}
|
||||
draft={composer}
|
||||
onChange={setComposer}
|
||||
onClose={() => {
|
||||
@@ -370,12 +389,13 @@ export function MessagesPage() {
|
||||
setComposer(emptyMessage)
|
||||
}}
|
||||
onSubmit={submitMessage}
|
||||
templates={templates}
|
||||
templates={availableTemplates}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{templateEditorOpen ? (
|
||||
<TemplateEditor
|
||||
allowedChannelKeys={allowedChannelKeys}
|
||||
draft={templateDraft}
|
||||
onChange={setTemplateDraft}
|
||||
onClose={() => {
|
||||
@@ -459,7 +479,7 @@ function TemplateCard({ onUse, template }) {
|
||||
)
|
||||
}
|
||||
|
||||
function MessageComposer({ draft, onChange, onClose, onSubmit, templates }) {
|
||||
function MessageComposer({ allowedChannelKeys, draft, onChange, onClose, onSubmit, templates }) {
|
||||
function update(field, value) {
|
||||
onChange((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
@@ -494,9 +514,9 @@ function MessageComposer({ draft, onChange, onClose, onSubmit, templates }) {
|
||||
</DarkField>
|
||||
<DarkField label="Canal">
|
||||
<select className={inputClass} onChange={(event) => update('channel', event.target.value)} value={draft.channel}>
|
||||
<option value="whatsapp">WhatsApp</option>
|
||||
<option value="email">E-mail</option>
|
||||
<option value="sms">SMS</option>
|
||||
{allowedChannelKeys.map((key) => (
|
||||
<option key={key} value={key}>{channels[key].label}</option>
|
||||
))}
|
||||
</select>
|
||||
</DarkField>
|
||||
</div>
|
||||
@@ -549,7 +569,7 @@ function MessageComposer({ draft, onChange, onClose, onSubmit, templates }) {
|
||||
)
|
||||
}
|
||||
|
||||
function TemplateEditor({ draft, onChange, onClose, onSubmit }) {
|
||||
function TemplateEditor({ allowedChannelKeys, draft, onChange, onClose, onSubmit }) {
|
||||
function update(field, value) {
|
||||
onChange((current) => ({ ...current, [field]: value }))
|
||||
}
|
||||
@@ -563,9 +583,9 @@ function TemplateEditor({ draft, onChange, onClose, onSubmit }) {
|
||||
</DarkField>
|
||||
<DarkField label="Canal">
|
||||
<select className={inputClass} onChange={(event) => update('channel', event.target.value)} value={draft.channel}>
|
||||
<option value="whatsapp">WhatsApp</option>
|
||||
<option value="email">E-mail</option>
|
||||
<option value="sms">SMS</option>
|
||||
{allowedChannelKeys.map((key) => (
|
||||
<option key={key} value={key}>{channels[key].label}</option>
|
||||
))}
|
||||
</select>
|
||||
</DarkField>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user