MelissaPaciente: bloco "Recorrencias do paciente" na Tab Agenda
User aprovou a ideia. Adiciona contexto "este paciente tem sessao toda segunda 14h" direto no prontuario, evitando duplicacao de regras e deixando claro o estado da serie. NOVO src/features/patients/composables/usePatientRecurrences.js (~110L) - load(patientId): SELECT recurrence_rules WHERE patient_id (DESC start_date) - cancel(ruleId) / reactivate(ruleId): UPDATE status + auto-reload - Computeds derivados: ativas, canceladas, totalAtivas, totalCanceladas - busy flag pra disable de buttons EXTENSAO src/features/patients/utils/patientFormatters.js - WEEKDAY_LABEL + WEEKDAY_LABEL_SHORT (arrays 0=Domingo..6=Sabado) - fmtRecurrenceLabel(rule): "Toda segunda às 14:00", "Quinzenal · Terça às 09:00", "Qua, Sex às 16:00" (custom_weekdays), "Mensal às 14:00", "Anual" — cobre todos os types do useRecurrence. - fmtRecurrenceFim(rule): "Sem data de fim" / "Até DD/MM/YYYY" / "N sessões no total" MELISSAPACIENTE.VUE - Composable + handlers (onCancelRecurrence, onReactivateRecurrence) com toast feedback. - recorrenciasShowCanc ref + recorrenciasVisiveis computed (toggle "ver canceladas"). - loadAll inclui recorrenciasHook.load. - salvarSessao no caminho recorrente recarrega sessions+recorrencias em Promise.all (regra recem-criada aparece na lista imediatamente). - 5o KPI na Tab Agenda: "Recorrencias" com count ativas + cap dinamica (cor #a855f7 quando > 0, cinza quando 0). - Bloco <section class="mpa-panel"> entre KPIs e filter chips listando rules ativas (default) ou todas (toggle "Ver canceladas" no header, so aparece quando ha canceladas): - Icon roxo .mpa-recur-item__icon - Top: label + Tag status (verde Ativa / amarelo Cancelada) - Meta: duracao + modalidade + fim + "desde DATE" - Obs (quando preenchido): block textual - Actions: pi-ban (cancelar) ou pi-undo (reativar) com tooltip - border-left adaptativa (#a855f7 ativo / cinza cancelado) + opacity 0.7 pros cancelados. - Mobile: stack icon+main em 2-col 2-row; actions full-width abaixo. CSS: ~120L novos. Padrao Melissa: status pills, icon roxo distintivo (diferente das sessoes que usam cinza), border-left por status. ESLint: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Agência PSI
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Arquivo: src/features/patients/composables/usePatientRecurrences.js
|
||||||
|
|
|
||||||
|
| Carrega regras de recorrencia (recurrence_rules) filtradas por paciente.
|
||||||
|
| Usado pela Tab Agenda do MelissaPaciente pra mostrar "este paciente tem
|
||||||
|
| sessao toda segunda 14h" e dar acoes inline (cancelar/reativar).
|
||||||
|
|
|
||||||
|
| Mutations espelham o pattern de MelissaRecorrencias.vue.
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
import { ref, computed } from 'vue';
|
||||||
|
import { supabase } from '@/lib/supabase/client';
|
||||||
|
|
||||||
|
export function usePatientRecurrences() {
|
||||||
|
const rules = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const error = ref('');
|
||||||
|
const busy = ref(false);
|
||||||
|
let _lastPatientId = null;
|
||||||
|
|
||||||
|
async function load(patientId) {
|
||||||
|
_lastPatientId = patientId || null;
|
||||||
|
if (!patientId) {
|
||||||
|
rules.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loading.value = true;
|
||||||
|
error.value = '';
|
||||||
|
rules.value = [];
|
||||||
|
try {
|
||||||
|
const { data, error: err } = await supabase
|
||||||
|
.from('recurrence_rules')
|
||||||
|
.select('*')
|
||||||
|
.eq('patient_id', patientId)
|
||||||
|
.order('start_date', { ascending: false });
|
||||||
|
if (err) throw err;
|
||||||
|
rules.value = data || [];
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e?.message || 'Falha ao carregar recorrencias.';
|
||||||
|
rules.value = [];
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function cancel(ruleId) {
|
||||||
|
if (!ruleId || busy.value) return { ok: false, error: 'busy' };
|
||||||
|
busy.value = true;
|
||||||
|
try {
|
||||||
|
const { error: err } = await supabase
|
||||||
|
.from('recurrence_rules')
|
||||||
|
.update({ status: 'cancelado', updated_at: new Date().toISOString() })
|
||||||
|
.eq('id', ruleId);
|
||||||
|
if (err) throw err;
|
||||||
|
if (_lastPatientId) await load(_lastPatientId);
|
||||||
|
return { ok: true };
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: e?.message || 'Erro ao cancelar' };
|
||||||
|
} finally {
|
||||||
|
busy.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reactivate(ruleId) {
|
||||||
|
if (!ruleId || busy.value) return { ok: false, error: 'busy' };
|
||||||
|
busy.value = true;
|
||||||
|
try {
|
||||||
|
const { error: err } = await supabase
|
||||||
|
.from('recurrence_rules')
|
||||||
|
.update({ status: 'ativo', updated_at: new Date().toISOString() })
|
||||||
|
.eq('id', ruleId);
|
||||||
|
if (err) throw err;
|
||||||
|
if (_lastPatientId) await load(_lastPatientId);
|
||||||
|
return { ok: true };
|
||||||
|
} catch (e) {
|
||||||
|
return { ok: false, error: e?.message || 'Erro ao reativar' };
|
||||||
|
} finally {
|
||||||
|
busy.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ativas = computed(() => rules.value.filter((r) => r.status === 'ativo'));
|
||||||
|
const canceladas = computed(() => rules.value.filter((r) => r.status === 'cancelado'));
|
||||||
|
const totalAtivas = computed(() => ativas.value.length);
|
||||||
|
const totalCanceladas = computed(() => canceladas.value.length);
|
||||||
|
|
||||||
|
return {
|
||||||
|
rules,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
busy,
|
||||||
|
load,
|
||||||
|
cancel,
|
||||||
|
reactivate,
|
||||||
|
ativas,
|
||||||
|
canceladas,
|
||||||
|
totalAtivas,
|
||||||
|
totalCanceladas
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -179,6 +179,58 @@ export const DOC_TYPE_LABEL = {
|
|||||||
outro: 'Outro'
|
outro: 'Outro'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map de dia da semana (0=Domingo) -> label pt-br.
|
||||||
|
*/
|
||||||
|
export const WEEKDAY_LABEL = ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'];
|
||||||
|
export const WEEKDAY_LABEL_SHORT = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label legivel da regra de recorrencia.
|
||||||
|
* Ex: "Toda segunda às 14:00", "A cada 2 semanas, terça às 09:00",
|
||||||
|
* "Quartas e sextas às 16:00", "Mensal no dia 15".
|
||||||
|
*/
|
||||||
|
export function fmtRecurrenceLabel(rule) {
|
||||||
|
if (!rule) return '—';
|
||||||
|
const time = String(rule.start_time || '').slice(0, 5);
|
||||||
|
const interval = Number(rule.interval) || 1;
|
||||||
|
|
||||||
|
if (rule.type === 'weekly' || (rule.type === 'biweekly' && interval === 1)) {
|
||||||
|
const dow = (rule.weekdays || [])[0];
|
||||||
|
if (dow == null) return time ? `Semanal às ${time}` : 'Semanal';
|
||||||
|
const dayLbl = WEEKDAY_LABEL[dow] || '?';
|
||||||
|
if (rule.type === 'biweekly') {
|
||||||
|
return time ? `Quinzenal · ${dayLbl} às ${time}` : `Quinzenal · ${dayLbl}`;
|
||||||
|
}
|
||||||
|
return time ? `Toda ${dayLbl.toLowerCase()} às ${time}` : `Toda ${dayLbl.toLowerCase()}`;
|
||||||
|
}
|
||||||
|
if (rule.type === 'custom_weekdays') {
|
||||||
|
const dows = (rule.weekdays || []).map((d) => WEEKDAY_LABEL_SHORT[d]).filter(Boolean);
|
||||||
|
const dayList = dows.length ? dows.join(', ') : '?';
|
||||||
|
return time ? `${dayList} às ${time}` : dayList;
|
||||||
|
}
|
||||||
|
if (rule.type === 'monthly') {
|
||||||
|
return time ? `Mensal às ${time}` : 'Mensal';
|
||||||
|
}
|
||||||
|
if (rule.type === 'yearly') {
|
||||||
|
return time ? `Anual às ${time}` : 'Anual';
|
||||||
|
}
|
||||||
|
return rule.type || 'Recorrência';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label pro fim da regra: "Sem data de fim", "Até DD/MM/YYYY", "N sessões no total".
|
||||||
|
*/
|
||||||
|
export function fmtRecurrenceFim(rule) {
|
||||||
|
if (!rule) return '';
|
||||||
|
if (rule.end_date) return `Até ${fmtDateBR(rule.end_date)}`;
|
||||||
|
if (rule.max_occurrences) {
|
||||||
|
const n = Number(rule.max_occurrences);
|
||||||
|
return `${n} ${n === 1 ? 'sessão' : 'sessões'} no total`;
|
||||||
|
}
|
||||||
|
return 'Sem data de fim';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel label pra conversa: whatsapp -> WhatsApp, sms -> SMS, email -> E-mail.
|
* Channel label pra conversa: whatsapp -> WhatsApp, sms -> SMS, email -> E-mail.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import { usePatientSessions } from '@/features/patients/composables/usePatientSe
|
|||||||
import { usePatientFinancial } from '@/features/patients/composables/usePatientFinancial';
|
import { usePatientFinancial } from '@/features/patients/composables/usePatientFinancial';
|
||||||
import { usePatientMessages } from '@/features/patients/composables/usePatientMessages';
|
import { usePatientMessages } from '@/features/patients/composables/usePatientMessages';
|
||||||
import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments';
|
import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments';
|
||||||
|
import { usePatientRecurrences } from '@/features/patients/composables/usePatientRecurrences';
|
||||||
import { useRecurrence } from '@/features/agenda/composables/useRecurrence';
|
import { useRecurrence } from '@/features/agenda/composables/useRecurrence';
|
||||||
import {
|
import {
|
||||||
pickField,
|
pickField,
|
||||||
@@ -41,6 +42,8 @@ import {
|
|||||||
fmtCurrency,
|
fmtCurrency,
|
||||||
fmtHourShort,
|
fmtHourShort,
|
||||||
fmtDayShort,
|
fmtDayShort,
|
||||||
|
fmtRecurrenceLabel,
|
||||||
|
fmtRecurrenceFim,
|
||||||
fmtCPF,
|
fmtCPF,
|
||||||
fmtRG,
|
fmtRG,
|
||||||
fmtGender,
|
fmtGender,
|
||||||
@@ -72,6 +75,7 @@ const sessionsHook = usePatientSessions();
|
|||||||
const financialHook = usePatientFinancial();
|
const financialHook = usePatientFinancial();
|
||||||
const messagesHook = usePatientMessages();
|
const messagesHook = usePatientMessages();
|
||||||
const documentsHook = usePatientDocuments();
|
const documentsHook = usePatientDocuments();
|
||||||
|
const recorrenciasHook = usePatientRecurrences();
|
||||||
const recurrenceHook = useRecurrence();
|
const recurrenceHook = useRecurrence();
|
||||||
|
|
||||||
// ── Breakpoints + drawer ───────────────────────────────────
|
// ── Breakpoints + drawer ───────────────────────────────────
|
||||||
@@ -217,6 +221,38 @@ const groupNames = computed(() => detail.groups.value.map((g) => g?.name).filter
|
|||||||
const groupLabel = computed(() => groupNames.value.length ? groupNames.value.join(', ') : '—');
|
const groupLabel = computed(() => groupNames.value.length ? groupNames.value.join(', ') : '—');
|
||||||
const groupCountLabel = computed(() => groupNames.value.length <= 1 ? 'Grupo' : 'Grupos');
|
const groupCountLabel = computed(() => groupNames.value.length <= 1 ? 'Grupo' : 'Grupos');
|
||||||
|
|
||||||
|
// ── Tab Agenda: bloco recorrencias do paciente ─────────────
|
||||||
|
const recorrenciasShowCanc = ref(false);
|
||||||
|
const recorrenciasVisiveis = computed(() =>
|
||||||
|
recorrenciasShowCanc.value ? recorrenciasHook.rules.value : recorrenciasHook.ativas.value
|
||||||
|
);
|
||||||
|
async function onCancelRecurrence(rule) {
|
||||||
|
const result = await recorrenciasHook.cancel(rule.id);
|
||||||
|
if (result.ok) {
|
||||||
|
toast.add({ severity: 'success', summary: 'Recorrência cancelada', life: 2200 });
|
||||||
|
} else {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Falha ao cancelar',
|
||||||
|
detail: result.error || 'Erro inesperado',
|
||||||
|
life: 4000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function onReactivateRecurrence(rule) {
|
||||||
|
const result = await recorrenciasHook.reactivate(rule.id);
|
||||||
|
if (result.ok) {
|
||||||
|
toast.add({ severity: 'success', summary: 'Recorrência reativada', life: 2200 });
|
||||||
|
} else {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Falha ao reativar',
|
||||||
|
detail: result.error || 'Erro inesperado',
|
||||||
|
life: 4000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Tab Agenda: filtros + agrupamento por mes ──────────────
|
// ── Tab Agenda: filtros + agrupamento por mes ──────────────
|
||||||
const agendaSessoesFiltradas = computed(() => {
|
const agendaSessoesFiltradas = computed(() => {
|
||||||
const list = sessionsHook.sessions.value;
|
const list = sessionsHook.sessions.value;
|
||||||
@@ -538,8 +574,12 @@ async function salvarSessao() {
|
|||||||
life: 3000
|
life: 3000
|
||||||
});
|
});
|
||||||
novaSessaoOpen.value = false;
|
novaSessaoOpen.value = false;
|
||||||
// Recarrega sessoes do paciente (caso start_date seja hoje).
|
// Recarrega sessoes (caso start_date seja hoje) + recorrencias
|
||||||
await sessionsHook.load(props.patientId);
|
// (a regra recem-criada precisa aparecer no bloco da Tab Agenda).
|
||||||
|
await Promise.all([
|
||||||
|
sessionsHook.load(props.patientId),
|
||||||
|
recorrenciasHook.load(props.patientId)
|
||||||
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.add({
|
toast.add({
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
@@ -618,7 +658,8 @@ async function loadAll(id) {
|
|||||||
sessionsHook.load(id),
|
sessionsHook.load(id),
|
||||||
financialHook.load(id),
|
financialHook.load(id),
|
||||||
messagesHook.load(id),
|
messagesHook.load(id),
|
||||||
documentsHook.load(id)
|
documentsHook.load(id),
|
||||||
|
recorrenciasHook.load(id)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1654,8 +1695,111 @@ onBeforeUnmount(() => {
|
|||||||
<div class="mpa-kpi__cap">Sem futura</div>
|
<div class="mpa-kpi__cap">Sem futura</div>
|
||||||
</template>
|
</template>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
<article class="mpa-kpi" :style="recorrenciasHook.totalAtivas.value > 0 ? '--c:#a855f7' : '--c:#94a3b8'">
|
||||||
|
<span class="mpa-kpi__num">05</span>
|
||||||
|
<header class="mpa-kpi__head">
|
||||||
|
<div class="mpa-kpi__icon"><i class="pi pi-sync" /></div>
|
||||||
|
<span class="mpa-kpi__tag">Recorrências</span>
|
||||||
|
</header>
|
||||||
|
<div class="mpa-kpi__big">{{ recorrenciasHook.totalAtivas.value }}</div>
|
||||||
|
<div class="mpa-kpi__cap">
|
||||||
|
<template v-if="recorrenciasHook.totalAtivas.value === 0">
|
||||||
|
nenhuma série ativa
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ recorrenciasHook.totalAtivas.value === 1 ? 'série ativa' : 'séries ativas' }}
|
||||||
|
<span v-if="recorrenciasHook.totalCanceladas.value" class="mpa-kpi__cap-dim">
|
||||||
|
· {{ recorrenciasHook.totalCanceladas.value }} cancel.
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Bloco: recorrencias do paciente (acima dos meses) -->
|
||||||
|
<section v-if="recorrenciasHook.rules.value.length" class="mpa-panel">
|
||||||
|
<header class="mpa-panel__head">
|
||||||
|
<div class="mpa-panel__title"><i class="pi pi-sync" /> Recorrências</div>
|
||||||
|
<div class="mpa-fin__head-actions">
|
||||||
|
<span class="mpa-panel__badge">{{ recorrenciasVisiveis.length }}</span>
|
||||||
|
<label
|
||||||
|
v-if="recorrenciasHook.totalCanceladas.value > 0"
|
||||||
|
class="mpa-recur-toggle"
|
||||||
|
v-tooltip.left="'Mostrar séries canceladas'"
|
||||||
|
>
|
||||||
|
<input v-model="recorrenciasShowCanc" type="checkbox" />
|
||||||
|
<span>Ver canceladas</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<ul class="mpa-recur-list">
|
||||||
|
<li
|
||||||
|
v-for="rule in recorrenciasVisiveis"
|
||||||
|
:key="rule.id"
|
||||||
|
class="mpa-recur-item"
|
||||||
|
:data-status="rule.status"
|
||||||
|
>
|
||||||
|
<div class="mpa-recur-item__icon">
|
||||||
|
<i class="pi pi-sync" />
|
||||||
|
</div>
|
||||||
|
<div class="mpa-recur-item__main">
|
||||||
|
<div class="mpa-recur-item__top">
|
||||||
|
<span class="mpa-recur-item__label">{{ fmtRecurrenceLabel(rule) }}</span>
|
||||||
|
<Tag
|
||||||
|
v-if="rule.status === 'cancelado'"
|
||||||
|
value="Cancelada"
|
||||||
|
severity="warn"
|
||||||
|
class="mpa-recur-item__tag"
|
||||||
|
/>
|
||||||
|
<Tag
|
||||||
|
v-else
|
||||||
|
value="Ativa"
|
||||||
|
severity="success"
|
||||||
|
class="mpa-recur-item__tag"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mpa-recur-item__meta">
|
||||||
|
<span><i class="pi pi-clock" />{{ rule.duration_min }}min</span>
|
||||||
|
<span v-if="rule.modalidade">
|
||||||
|
<i :class="rule.modalidade === 'online' ? 'pi pi-video' : 'pi pi-map-marker'" />
|
||||||
|
{{ rule.modalidade === 'online' ? 'Online' : 'Presencial' }}
|
||||||
|
</span>
|
||||||
|
<span><i class="pi pi-flag-fill" />{{ fmtRecurrenceFim(rule) }}</span>
|
||||||
|
<span class="mpa-recur-item__since">
|
||||||
|
desde {{ fmtDateBR(rule.start_date) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p v-if="rule.observacoes" class="mpa-recur-item__obs">
|
||||||
|
{{ rule.observacoes }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mpa-recur-item__actions">
|
||||||
|
<button
|
||||||
|
v-if="rule.status === 'ativo'"
|
||||||
|
type="button"
|
||||||
|
v-tooltip.left="'Cancelar série'"
|
||||||
|
class="mpa-ag__act mpa-ag__act--danger"
|
||||||
|
:disabled="recorrenciasHook.busy.value"
|
||||||
|
@click="onCancelRecurrence(rule)"
|
||||||
|
>
|
||||||
|
<i class="pi pi-ban" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-else
|
||||||
|
type="button"
|
||||||
|
v-tooltip.left="'Reativar série'"
|
||||||
|
class="mpa-ag__act mpa-ag__act--ok"
|
||||||
|
:disabled="recorrenciasHook.busy.value"
|
||||||
|
@click="onReactivateRecurrence(rule)"
|
||||||
|
>
|
||||||
|
<i class="pi pi-undo" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Filter chips -->
|
<!-- Filter chips -->
|
||||||
<div class="mpa-pron-filters" role="tablist">
|
<div class="mpa-pron-filters" role="tablist">
|
||||||
<button
|
<button
|
||||||
@@ -3646,6 +3790,130 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ═══════ Bloco recorrencias do paciente (Tab Agenda topo) ═══════ */
|
||||||
|
.mpa-recur-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--m-border);
|
||||||
|
background: var(--m-bg-medium);
|
||||||
|
transition: background-color 120ms ease;
|
||||||
|
}
|
||||||
|
.mpa-recur-toggle:hover { background: var(--m-bg-soft-hover); }
|
||||||
|
.mpa-recur-toggle > input { accent-color: var(--p-primary-color); }
|
||||||
|
|
||||||
|
.mpa-recur-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.mpa-recur-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 32px 1fr auto;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-top: 1px solid var(--m-border);
|
||||||
|
border-left: 3px solid #a855f7;
|
||||||
|
transition: background-color 120ms ease;
|
||||||
|
}
|
||||||
|
.mpa-recur-item:first-child { border-top: none; }
|
||||||
|
.mpa-recur-item:hover { background: var(--m-bg-medium); }
|
||||||
|
.mpa-recur-item[data-status="cancelado"] {
|
||||||
|
border-left-color: var(--m-text-muted);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mpa-recur-item__icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: color-mix(in srgb, #a855f7 16%, transparent);
|
||||||
|
color: #a855f7;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mpa-recur-item__icon > i { font-size: 0.85rem; }
|
||||||
|
.mpa-recur-item[data-status="cancelado"] .mpa-recur-item__icon {
|
||||||
|
background: var(--m-bg-medium);
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mpa-recur-item__main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.mpa-recur-item__top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.mpa-recur-item__label {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--m-text);
|
||||||
|
}
|
||||||
|
.mpa-recur-item__tag { font-size: 0.66rem !important; }
|
||||||
|
|
||||||
|
.mpa-recur-item__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px 12px;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
}
|
||||||
|
.mpa-recur-item__meta > span {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.mpa-recur-item__meta > span > i { font-size: 0.66rem; opacity: 0.7; }
|
||||||
|
.mpa-recur-item__since { opacity: 0.65; }
|
||||||
|
|
||||||
|
.mpa-recur-item__obs {
|
||||||
|
font-size: 0.74rem;
|
||||||
|
color: var(--m-text-muted);
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-left: 2px solid var(--m-border);
|
||||||
|
background: var(--m-bg-medium);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 2px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mpa-recur-item__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: stack icon + main em coluna; actions vai pra baixo */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.mpa-recur-item {
|
||||||
|
grid-template-columns: 32px 1fr;
|
||||||
|
grid-template-rows: auto auto;
|
||||||
|
}
|
||||||
|
.mpa-recur-item__actions {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ═══════ Tab Agenda (Fase 5) ═══════ */
|
/* ═══════ Tab Agenda (Fase 5) ═══════ */
|
||||||
.mpa-ag__group + .mpa-ag__group { margin-top: 10px; }
|
.mpa-ag__group + .mpa-ag__group { margin-top: 10px; }
|
||||||
.mpa-ag__list {
|
.mpa-ag__list {
|
||||||
|
|||||||
Reference in New Issue
Block a user