feat: componentes reutilizáveis StatusChip, StatCard, PipelineBar
This commit is contained in:
54
front-end/app/components/PipelineBar.vue
Normal file
54
front-end/app/components/PipelineBar.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { PIPELINE_ETAPAS } from '~/types'
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
contagemPorEtapa: Record<number, number>
|
||||||
|
etapaAtual?: number
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="pipeline">
|
||||||
|
<div
|
||||||
|
v-for="(etapa, idx) in PIPELINE_ETAPAS"
|
||||||
|
:key="idx"
|
||||||
|
class="pipe-step"
|
||||||
|
:class="{
|
||||||
|
done: etapaAtual !== undefined && idx + 1 < etapaAtual,
|
||||||
|
active: etapaAtual === idx + 1,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div class="pipe-circle">
|
||||||
|
<UIcon v-if="etapaAtual !== undefined && idx + 1 < etapaAtual" name="i-heroicons-check" class="w-3 h-3" />
|
||||||
|
<span v-else>{{ idx + 1 }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="pipe-label">{{ etapa }}</p>
|
||||||
|
<p class="pipe-count">{{ contagemPorEtapa[idx + 1] ?? 0 }} editais</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pipeline { display: flex; gap: 0; overflow-x: auto; padding: 14px 0; }
|
||||||
|
.pipe-step {
|
||||||
|
flex: 1; min-width: 80px; text-align: center;
|
||||||
|
display: flex; flex-direction: column; align-items: center; gap: 4px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.pipe-step:not(:last-child)::after {
|
||||||
|
content: ''; position: absolute; top: 13px; left: 60%; right: -40%;
|
||||||
|
height: 2px; background: #e2e8f0; z-index: 0;
|
||||||
|
}
|
||||||
|
.pipe-step.done:not(:last-child)::after { background: #667eea; }
|
||||||
|
.pipe-circle {
|
||||||
|
width: 28px; height: 28px; border-radius: 50%;
|
||||||
|
background: #f1f5f9; border: 2px solid #e2e8f0;
|
||||||
|
display: flex; align-items: center; justify-content: center;
|
||||||
|
font-size: 10px; font-weight: 700; color: #94a3b8; position: relative; z-index: 1;
|
||||||
|
}
|
||||||
|
.pipe-step.done .pipe-circle { background: #667eea; border-color: #667eea; color: white; }
|
||||||
|
.pipe-step.active .pipe-circle { background: white; border-color: #667eea; color: #667eea; box-shadow: 0 0 0 3px rgba(102,126,234,0.15); }
|
||||||
|
.pipe-label { font-size: 9.5px; color: #94a3b8; line-height: 1.2; }
|
||||||
|
.pipe-step.done .pipe-label, .pipe-step.active .pipe-label { color: #667eea; font-weight: 600; }
|
||||||
|
.pipe-count { font-size: 9px; color: #94a3b8; font-weight: 600; }
|
||||||
|
</style>
|
||||||
23
front-end/app/components/StatCard.vue
Normal file
23
front-end/app/components/StatCard.vue
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
label: string
|
||||||
|
value: string | number
|
||||||
|
sub?: string
|
||||||
|
color?: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="stat-card">
|
||||||
|
<p class="stat-label">{{ label }}</p>
|
||||||
|
<p class="stat-value" :style="color ? { color } : {}">{{ value }}</p>
|
||||||
|
<p v-if="sub" class="stat-sub">{{ sub }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.stat-card { background: white; border-radius: 11px; padding: 16px; border: 1px solid #e2e8f0; }
|
||||||
|
.stat-label { font-size: 10.5px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.5px; font-weight: 600; margin-bottom: 8px; }
|
||||||
|
.stat-value { font-size: 26px; font-weight: 800; color: #0f172a; letter-spacing: -1px; line-height: 1; }
|
||||||
|
.stat-sub { font-size: 10.5px; color: #64748b; margin-top: 5px; }
|
||||||
|
</style>
|
||||||
24
front-end/app/components/StatusChip.vue
Normal file
24
front-end/app/components/StatusChip.vue
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import type { StatusEdital } from '~/types'
|
||||||
|
import { STATUS_EDITAL_CONFIG } from '~/types'
|
||||||
|
|
||||||
|
const props = defineProps<{ status: StatusEdital }>()
|
||||||
|
const config = computed(() => STATUS_EDITAL_CONFIG[props.status])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span class="chip" :style="{ background: config.bg, color: config.color }">
|
||||||
|
{{ config.label }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.chip {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user