3 Revīzijas dda9392564 ... b85af399ed

Autors SHA1 Ziņojums Datums
  ebagabee b85af399ed chore(general): change the past props name to the new props name 2 dienas atpakaļ
  ebagabee 37a89f7376 feat(contracts): remove the past code to the new feature. 2 dienas atpakaļ
  ebagabee e5d0bb376e chore(DefaultTable): change the descricao to description and feminino to female to follows english pattern code 2 dienas atpakaļ

+ 3 - 3
src/components/defaults/DefaultTable.vue

@@ -23,7 +23,7 @@
         <div v-if="title" class="column text-h6">
           <span>{{ title }}</span>
           <span class="text-body2">{{
-            `${rows.length} ${descricao} ${feminino ? "cadastradas" : "cadastrados"}`
+            `${rows.length} ${description} ${female ? "cadastradas" : "cadastrados"}`
           }}</span>
         </div>
 
@@ -208,11 +208,11 @@ const {
     type: Function,
     default: null,
   },
-  descricao: {
+  description: {
     type: String,
     default: "linhas",
   },
-  feminino: {
+  female: {
     type: Boolean,
     default: true,
   },

+ 2 - 2
src/pages/contracts/ContractPage.vue

@@ -34,8 +34,8 @@
         no-api-call
         :rows
         title="Lista de Contratos"
-        descricao="Contratos"
-        :feminino="false"
+        description="Contratos"
+        :female="false"
       />
     </div>
   </div>

+ 2 - 2
src/pages/franchisee/FranchiseePage.vue

@@ -5,8 +5,8 @@
     <div class="q-px-sm">
       <DefaultTable
         title="Lista de Unidades"
-        descricao="unidades"
-        :feminino="false"
+        description="unidades"
+        :female="false"
         :columns
         add-item
         open-item

+ 6 - 12
src/pages/unit/UnitActionPage.vue

@@ -12,14 +12,8 @@
       :set-update-form-as-original="setUpdateFormAsOriginal"
     />
     <template v-if="unitId">
-      <PartnersTab
-        v-show="activeTab === 'partners'"
-        :unit-id="unitId"
-      />
-      <ContractsTab
-        v-show="activeTab === 'contracts'"
-        :unit-id="unitId"
-      />
+      <PartnersTab v-show="activeTab === 'partners'" :unit-id="unitId" />
+      <ContractsTab v-show="activeTab === 'contracts'" :unit-id="unitId" />
       <FinancialTab v-show="activeTab === 'financial'" :unit-id="unitId" />
       <HistoryTab v-show="activeTab === 'history'" :unit-id="unitId" />
       <MediasTab v-show="activeTab === 'medias'" :unit-id="unitId" />
@@ -41,7 +35,9 @@ import { useRoute } from "vue-router";
 import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 
 const route = useRoute();
-const unitId = computed(() => route.params.id ? Number(route.params.id) : null);
+const unitId = computed(() =>
+  route.params.id ? Number(route.params.id) : null,
+);
 
 const { form, getFormAsFormData, setUpdateFormAsOriginal } =
   useFormUpdateTracker({
@@ -74,7 +70,5 @@ const allTabs = [
   { name: "medias", label: "Mídias" },
 ];
 
-const tabs = computed(() =>
-  unitId.value ? allTabs : allTabs.slice(0, 1)
-);
+const tabs = computed(() => (unitId.value ? allTabs : allTabs.slice(0, 1)));
 </script>

+ 61 - 237
src/pages/unit/tabs/ContractsTab.vue

@@ -1,253 +1,77 @@
 <template>
   <div class="q-pa-md">
-    <input
-      ref="fileInputRef"
-      type="file"
-      accept=".pdf"
-      class="hidden"
-      @change="onFileSelected"
-    />
-
-    <div class="row q-col-gutter-md">
-      <!-- Coluna esquerda: lista -->
-      <div class="col-12 col-md-5">
-        <div class="row justify-between items-center q-mb-md">
-          <span class="text-subtitle1 text-weight-medium">Contratos</span>
-          <q-btn
-            icon="add"
-            color="primary-2"
-            style="height: 40px; width: 40px; border-radius: 8px"
-            :loading="uploading"
-            @click="fileInputRef.click()"
-          />
-        </div>
-
-        <div v-if="loading" class="row justify-center q-pa-xl">
-          <q-spinner color="primary" size="40px" />
-        </div>
-
-        <template v-else>
-          <div v-if="contracts.length === 0" class="text-center text-grey-6 q-pa-xl">
-            <q-icon name="mdi-file-pdf-box" size="48px" color="grey-4" />
-            <div class="q-mt-sm">Nenhum contrato adicionado.</div>
-            <div v-if="!unitId" class="text-caption q-mt-xs">
-              Os contratos serão enviados junto com a unidade.
-            </div>
-          </div>
-
-          <q-list v-else separator>
-            <q-item
-              v-for="(contract, index) in contracts"
-              :key="contract.id ?? index"
-              clickable
-              :active="selectedIndex === index"
-              active-class="contract-item-active"
-              @click="selectedIndex = index"
-            >
-              <q-item-section avatar>
-                <q-icon name="mdi-file-pdf-box" color="negative" size="md" />
-              </q-item-section>
-
-              <q-item-section>
-                <q-item-label class="ellipsis" style="max-width: 180px">
-                  {{ contract.name }}
-                </q-item-label>
-                <q-item-label caption>
-                  <span v-if="contract.id">{{ formatDate(contract.created_at) }}</span>
-                  <q-badge v-else color="orange" label="Pendente" />
-                </q-item-label>
-              </q-item-section>
-
-              <q-item-section side>
-                <q-btn
-                  flat
-                  round
-                  dense
-                  icon="delete"
-                  color="negative"
-                  size="sm"
-                  @click.stop="onRemove(contract, index)"
-                />
-              </q-item-section>
-            </q-item>
-          </q-list>
-        </template>
-      </div>
-
-      <!-- Coluna direita: pré-visualização -->
-      <div class="col-12 col-md-7">
-        <div class="preview-box">
-          <div
-            v-if="selectedIndex === null || !contracts[selectedIndex]"
-            class="flex flex-center full-height text-grey-5"
-            style="min-height: 500px"
-          >
-            <div class="column items-center q-gutter-sm">
-              <q-icon name="mdi-file-pdf-box" size="64px" color="grey-3" />
-              <span>Selecione um contrato para visualizar</span>
-            </div>
+    <DefaultTable
+      :columns="columns"
+      :no-api-call="true"
+      :show-search-field="true"
+      :add-item="true"
+      :female="false"
+      title="Contratos"
+      description="Contratos"
+    >
+      <template #body-cell-contract_dates="{ row }">
+        <q-td>{{ row.contract_start_date }} - {{ row.contract_end_date }}</q-td>
+      </template>
+
+      <template #body-cell-actions="{ row: contract }">
+        <q-td auto-width>
+          <div class="row no-wrap" style="gap: 4px">
+            <q-btn flat round dense icon="mdi-eye-outline" size="sm" />
+            <q-btn
+              v-show="contract.status !== 'active'"
+              flat
+              round
+              dense
+              icon="mdi-file-edit-outline"
+              size="sm"
+            />
           </div>
-
-          <iframe
-            v-else
-            :src="getPreviewUrl(contracts[selectedIndex])"
-            style="width: 100%; min-height: 500px; border: none; border-radius: 8px"
-          />
-        </div>
-      </div>
-    </div>
+        </q-td>
+      </template>
+    </DefaultTable>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted, onUnmounted } from "vue";
-import { useQuasar } from "quasar";
-import { getContractsByUnit, createContract, deleteContract } from "src/api/unit_contract";
+import DefaultTable from "src/components/defaults/DefaultTable.vue";
 
-const props = defineProps({
+defineProps({
   unitId: {
     type: Number,
     default: null,
   },
 });
 
-const pendingContracts = defineModel("contracts", { type: Array, default: () => [] });
-
-const $q = useQuasar();
-const fileInputRef = ref(null);
-const contracts = ref([]);
-const selectedIndex = ref(null);
-const loading = ref(false);
-const uploading = ref(false);
-
-// ─── Busca contratos salvos (modo edição) ───────────────────────────────────
-async function fetchContracts() {
-  if (!props.unitId) {
-    contracts.value = pendingContracts.value;
-    return;
-  }
-
-  loading.value = true;
-  try {
-    contracts.value = await getContractsByUnit(props.unitId);
-  } catch (e) {
-    console.error(e);
-  } finally {
-    loading.value = false;
-  }
-}
-
-// ─── Seleção de arquivo ─────────────────────────────────────────────────────
-async function onFileSelected(event) {
-  const file = event.target.files[0];
-  if (!file) return;
-
-  // Limpa o input para permitir selecionar o mesmo arquivo novamente
-  event.target.value = "";
-
-  if (props.unitId) {
-    // Modo edição: envia direto para a API
-    uploading.value = true;
-    try {
-      const formData = new FormData();
-      formData.append("unit_id", props.unitId);
-      formData.append("file", file);
-      formData.append("name", file.name);
-
-      const created = await createContract(formData);
-      contracts.value.unshift(created);
-      selectedIndex.value = 0;
-    } catch (e) {
-      console.error(e);
-    } finally {
-      uploading.value = false;
-    }
-  } else {
-    // Modo criação: adiciona à lista pendente
-    const previewUrl = URL.createObjectURL(file);
-    const item = { name: file.name, file, previewUrl };
-    pendingContracts.value.push(item);
-  }
-}
-
-// ─── Remoção ────────────────────────────────────────────────────────────────
-function onRemove(contract, index) {
-  if (contract.id) {
-    // Modo edição: confirma e deleta via API
-    $q.dialog({
-      title: "Remover contrato",
-      message: `Deseja remover o contrato "${contract.name}"?`,
-      ok: { color: "negative", label: "Remover" },
-      cancel: { color: "primary", outline: true, label: "Cancelar" },
-    }).onOk(async () => {
-      try {
-        await deleteContract(contract.id);
-        contracts.value.splice(index, 1);
-        if (selectedIndex.value === index) selectedIndex.value = null;
-        else if (selectedIndex.value > index) selectedIndex.value--;
-      } catch (e) {
-        console.error(e);
-      }
-    });
-  } else {
-    // Modo criação: revoga object URL e remove da lista pendente
-    if (contract.previewUrl) URL.revokeObjectURL(contract.previewUrl);
-    pendingContracts.value.splice(index, 1);
-  }
-}
-
-// ─── Helpers ────────────────────────────────────────────────────────────────
-function getPreviewUrl(contract) {
-  return contract.previewUrl ?? contract.file_url;
-}
-
-function formatDate(dateStr) {
-  if (!dateStr) return "";
-  return new Date(dateStr).toLocaleDateString("pt-BR");
-}
-
-// ─── Sincroniza lista pendente quando o pai atualiza ────────────────────────
-// (necessário pois v-show mantém o componente vivo mas não re-executa onMounted)
-import { watch } from "vue";
-
-watch(
-  pendingContracts,
-  (val) => {
-    if (!props.unitId) contracts.value = val;
+const columns = [
+  {
+    name: "contract_number",
+    label: "Contrato",
+    field: "contract_number",
+    align: "left",
   },
-  { immediate: true },
-);
-
-onMounted(fetchContracts);
-
-onUnmounted(() => {
-  // Libera object URLs de pendentes não enviados
-  if (!props.unitId) {
-    pendingContracts.value.forEach((c) => {
-      if (c.previewUrl) URL.revokeObjectURL(c.previewUrl);
-    });
-  }
-});
+  {
+    name: "contract_dates",
+    label: "Data Inicial - Final",
+    field: "contract_start_date",
+    align: "left",
+  },
+  {
+    name: "contract_tbr",
+    label: "TBR",
+    field: "contract_tbr",
+    align: "left",
+  },
+  {
+    name: "contract_status",
+    label: "Status Contrato",
+    field: "contract_status",
+    align: "left",
+  },
+  {
+    name: "actions",
+    label: "Ações",
+    field: "actions",
+    align: "center",
+  },
+];
 </script>
-
-<style scoped>
-.hidden {
-  display: none;
-}
-
-.preview-box {
-  border: 1px solid #e0e0e0;
-  border-radius: 8px;
-  overflow: hidden;
-}
-
-.contract-item-active {
-  background-color: rgba(255, 131, 64, 0.08);
-}
-
-.ellipsis {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-</style>

+ 1 - 1
src/pages/users/UserPage.vue

@@ -25,7 +25,7 @@
         v-model:rows="filteredRows"
         title="Lista de Usuários"
         :columns
-        descricao="Usuários"
+        description="Usuários"
         no-api-call
         add-item
         add-item-route="UserAddPage"