feat: destaca dados mockados e WIP no app

This commit is contained in:
EdilbertoC
2026-04-28 10:34:05 -03:00
parent 7199c107f2
commit 767f226952
14 changed files with 209 additions and 25 deletions

View File

@@ -1,6 +1,7 @@
import { useMemo, useState } from 'react'
import { BrandLogo } from './Brand.jsx'
import { FeatureLegend } from './FeatureState.jsx'
const navItems = [
{ href: '/inicio', label: 'Painel', icon: 'pulse', activePaths: ['/inicio', '/home', '/dashboard'] },
@@ -176,6 +177,9 @@ export function AppShell({ children, currentPath, navigate, routeTitle }) {
</header>
<main className="w-full px-4 py-6 md:px-8 md:py-8" id="app-content">
<div className="mb-6">
<FeatureLegend />
</div>
<div className="sr-only" aria-live="polite">
{pageTitle}
</div>

View File

@@ -0,0 +1,39 @@
import { featureStateStyles } from './featureStateStyles.js'
export function FeatureBadge({ className = '', status = 'partial', text }) {
const current = featureStateStyles[status] || featureStateStyles.partial
return (
<span
className={`inline-flex items-center rounded-full border px-2.5 py-1 text-[10px] font-bold uppercase tracking-[0.14em] ${current.badge} ${className}`}
>
{text || current.label}
</span>
)
}
export function FeatureCallout({ className = '', description, status = 'partial', title }) {
const current = featureStateStyles[status] || featureStateStyles.partial
return (
<div className={`rounded-2xl border px-4 py-3 ${current.panel} ${className}`}>
<div className="flex flex-wrap items-center gap-3">
<FeatureBadge status={status} />
{title ? <p className={`text-sm font-semibold ${current.title}`}>{title}</p> : null}
</div>
{description ? <p className="mt-2 text-sm leading-6 text-[#d4d4d4]">{description}</p> : null}
</div>
)
}
export function FeatureLegend() {
return (
<div className="flex flex-wrap items-center gap-2 rounded-xl border border-[#404040] bg-[#202020] px-3 py-2">
<span className="text-[11px] font-semibold uppercase tracking-[0.14em] text-[#a3a3a3]">Legenda</span>
<FeatureBadge status="live" />
<FeatureBadge status="partial" />
<FeatureBadge status="mock" />
<FeatureBadge status="wip" />
</div>
)
}

View File

@@ -0,0 +1,31 @@
export const featureStateStyles = {
live: {
badge: 'border-emerald-500/40 bg-emerald-500/15 text-emerald-300',
panel: 'border-emerald-500/35 bg-emerald-500/8',
title: 'text-emerald-300',
label: 'Integrado',
},
partial: {
badge: 'border-sky-500/40 bg-sky-500/15 text-sky-300',
panel: 'border-sky-500/35 bg-sky-500/8',
title: 'text-sky-300',
label: 'Parcial',
},
mock: {
badge: 'border-amber-500/40 bg-amber-500/15 text-amber-300',
panel: 'border-amber-500/35 bg-amber-500/8',
title: 'text-amber-300',
label: 'Mockado',
},
wip: {
badge: 'border-rose-500/40 bg-rose-500/15 text-rose-300',
panel: 'border-rose-500/35 bg-rose-500/8',
title: 'text-rose-300',
label: 'WIP',
},
}
export function featurePanelClass(status = 'partial') {
const current = featureStateStyles[status] || featureStateStyles.partial
return current.panel
}

View File

@@ -1,6 +1,8 @@
import { useEffect, useMemo, useState } from 'react'
import { appointmentRepository } from '../repositories/appointmentRepository.js'
import { FeatureBadge, FeatureCallout } from '../components/FeatureState.jsx'
import { featurePanelClass } from '../components/featureStateStyles.js'
import { patientRepository } from '../repositories/patientRepository.js'
import { professionalRepository } from '../repositories/professionalRepository.js'
@@ -88,6 +90,12 @@ useEffect(() => {
return (
<div className="mx-auto flex max-w-[1180px] flex-col gap-8 text-[#e5e5e5]">
<FeatureCallout
description="Listagem e criação de agendamentos usam API. Linha do tempo, resumo preditivo e calendário semanal ainda são visuais simulados."
status="partial"
title="Agenda com partes reais e partes mockadas"
/>
<section className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="text-[32px] font-bold leading-8 tracking-[-0.02em] text-[#e5e5e5]">
@@ -116,7 +124,7 @@ useEffect(() => {
</div>
</section>
<section className="grid gap-4 lg:grid-cols-5">
<section className={`grid gap-4 lg:grid-cols-5 ${featurePanelClass('mock')}`}>
{weekDays.map((day) => (
<button
className={`rounded-2xl border p-4 text-left transition ${
@@ -137,10 +145,13 @@ useEffect(() => {
</section>
<section className="grid gap-6 xl:grid-cols-[1.45fr_0.85fr]">
<div className="rounded-2xl border border-[#404040] bg-[#262626] p-5 shadow-[0_1px_3px_rgba(0,0,0,0.2)]">
<div className={`rounded-2xl border bg-[#262626] p-5 shadow-[0_1px_3px_rgba(0,0,0,0.2)] ${featurePanelClass('live')}`}>
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div>
<h2 className="text-base font-bold leading-6 text-[#e5e5e5]">Terça, 07 abril</h2>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-base font-bold leading-6 text-[#e5e5e5]">Terça, 07 abril</h2>
<FeatureBadge status="live" />
</div>
<p className="mt-1 text-sm leading-5 text-[#a3a3a3]">
Visualização: {activeView.toLowerCase()} | {visibleAppointments.length} registros no filtro
</p>
@@ -202,8 +213,11 @@ useEffect(() => {
</div>
<div className="grid gap-6">
<div className="rounded-2xl border border-[#404040] bg-[#262626] p-5">
<h2 className="text-base font-bold text-[#e5e5e5]">Linha do tempo</h2>
<div className={`rounded-2xl border bg-[#262626] p-5 ${featurePanelClass('mock')}`}>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-base font-bold text-[#e5e5e5]">Linha do tempo</h2>
<FeatureBadge status="mock" />
</div>
<div className="mt-5 grid gap-1">
{timeline.map((item) => (
<button
@@ -223,8 +237,11 @@ useEffect(() => {
</div>
</div>
<div className="rounded-2xl border border-[#404040] bg-[#262626] p-5">
<h2 className="text-base font-bold text-[#e5e5e5]">Resumo preditivo</h2>
<div className={`rounded-2xl border bg-[#262626] p-5 ${featurePanelClass('mock')}`}>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-base font-bold text-[#e5e5e5]">Resumo preditivo</h2>
<FeatureBadge status="mock" />
</div>
<div className="mt-5 grid gap-3">
{queue.map((item) => (
<div className="flex items-center justify-between rounded-md bg-[#2a2a2a] px-4 py-3" key={item.label}>

View File

@@ -1,5 +1,6 @@
import { useState } from 'react'
import { FeatureCallout } from '../components/FeatureState.jsx'
import { analyticsRepository } from '../repositories/analyticsRepository.js'
const periods = [
@@ -25,6 +26,12 @@ export function AnalyticsPage() {
return (
<div className="mx-auto max-w-7xl space-y-6">
<FeatureCallout
description="Os indicadores, gráficos e rankings desta tela ainda vêm de dados mockados."
status="mock"
title="Analytics ainda é demonstrativo"
/>
<section 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]">Relatórios & Analytics</h1>

View File

@@ -3,6 +3,7 @@ import { useState } from 'react'
import { authRepository } from '../repositories/authRepository.js'
import { BrandLogo } from '../components/Brand.jsx'
import { FeatureBadge, FeatureCallout } from '../components/FeatureState.jsx'
import loginClinicImage from '../assets/figma/login-clinic.png'
export function LoginPage({ navigate }) {
@@ -163,6 +164,7 @@ export function LoginPage({ navigate }) {
type="button"
>
dev · credenciais
<FeatureBadge className="border-white/20 bg-white/10 text-white/70" status="mock" text="mock" />
<span aria-hidden="true" className="text-[9px]">
^
</span>
@@ -181,6 +183,12 @@ export function RegisterPage({ navigate }) {
description="Crie um acesso mockado para navegar pelo ambiente da clínica."
title="Criar acesso"
>
<FeatureCallout
className="mt-6"
description="Cadastro ainda é apenas demonstrativo e não cria conta real."
status="mock"
title="Fluxo mockado"
/>
<form
className="mt-8 grid gap-5"
onSubmit={(event) => {

View File

@@ -1,4 +1,6 @@
import loginClinicImage from '../assets/figma/login-clinic.png'
import { FeatureBadge, FeatureCallout } from '../components/FeatureState.jsx'
import { featurePanelClass } from '../components/featureStateStyles.js'
import { homeRepository } from '../repositories/homeRepository.js'
export function HomePage({ navigate }) {
@@ -6,6 +8,12 @@ export function HomePage({ navigate }) {
return (
<div className="mx-auto flex max-w-[1180px] flex-col gap-8 text-[#e5e5e5]">
<FeatureCallout
description="Painel, métricas, insights e relatórios desta tela ainda usam dados mockados."
status="mock"
title="Visão geral ainda não está ligada à API"
/>
<section className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<h1 className="text-[32px] font-bold leading-8 tracking-[-0.02em] text-[#e5e5e5]">
@@ -41,14 +49,17 @@ export function HomePage({ navigate }) {
</section>
<section className="grid gap-6 xl:grid-cols-[1.7fr_0.9fr]">
<div className="rounded-2xl border border-[#3b82f6] bg-[#262626] p-5 shadow-[0_1px_3px_rgba(0,0,0,0.2)]">
<div className={`rounded-2xl border bg-[#262626] p-5 shadow-[0_1px_3px_rgba(0,0,0,0.2)] ${featurePanelClass('mock')}`}>
<div className="flex items-start justify-between gap-4">
<div className="flex items-start gap-4">
<div className="grid size-12 shrink-0 place-items-center rounded-md bg-[#3b82f6] text-white">
<SparkLineIcon className="size-6" />
</div>
<div>
<h2 className="text-base font-bold leading-6 text-[#3b82f6]">Insights de IA</h2>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-base font-bold leading-6 text-[#3b82f6]">Insights de IA</h2>
<FeatureBadge status="mock" />
</div>
<p className="mt-1 text-sm font-medium leading-5 text-[#a3a3a3]">
Evolução de absenteísmo e risco da semana
</p>
@@ -65,8 +76,11 @@ export function HomePage({ navigate }) {
</div>
<div className="grid gap-4">
<div className="rounded-2xl border border-[#404040] bg-[#262626] p-5">
<h2 className="text-base font-bold text-[#e5e5e5]">Pacientes de hoje</h2>
<div className={`rounded-2xl border bg-[#262626] p-5 ${featurePanelClass('mock')}`}>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-base font-bold text-[#e5e5e5]">Pacientes de hoje</h2>
<FeatureBadge status="mock" />
</div>
<div className="mt-4 grid gap-3">
{appointmentsToday.map((item) => (
<button
@@ -85,8 +99,11 @@ export function HomePage({ navigate }) {
</div>
</div>
<div className="rounded-2xl border border-[#404040] bg-[#262626] p-5">
<h2 className="text-base font-bold text-[#e5e5e5]">Alerta preditivo</h2>
<div className={`rounded-2xl border bg-[#262626] p-5 ${featurePanelClass('mock')}`}>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-base font-bold text-[#e5e5e5]">Alerta preditivo</h2>
<FeatureBadge status="mock" />
</div>
<p className="mt-3 text-sm leading-6 text-[#a3a3a3]">
3 pacientes apresentam risco de falta. Recomenda-se confirmar presença antes das 16h.
</p>
@@ -102,7 +119,10 @@ export function HomePage({ navigate }) {
</section>
<section className="grid gap-4" id="relatorios">
<h2 className="text-base font-bold text-[#e5e5e5]">Relatórios e Análises</h2>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-base font-bold text-[#e5e5e5]">Relatórios e Análises</h2>
<FeatureBadge status="mock" />
</div>
<div className="grid gap-4 lg:grid-cols-2">
<button
className="relative min-h-[164px] overflow-hidden rounded-2xl border border-[#3b82f6] bg-[#262626] p-5 text-left shadow-[0_1px_3px_rgba(0,0,0,0.2)]"

View File

@@ -1,5 +1,6 @@
import { useMemo, useState } from 'react'
import { FeatureCallout } from '../components/FeatureState.jsx'
import { medicalRecordRepository } from '../repositories/medicalRecordRepository.js'
@@ -34,6 +35,12 @@ export function MedicalRecordsPage() {
return (
<div className="mx-auto max-w-7xl space-y-6 text-[#e5e5e5]">
<FeatureCallout
description="Prontuário, listagem e criação de registros ainda usam dados locais e não persistem na API."
status="mock"
title="Prontuário ainda é mockado"
/>
<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-[#e5e5e5]">Prontuário Médico</h1>

View File

@@ -1,5 +1,7 @@
import { useMemo, useState } from 'react'
import { FeatureCallout } from '../components/FeatureState.jsx'
import { featurePanelClass } from '../components/featureStateStyles.js'
import { communicationRepository } from '../repositories/communicationRepository.js'
const channels = {
@@ -155,6 +157,12 @@ 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."
status="partial"
title="Mensageria híbrida"
/>
<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>
@@ -210,7 +218,7 @@ export function MessagesPage() {
</div>
{activeTab === 'historico' ? (
<section className={`${cardClass} p-5 md:p-6`} aria-label="Histórico de comunicação">
<section className={`${cardClass} ${featurePanelClass('mock')} p-5 md:p-6`} aria-label="Histórico de comunicação">
<div className="mb-6 flex flex-col gap-3 md:flex-row">
<label className="relative flex-1">
<span className="sr-only">Buscar comunicação</span>
@@ -279,7 +287,7 @@ export function MessagesPage() {
) : null}
{activeTab === 'templates' ? (
<section className="space-y-4" aria-label="Templates de comunicação">
<section className={`space-y-4 rounded-2xl p-4 ${featurePanelClass('mock')}`} aria-label="Templates de comunicação">
<div className="flex justify-end">
<button
className="inline-flex h-10 items-center gap-2 rounded-sm bg-[#3b82f6] px-4 text-sm font-semibold text-white transition hover:bg-[#2563eb]"
@@ -300,7 +308,7 @@ export function MessagesPage() {
) : null}
{activeTab === 'campanha' ? (
<section className={`${cardClass} p-6`} aria-label="Campanhas inteligentes">
<section className={`${cardClass} ${featurePanelClass('mock')} p-6`} aria-label="Campanhas inteligentes">
<div className="py-8 text-center">
<div className="mx-auto mb-4 grid size-16 place-items-center rounded-full bg-[#303030]">
<CommIcon className="size-8 text-[#51a2ff]" name="send" />

View File

@@ -1,5 +1,7 @@
import { useRef, useState, useEffect } from 'react'
import { FeatureCallout } from '../components/FeatureState.jsx'
import { featurePanelClass } from '../components/featureStateStyles.js'
import { profileRepository } from '../repositories/profileRepository.js'
import { authRepository } from '../repositories/authRepository.js'
@@ -59,13 +61,19 @@ export function ProfilePage({ navigate }) {
return (
<div className="mx-auto max-w-6xl space-y-6">
<FeatureCallout
description="Carregar perfil, avatar e logout usam integração. O botão de salvar preferências desta tela ainda grava só localmente."
status="partial"
title="Perfil com persistência parcial"
/>
<header>
<h1 className="text-2xl font-bold tracking-tight text-[#f5f5f5]">Perfil</h1>
<p className="mt-1 text-sm text-[#b8b8b8]">Dados locais do usuário logado e preferências básicas do shell.</p>
</header>
<div className="grid gap-6 lg:grid-cols-[1fr_360px]">
<section className={`${cardClass} p-6`}>
<section className={`${cardClass} ${featurePanelClass('partial')} p-6`}>
<div className="mb-6 flex items-center gap-4">
{profile.avatarUrl ? (
<img
@@ -134,12 +142,12 @@ export function ProfilePage({ navigate }) {
<button className="h-10 rounded-sm bg-[#3b82f6] px-4 text-sm font-semibold text-white" type="submit">
Salvar alterações
</button>
{saved ? <span className="rounded bg-emerald-500/20 px-2.5 py-1 text-xs font-bold text-emerald-400">Preferências salvas localmente</span> : null}
{saved ? <span className="rounded bg-amber-500/20 px-2.5 py-1 text-xs font-bold text-amber-300">Preferências salvas localmente</span> : null}
</div>
</form>
</section>
<aside className={`${cardClass} p-6`}>
<aside className={`${cardClass} ${featurePanelClass('live')} p-6`}>
<h2 className="text-xl font-bold text-[#f5f5f5]">Resumo de acesso</h2>
<dl className="mt-5 grid gap-4 text-sm">
<Info label="Perfil" value={profile.role} />

View File

@@ -1,5 +1,7 @@
import { useEffect, useMemo, useState } from 'react'
import { FeatureBadge, FeatureCallout } from '../components/FeatureState.jsx'
import { featurePanelClass } from '../components/featureStateStyles.js'
import { reportRepository } from '../repositories/reportRepository.js'
import { patientRepository } from '../repositories/patientRepository.js'
@@ -186,18 +188,25 @@ export function ReportsPage() {
return (
<div className="mx-auto max-w-7xl space-y-6 text-[#e5e5e5]">
<FeatureCallout
description="Listagem e salvar laudo usam API. Templates, protocolo de entrega e parte do fluxo editorial ainda são locais."
status="partial"
title="Laudos com integração parcial"
/>
<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-[#e5e5e5]">Gestão de Laudos</h1>
</div>
<div className="flex w-full flex-col gap-2 sm:w-auto sm:flex-row sm:items-center">
<button
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg border border-[#404040] bg-[#262626] px-4 text-sm font-medium text-[#e5e5e5] transition hover:bg-[#2a2a2a]"
className={`inline-flex h-10 items-center justify-center gap-2 rounded-lg border px-4 text-sm font-medium text-[#e5e5e5] transition hover:bg-[#2a2a2a] ${featurePanelClass('mock')}`}
onClick={() => setTemplatesOpen(true)}
type="button"
>
<ReportIcon className="size-4 text-[#3b82f6]" name="template" />
Templates
<FeatureBadge status="mock" />
</button>
<button
className="inline-flex h-10 items-center justify-center gap-2 rounded-lg bg-[#3b82f6] px-4 text-sm font-medium text-white transition hover:bg-[#2563eb]"
@@ -219,7 +228,7 @@ export function ReportsPage() {
))}
</section>
<section className={`${cardClass} p-6`}>
<section className={`${cardClass} ${featurePanelClass('live')} p-6`}>
<div className="mb-6 flex flex-col gap-4 md:flex-row">
<div className="relative flex-1">
<ReportIcon className="pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-[#a3a3a3]" name="search" />

View File

@@ -1,5 +1,6 @@
import { useState } from 'react'
import { FeatureCallout } from '../components/FeatureState.jsx'
import { settingsRepository } from '../repositories/settingsRepository.js'
@@ -14,6 +15,13 @@ export function SettingsPage() {
return (
<div className="mx-auto max-w-5xl">
<FeatureCallout
className="mb-6"
description="Preferências, integrações e backup ainda são protótipos locais, sem persistência real."
status="mock"
title="Configurações ainda estão em modo protótipo"
/>
<header className="mb-8">
<h1 className="text-2xl font-bold tracking-tight text-[#f5f5f5]">Configurações</h1>
<p className="mt-1 text-sm text-[#b8b8b8]">Gerencie preferências, segurança e integrações do MediConnect</p>

View File

@@ -1,4 +1,6 @@
import { useState, useEffect } from 'react'
import { FeatureBadge, FeatureCallout } from '../components/FeatureState.jsx'
import { featurePanelClass } from '../components/featureStateStyles.js'
import { professionalRepository } from '../repositories/professionalRepository.js'
const cardClass = 'rounded-2xl border border-[#404040] bg-[#262626] shadow-sm'
@@ -13,6 +15,12 @@ export function TeamPage({ navigate }) {
return (
<div className="mx-auto max-w-7xl space-y-6">
<FeatureCallout
description="A listagem de profissionais usa API, mas o mapa de cobertura e parte da disponibilidade ainda são simulados."
status="partial"
title="Tela híbrida: parte real, parte mockada"
/>
<header 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]">Profissionais</h1>
@@ -29,7 +37,7 @@ export function TeamPage({ navigate }) {
<section className="grid gap-4 md:grid-cols-2 xl:grid-cols-4" aria-label="Equipe médica">
{professionals.map((professional) => (
<article className={`${cardClass} p-5`} key={professional.id}>
<article className={`${cardClass} ${featurePanelClass('live')} p-5`} key={professional.id}>
<div className="flex items-start justify-between gap-3">
<div>
<div className="grid size-11 place-items-center rounded-full border border-[#3b82f6]/30 bg-[#3b82f6]/10 text-sm font-bold text-[#3b82f6]">
@@ -50,10 +58,13 @@ export function TeamPage({ navigate }) {
))}
</section>
<section className={`${cardClass} p-5`}>
<section className={`${cardClass} ${featurePanelClass('mock')} p-5`}>
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h2 className="text-xl font-bold text-[#f5f5f5]">Mapa de cobertura</h2>
<div className="flex flex-wrap items-center gap-2">
<h2 className="text-xl font-bold text-[#f5f5f5]">Mapa de cobertura</h2>
<FeatureBadge status="mock" />
</div>
<p className="mt-1 text-sm text-[#a3a3a3]">
Matriz simples para preparar o fluxo de agenda, plantão e disponibilidade.
</p>

View File

@@ -1,5 +1,6 @@
import { useMemo, useState } from 'react'
import { FeatureCallout } from '../components/FeatureState.jsx'
import { visitRepository } from '../repositories/visitRepository.js'
const tabs = [
@@ -35,6 +36,12 @@ export function VisitsPage({ navigate }) {
return (
<div className="mx-auto max-w-7xl space-y-6">
<FeatureCallout
description="Fila, etapas e resumo desta tela ainda são inteiramente mockados."
status="mock"
title="Consultas ainda não usam backend"
/>
<header 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]">Consultas</h1>