| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- <template>
- <div>
- <DefaultHeaderPage title="Atividades" :show-filter-icon="false" />
- <div v-if="loading" class="flex flex-center q-pa-xl">
- <q-spinner color="primary" size="48px" />
- </div>
- <div
- v-else
- class="kanban-board q-px-md q-pb-md"
- style="
- display: flex;
- gap: 16px;
- overflow-x: auto;
- align-items: flex-start;
- min-height: calc(100vh - 120px);
- "
- >
- <div
- v-for="column in columns"
- :key="column.phase"
- style="
- min-width: 280px;
- max-width: 320px;
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: 8px;
- "
- >
- <!-- Column header -->
- <div
- class="row items-center justify-between q-px-md q-py-sm"
- :style="{ backgroundColor: column.color, borderRadius: '8px' }"
- >
- <span class="text-weight-bold text-white" style="font-size: 14px">
- {{ column.label }}
- </span>
- <q-badge
- color="white"
- :text-color="column.badgeTextColor"
- :label="columnMap[column.phase].length"
- style="font-size: 11px"
- />
- </div>
- <!-- Draggable card list -->
- <draggable
- :list="columnMap[column.phase]"
- :data-phase="column.phase"
- group="kanban"
- item-key="id"
- :animation="180"
- ghost-class="drag-ghost"
- drag-class="drag-active"
- style="display: flex; flex-direction: column; gap: 8px; min-height: 48px; flex: 1"
- @end="onDragEnd"
- >
- <template #item="{ element }">
- <KanbanCard
- :card="element"
- @edit="openDialog(element, column.phase)"
- @delete="removeCard($event, column.phase)"
- />
- </template>
- </draggable>
- <!-- Add button -->
- <q-btn
- flat
- color="grey-6"
- icon="mdi-plus"
- label="Adicionar"
- class="q-mt-xs full-width"
- style="border-radius: 8px; border: 1px dashed #ccc"
- @click="openDialog(null, column.phase)"
- />
- </div>
- </div>
- </div>
- </template>
- <script setup>
- import { ref, reactive, defineAsyncComponent, onMounted, watch } from "vue";
- import { useQuasar } from "quasar";
- import draggable from "vuedraggable";
- import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
- import KanbanCard from "./components/KanbanCard.vue";
- import { getKanbans, reorderKanbans } from "src/api/kanban";
- import { userStore } from "src/stores/user";
- const AddEditKanbanDialog = defineAsyncComponent(
- () => import("./components/AddEditKanbanDialog.vue"),
- );
- const $q = useQuasar();
- const { selectedUnit } = userStore();
- const loading = ref(false);
- const columns = [
- { phase: "a_fazer", label: "A Fazer", color: "#757575", badgeTextColor: "grey-9" },
- { phase: "em_progresso", label: "Em Progresso", color: "#1976D2", badgeTextColor: "blue-9" },
- { phase: "em_revisao", label: "Em Revisão", color: "#F57C00", badgeTextColor: "orange-9" },
- { phase: "concluido", label: "Concluído", color: "#388E3C", badgeTextColor: "green-9" },
- { phase: "demandas_especiais", label: "Demandas Especiais", color: "#F9A825", badgeTextColor: "yellow-9" },
- ];
- const columnMap = reactive({
- a_fazer: [],
- em_progresso: [],
- em_revisao: [],
- concluido: [],
- demandas_especiais: [],
- });
- const loadCards = async () => {
- loading.value = true;
- try {
- const data = await getKanbans();
- Object.keys(columnMap).forEach((k) => (columnMap[k] = []));
- data.forEach((card) => {
- if (columnMap[card.phase]) {
- columnMap[card.phase].push(card);
- }
- });
- } finally {
- loading.value = false;
- }
- };
- const onDragEnd = async (evt) => {
- const sourcePhase = evt.from.dataset.phase;
- const targetPhase = evt.to.dataset.phase;
- const phasesToUpdate = new Set([sourcePhase, targetPhase].filter(Boolean));
- const items = [];
- phasesToUpdate.forEach((phase) => {
- columnMap[phase].forEach((card, idx) => {
- card.phase = phase;
- items.push({ id: card.id, phase, order: idx });
- });
- });
- try {
- await reorderKanbans(items);
- } catch {
- await loadCards();
- }
- };
- const removeCard = (cardId, phase) => {
- const idx = columnMap[phase].findIndex((c) => c.id === cardId);
- if (idx !== -1) columnMap[phase].splice(idx, 1);
- };
- const openDialog = (card = null, phase = "a_fazer") => {
- $q.dialog({
- component: AddEditKanbanDialog,
- componentProps: { card, initialPhase: phase },
- }).onOk(() => {
- loadCards();
- });
- };
- // Reload whenever the user switches units via the top selector
- watch(() => selectedUnit?.id, (newId, oldId) => {
- if (newId !== oldId) loadCards();
- });
- onMounted(loadCards);
- </script>
- <style scoped>
- .kanban-board {
- padding-top: 12px;
- }
- :deep(.drag-ghost) {
- opacity: 0.4;
- border: 2px dashed #aaa;
- border-radius: 10px;
- }
- :deep(.drag-active) {
- opacity: 0.95;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
- transform: rotate(1.5deg);
- }
- </style>
|