Setup Wizard

This commit is contained in:
Leonardo
2026-03-14 19:09:44 -03:00
parent 587079e414
commit ee09b30987
16 changed files with 25276 additions and 62 deletions
+7 -1
View File
@@ -100,8 +100,14 @@ onBeforeUnmount(() => {
</script>
<template>
<!-- Fullscreen: setup wizard (sem sidebar/topbar/footer) -->
<template v-if="route.meta?.fullscreen">
<router-view />
<Toast />
</template>
<!-- Layout 2: Rail + Painel + Main (full-width) -->
<template v-if="layoutConfig.variant === 'rail' && isDesktop()">
<template v-else-if="layoutConfig.variant === 'rail' && isDesktop()">
<div class="l2-root">
<AppRail />
<div class="l2-body">
@@ -38,9 +38,21 @@ const cfg = ref({
online_ativo: false,
setup_clinica_concluido: false,
setup_clinica_concluido_em: null,
jornada_igual_todos: true
jornada_igual_todos: true,
timezone: 'America/Sao_Paulo',
is_conveniado: false,
})
const timezones = [
{ label: 'Brasília (BRT)', value: 'America/Sao_Paulo' },
{ label: 'Manaus (AMT)', value: 'America/Manaus' },
{ label: 'Fortaleza (BRT)', value: 'America/Fortaleza' },
{ label: 'Belém (BRT)', value: 'America/Belem' },
{ label: 'Rio Branco (ACT)', value: 'America/Rio_Branco' },
{ label: 'Noronha (FNT)', value: 'America/Noronha' },
{ label: 'Lisboa (WET)', value: 'Europe/Lisbon' },
]
// ── Jornada ────────────────────────────────────────────────────
const regras = ref([])
const workDays = ref({ 1: false, 2: false, 3: false, 4: false, 5: false, 6: false, 0: false })
@@ -86,7 +98,8 @@ const diasSemana = [
{ label: 'Domingo', short: 'Dom', value: 0 }
]
const selectedDays = computed(() => diasSemana.filter(d => !!workDays.value[d.value]))
const selectedDays = computed(() => diasSemana.filter(d => !!workDays.value[d.value]))
const selectedWeekdays = computed(() => diasSemana.filter(d => d.value >= 1 && d.value <= 5 && !!workDays.value[d.value]))
function hhmmToMin (hhmm) {
const [h, m] = String(hhmm).split(':').map(Number)
@@ -196,7 +209,10 @@ const orphanSlotDays = computed(() => {
watch([selectedDays, jornadaStart, jornadaEnd], () => {
if (!isValidHHMM(jornadaStart.value) || !isValidHHMM(jornadaEnd.value)) return
if (jornadaIgualTodos.value !== false) {
selectedDays.value.forEach(d => { jornadaPorDia.value[d.value] = { inicio: jornadaStart.value, fim: jornadaEnd.value } })
// Sync apenas SegSex; Sáb e Dom têm horário próprio
selectedDays.value
.filter(d => d.value >= 1 && d.value <= 5)
.forEach(d => { jornadaPorDia.value[d.value] = { inicio: jornadaStart.value, fim: jornadaEnd.value } })
}
}, { immediate: true })
@@ -208,10 +224,15 @@ function getPausasForDay (dayValue) {
// ── Toggle igual/diferente ─────────────────────────────────────
function switchToIgual () {
// Copia global para todos os dias (zera divergências por dia)
if (isValidHHMM(jornadaStart.value) && isValidHHMM(jornadaEnd.value)) {
selectedDays.value.forEach(d => {
jornadaPorDia.value[d.value] = { inicio: jornadaStart.value, fim: jornadaEnd.value }
// Sync apenas SegSex; Sáb e Dom mantêm horário próprio
selectedDays.value
.filter(d => d.value >= 1 && d.value <= 5)
.forEach(d => { jornadaPorDia.value[d.value] = { inicio: jornadaStart.value, fim: jornadaEnd.value } })
// Inicializa Sáb/Dom com horário global se ainda não tiver valor
;[6, 0].forEach(v => {
if (workDays.value[v] && !jornadaPorDia.value[v]?.inicio)
jornadaPorDia.value[v] = { inicio: jornadaStart.value, fim: jornadaEnd.value }
})
}
// Copia pausas globais para todos os dias e usa apenas pausasGlobais
@@ -423,6 +444,7 @@ async function saveJornada () {
tenant_id: tenantId,
pausas_semanais: pausasToSave,
jornada_igual_todos: igualTodos,
timezone: cfg.value.timezone || 'America/Sao_Paulo',
setup_clinica_concluido: true,
setup_clinica_concluido_em: cfg.value.setup_clinica_concluido_em || new Date().toISOString()
}, { onConflict: 'owner_id' })
@@ -431,7 +453,8 @@ async function saveJornada () {
// 2. regras semanais
const rows = selectedDays.value.map(d => {
const t = jornadaIgualTodos.value === false
const isWeekend = d.value === 6 || d.value === 0
const t = (jornadaIgualTodos.value === false || isWeekend)
? (jornadaPorDia.value[d.value] || { inicio: jornadaStart.value, fim: jornadaEnd.value })
: { inicio: jornadaStart.value, fim: jornadaEnd.value }
return { owner_id: uid, tenant_id: tenantId, dia_semana: d.value, hora_inicio: normalizeTime(t.inicio), hora_fim: normalizeTime(t.fim), modalidade: 'ambos', ativo: true }
@@ -797,7 +820,19 @@ const jornadaEndDate = computed({
<div v-show="expandedCard === 'jornada'" class="cfg-card__body">
<div class="border-t border-[var(--surface-border)] pt-4">
<!-- Início das sessões (alinhamento de horário) -->
<!-- Fuso horário -->
<div class="mb-5">
<div class="cfg-label mb-2">Fuso horário</div>
<Select
v-model="cfg.timezone"
:options="timezones"
optionLabel="label"
optionValue="value"
class="w-full max-w-xs"
placeholder="Selecione o fuso..."
/>
</div>
<!-- Dias da semana -->
<div class="mb-5">
<div class="cfg-label mb-2">Quais dias você trabalha?</div>
@@ -837,23 +872,77 @@ const jornadaEndDate = computed({
</div>
<!-- Igual para todos -->
<div v-if="jornadaIgualTodos !== false" class="flex flex-wrap items-center gap-3">
<div class="flex items-center gap-2">
<span class="text-sm text-[var(--text-color-secondary)]">Das</span>
<div class="w-32">
<DatePicker v-model="jornadaStartDate" showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps">
<i class="pi pi-clock" @click="slotProps.clickCallback" />
</template>
</DatePicker>
<div v-if="jornadaIgualTodos !== false" class="flex flex-col gap-3">
<!-- SegSex -->
<div v-if="selectedWeekdays.length > 0" class="cfg-equal-group">
<div class="cfg-equal-chips">
<span v-for="d in selectedWeekdays" :key="d.value" class="day-chip day-chip--active">{{ d.short }}</span>
</div>
<span class="text-sm text-[var(--text-color-secondary)]">até</span>
<div class="w-32">
<DatePicker v-model="jornadaEndDate" showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps">
<i class="pi pi-clock" @click="slotProps.clickCallback" />
</template>
</DatePicker>
<div class="flex items-center gap-2">
<span class="text-sm text-[var(--text-color-secondary)]">Das</span>
<div class="w-32">
<DatePicker v-model="jornadaStartDate" showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps"><i class="pi pi-clock" @click="slotProps.clickCallback" /></template>
</DatePicker>
</div>
<span class="text-sm text-[var(--text-color-secondary)]">até</span>
<div class="w-32">
<DatePicker v-model="jornadaEndDate" showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps"><i class="pi pi-clock" @click="slotProps.clickCallback" /></template>
</DatePicker>
</div>
</div>
</div>
<!-- Sábado -->
<div v-if="workDays[6]" class="cfg-equal-group">
<div class="cfg-equal-chips">
<span class="day-chip day-chip--active">Sáb</span>
</div>
<div class="flex items-center gap-2">
<span class="text-sm text-[var(--text-color-secondary)]">Das</span>
<div class="w-32">
<DatePicker
:modelValue="hhmmToDate(jornadaPorDia[6]?.inicio)"
@update:modelValue="v => { const h = dateToHHMM(v); if (h) jornadaPorDia[6] = { ...jornadaPorDia[6], inicio: h } }"
showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps"><i class="pi pi-clock" @click="slotProps.clickCallback" /></template>
</DatePicker>
</div>
<span class="text-sm text-[var(--text-color-secondary)]">até</span>
<div class="w-32">
<DatePicker
:modelValue="hhmmToDate(jornadaPorDia[6]?.fim)"
@update:modelValue="v => { const h = dateToHHMM(v); if (h) jornadaPorDia[6] = { ...jornadaPorDia[6], fim: h } }"
showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps"><i class="pi pi-clock" @click="slotProps.clickCallback" /></template>
</DatePicker>
</div>
</div>
</div>
<!-- Domingo -->
<div v-if="workDays[0]" class="cfg-equal-group">
<div class="cfg-equal-chips">
<span class="day-chip day-chip--active">Dom</span>
</div>
<div class="flex items-center gap-2">
<span class="text-sm text-[var(--text-color-secondary)]">Das</span>
<div class="w-32">
<DatePicker
:modelValue="hhmmToDate(jornadaPorDia[0]?.inicio)"
@update:modelValue="v => { const h = dateToHHMM(v); if (h) jornadaPorDia[0] = { ...jornadaPorDia[0], inicio: h } }"
showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps"><i class="pi pi-clock" @click="slotProps.clickCallback" /></template>
</DatePicker>
</div>
<span class="text-sm text-[var(--text-color-secondary)]">até</span>
<div class="w-32">
<DatePicker
:modelValue="hhmmToDate(jornadaPorDia[0]?.fim)"
@update:modelValue="v => { const h = dateToHHMM(v); if (h) jornadaPorDia[0] = { ...jornadaPorDia[0], fim: h } }"
showIcon fluid iconDisplay="input" timeOnly hourFormat="24" :stepMinute="15" :manualInput="false">
<template #inputicon="slotProps"><i class="pi pi-clock" @click="slotProps.clickCallback" /></template>
</DatePicker>
</div>
</div>
</div>
</div>
@@ -1306,6 +1395,8 @@ const jornadaEndDate = computed({
.day-chip:hover { border-color: var(--primary-color); }
.day-chip--active { background: var(--primary-color); border-color: var(--primary-color); color: #fff; }
.day-chip--sm { padding: .2rem .55rem; font-size: .75rem; }
.cfg-equal-group { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.cfg-equal-chips { display: flex; flex-wrap: wrap; gap: 0.3rem; min-width: 200px; }
/* ── Toggle opções ──────────────────────────────────────────── */
.toggle-opt {