浏览代码

feat: :sparkles: cruds cliente -> prestadores bloqueados + crud prestador -> clientes bloqueados

cruds cliente -> prestadores bloqueados + crud prestador -> clientes bloqueados
Gustavo Zanatta 5 天之前
父节点
当前提交
f922826e5f

+ 21 - 0
src/api/clientProviderBlock.js

@@ -0,0 +1,21 @@
+import api from "src/api";
+
+export const getClientProviderBlocks = async (clientId) => {
+  const { data } = await api.get(`/client-provider-blocks/${clientId}`);
+  return data.payload;
+}
+
+export const getBlockedProviderIds = async (clientId) => {
+  const { data } = await api.get(`/client-provider-blocks/${clientId}/ids`);
+  return data.payload;
+}
+
+export const createClientProviderBlock = async (info) => {
+  const { data } = await api.post(`/client-provider-blocks`, info);
+  return data.payload;
+}
+
+export const deleteClientProviderBlock = async (id) => {
+  const { data } = await api.delete(`/client-provider-blocks/${id}`);
+  return data.payload;
+}

+ 21 - 0
src/api/providerClientBlock.js

@@ -0,0 +1,21 @@
+import api from "src/api";
+
+export const getProviderClientBlocks = async (providerId) => {
+  const { data } = await api.get(`/provider-client-blocks/${providerId}`);
+  return data.payload;
+}
+
+export const getBlockedClientIds = async (providerId) => {
+  const { data } = await api.get(`/provider-client-blocks/${providerId}/ids`);
+  return data.payload;
+}
+
+export const createProviderClientBlock = async (info) => {
+  const { data } = await api.post(`/provider-client-blocks`, info);
+  return data.payload;
+}
+
+export const deleteProviderClientBlock = async (id) => {
+  const { data } = await api.delete(`/provider-client-blocks/${id}`);
+  return data.payload;
+}

+ 22 - 0
src/i18n/locales/en.json

@@ -413,6 +413,28 @@
     "notes": "Notes",
     "favorited_at": "Favorited at"
   },
+  "provider_client_blocks": {
+    "singular": "Blocked Client",
+    "plural": "Blocked Clients",
+    "header": "Blocked Clients",
+    "tab": "Blocked Clients",
+    "add_button": "Add Block",
+    "edit_button": "Edit Block",
+    "empty_state": "No blocked clients",
+    "client": "Client",
+    "blocked_at": "Blocked at"
+  },
+  "client_provider_blocks": {
+    "singular": "Blocked Provider",
+    "plural": "Blocked Providers",
+    "header": "Blocked Providers",
+    "tab": "Blocked Providers",
+    "add_button": "Add Block",
+    "edit_button": "Edit Block",
+    "empty_state": "No blocked providers",
+    "provider": "Provider",
+    "blocked_at": "Blocked at"
+  },
   "client_payment_methods": {
     "singular": "Payment Method",
     "plural": "Payment Methods",

+ 22 - 0
src/i18n/locales/es.json

@@ -413,6 +413,28 @@
     "notes": "Observaciones",
     "favorited_at": "Favoritado en"
   },
+  "provider_client_blocks": {
+    "singular": "Cliente Bloqueado",
+    "plural": "Clientes Bloqueados",
+    "header": "Clientes Bloqueados",
+    "tab": "Clientes Bloqueados",
+    "add_button": "Agregar Bloqueo",
+    "edit_button": "Editar Bloqueo",
+    "empty_state": "No hay clientes bloqueados",
+    "client": "Cliente",
+    "blocked_at": "Bloqueado en"
+  },
+  "client_provider_blocks": {
+    "singular": "Proveedor Bloqueado",
+    "plural": "Proveedores Bloqueados",
+    "header": "Proveedores Bloqueados",
+    "tab": "Proveedores Bloqueados",
+    "add_button": "Agregar Bloqueo",
+    "edit_button": "Editar Bloqueo",
+    "empty_state": "No hay proveedores bloqueados",
+    "provider": "Proveedor",
+    "blocked_at": "Bloqueado en"
+  },
   "client_payment_methods": {
     "singular": "Método de Pago",
     "plural": "Métodos de Pago",

+ 22 - 0
src/i18n/locales/pt.json

@@ -413,6 +413,28 @@
     "notes": "Observações",
     "favorited_at": "Favoritado em"
   },
+  "provider_client_blocks": {
+    "singular": "Cliente Bloqueado",
+    "plural": "Clientes Bloqueados",
+    "header": "Clientes Bloqueados",
+    "tab": "Clientes Bloqueados",
+    "add_button": "Adicionar Bloqueio",
+    "edit_button": "Editar Bloqueio",
+    "empty_state": "Nenhum cliente bloqueado",
+    "client": "Cliente",
+    "blocked_at": "Bloqueado em"
+  },
+  "client_provider_blocks": {
+    "singular": "Prestador Bloqueado",
+    "plural": "Prestadores Bloqueados",
+    "header": "Prestadores Bloqueados",
+    "tab": "Prestadores Bloqueados",
+    "add_button": "Adicionar Bloqueio",
+    "edit_button": "Editar Bloqueio",
+    "empty_state": "Nenhum prestador bloqueado",
+    "provider": "Prestador",
+    "blocked_at": "Bloqueado em"
+  },
   "client_payment_methods": {
     "singular": "Método de Pagamento",
     "plural": "Métodos de Pagamento",

+ 6 - 0
src/pages/client/components/AddEditClientDialog.vue

@@ -15,6 +15,7 @@
         <q-tab v-if="client" name="addresses" :label="$t('address.tab')" />
         <q-tab v-if="client" name="favorites" :label="$t('client_favorite_providers.header')" />
         <q-tab v-if="client" name="payment_methods" :label="$t('client_payment_methods.header')" />
+        <q-tab v-if="client" name="blocked_providers" :label="$t('client_provider_blocks.tab')" />
       </q-tabs>
 
       <q-separator v-if="client" />
@@ -75,6 +76,10 @@
         <q-tab-panel v-if="client" name="payment_methods">
           <ClientPaymentMethodsPanel :client-id="client.id" />
         </q-tab-panel>
+
+        <q-tab-panel v-if="client" name="blocked_providers">
+          <ClientProvidersBlocksPanel :client-id="client.id" />
+        </q-tab-panel>
       </q-tab-panels>
     </q-card>
   </q-dialog>
@@ -95,6 +100,7 @@ import UserSelect from 'src/components/user/UserSelect.vue';
 import AddressesPanel from 'src/pages/address/components/AddressesPanel.vue';
 import ClientFavoriteProvidersPanel from './ClientFavoriteProvidersPanel.vue';
 import ClientPaymentMethodsPanel from './ClientPaymentMethodsPanel.vue';
+import ClientProvidersBlocksPanel from './ClientProvidersBlocksPanel.vue';
 
 defineEmits([
   ...useDialogPluginComponent.emits,

+ 109 - 0
src/pages/client/components/AddEditClientProviderBlockDialog.vue

@@ -0,0 +1,109 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin" style="width: 700px; max-width: 90vw">
+      <DefaultDialogHeader :title="title" @close="onDialogCancel" />
+      <q-form ref="formRef" @submit="onOKClick">
+        <q-card-section class="row q-col-gutter-sm">
+          <ProviderSelect
+            v-model="selectedProvider"
+            :label="$t('client_provider_blocks.provider')"
+            :rules="[inputRules.required]"
+            :error="!!serverErrors?.provider_id"
+            :error-message="serverErrors?.provider_id"
+            :initial-id="block ? block.provider_id : null"
+            :exclude-ids="excludedProviderIds"
+            class="col-12"
+            @update:model-value="($event) => atualizaForm($event)"
+          />
+        </q-card-section>
+
+        <q-card-actions align="right">
+          <q-btn
+            flat
+            :label="$t('common.actions.cancel')"
+            color="negative"
+            @click="onDialogCancel"
+          />
+          <q-btn
+            type="submit"
+            :label="$t('common.actions.save')"
+            :loading="loading"
+            :disable="!hasUpdatedFields"
+            color="primary"
+          />
+        </q-card-actions>
+      </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useDialogPluginComponent } from 'quasar'
+import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker'
+import { useSubmitHandler } from 'src/composables/useSubmitHandler'
+import { createClientProviderBlock, getBlockedProviderIds } from 'src/api/clientProviderBlock'
+import DefaultDialogHeader from 'src/components/defaults/DefaultDialogHeader.vue'
+import ProviderSelect from 'src/components/provider/ProviderSelect.vue'
+import { useInputRules } from 'src/composables/useInputRules'
+
+const props = defineProps({
+  block: {
+    type: Object,
+    default: null
+  },
+  clientId: {
+    type: Number,
+    required: true
+  },
+  title: {
+    type: Function,
+    default: () => ''
+  }
+})
+
+defineEmits([...useDialogPluginComponent.emits])
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
+const { inputRules } = useInputRules()
+const formRef = ref(null)
+const selectedProvider = ref(null)
+const excludedProviderIds = ref([])
+
+const { form, hasUpdatedFields } = useFormUpdateTracker({
+  client_id: props.clientId,
+  provider_id: props.block ? props.block.provider_id : null
+})
+
+const {
+  loading,
+  serverErrors,
+  execute: submitForm,
+} = useSubmitHandler({
+  onSuccess: () => onDialogOK(true),
+  formRef: formRef,
+})
+
+const onOKClick = async () => {
+  if (selectedProvider.value?.value) {
+    form.provider_id = selectedProvider.value.value
+  }
+
+  await submitForm(() => createClientProviderBlock({ ...form }))
+}
+
+const atualizaForm = (provider) => {
+  form.provider_id = provider.value
+  serverErrors.provider_id = null
+}
+
+onMounted(async () => {
+  try {
+    const blockedIds = await getBlockedProviderIds(props.clientId)
+    excludedProviderIds.value = props.block
+      ? blockedIds.filter(id => id !== props.block.provider_id)
+      : blockedIds
+  } catch (error) {
+    console.error('Error loading blocked provider ids:', error)
+  }
+})
+</script>

+ 88 - 0
src/pages/client/components/ClientProvidersBlocksPanel.vue

@@ -0,0 +1,88 @@
+<template>
+  <DefaultTable
+    ref="tableRef"
+    :columns="columns"
+    :loading="loading"
+    :api-call="() => getClientProviderBlocks(props.clientId)"
+    :add-button-label="$t('client_provider_blocks.add_button')"
+    :empty-message="$t('client_provider_blocks.empty_state')"
+    :delete-function="deleteClientProviderBlock"
+    :mostrar-selecao-de-colunas="false"
+    :mostrar-botao-fullscreen="false"
+    :mostrar-toggle-inativos="false"
+    :open-item="false"
+    @on-add-item="onAddItem"
+  >
+    <template #body-cell-blocked_at="template_props">
+      <q-td :props="template_props">
+        {{ format(parseISO(template_props.row.created_at)) }}
+      </q-td>
+    </template>
+  </DefaultTable>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useQuasar } from 'quasar'
+import { getClientProviderBlocks, deleteClientProviderBlock } from 'src/api/clientProviderBlock'
+import DefaultTable from 'src/components/defaults/DefaultTable.vue'
+import AddEditClientProviderBlockDialog from './AddEditClientProviderBlockDialog.vue'
+import { format, parseISO } from 'date-fns'
+
+const props = defineProps({
+  clientId: {
+    type: Number,
+    required: true
+  }
+})
+
+const { t } = useI18n()
+const $q = useQuasar()
+const tableRef = ref(null)
+const loading = ref(false)
+
+const columns = computed(() => [
+  {
+    name: 'provider_name',
+    label: t('client_provider_blocks.provider'),
+    align: 'left',
+    field: 'provider_name',
+    sortable: true
+  },
+  {
+    name: 'provider_email',
+    label: t('common.terms.email'),
+    align: 'left',
+    field: 'provider_email',
+    sortable: false
+  },
+  {
+    name: 'blocked_at',
+    label: t('client_provider_blocks.blocked_at'),
+    align: 'left',
+    field: 'created_at',
+    sortable: true
+  },
+  {
+    name: 'actions',
+    label: t('common.terms.actions'),
+    align: 'center',
+    field: 'actions'
+  }
+])
+
+const onAddItem = () => {
+  $q.dialog({
+    component: AddEditClientProviderBlockDialog,
+    componentProps: {
+      clientId: props.clientId,
+      title: () => t('client_provider_blocks.add_button')
+    }
+  }).onOk((success) => {
+    if (success) {
+      tableRef.value.refresh()
+    }
+  })
+}
+</script>

+ 109 - 0
src/pages/provider/components/AddEditProviderClientBlockDialog.vue

@@ -0,0 +1,109 @@
+<template>
+  <q-dialog ref="dialogRef" @hide="onDialogHide">
+    <q-card class="q-dialog-plugin" style="width: 700px; max-width: 90vw">
+      <DefaultDialogHeader :title="title" @close="onDialogCancel" />
+      <q-form ref="formRef" @submit="onOKClick">
+        <q-card-section class="row q-col-gutter-sm">
+          <ClientSelect
+            v-model="selectedClient"
+            :label="$t('provider_client_blocks.client')"
+            :rules="[inputRules.required]"
+            :error="!!serverErrors?.client_id"
+            :error-message="serverErrors?.client_id"
+            :initial-id="block ? block.client_id : null"
+            :exclude-ids="excludedClientIds"
+            class="col-12"
+            @update:model-value="($event) => atualizaForm($event)"
+          />
+        </q-card-section>
+
+        <q-card-actions align="right">
+          <q-btn
+            flat
+            :label="$t('common.actions.cancel')"
+            color="negative"
+            @click="onDialogCancel"
+          />
+          <q-btn
+            type="submit"
+            :label="$t('common.actions.save')"
+            :loading="loading"
+            :disable="!hasUpdatedFields"
+            color="primary"
+          />
+        </q-card-actions>
+      </q-form>
+    </q-card>
+  </q-dialog>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useDialogPluginComponent } from 'quasar'
+import { useFormUpdateTracker } from 'src/composables/useFormUpdateTracker'
+import { useSubmitHandler } from 'src/composables/useSubmitHandler'
+import { createProviderClientBlock, getBlockedClientIds } from 'src/api/providerClientBlock'
+import DefaultDialogHeader from 'src/components/defaults/DefaultDialogHeader.vue'
+import ClientSelect from 'src/components/client/ClientSelect.vue'
+import { useInputRules } from 'src/composables/useInputRules'
+
+const props = defineProps({
+  block: {
+    type: Object,
+    default: null
+  },
+  providerId: {
+    type: Number,
+    required: true
+  },
+  title: {
+    type: Function,
+    default: () => ''
+  }
+})
+
+defineEmits([...useDialogPluginComponent.emits])
+const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
+const { inputRules } = useInputRules()
+const formRef = ref(null)
+const selectedClient = ref(null)
+const excludedClientIds = ref([])
+
+const { form, hasUpdatedFields } = useFormUpdateTracker({
+  provider_id: props.providerId,
+  client_id: props.block ? props.block.client_id : null
+})
+
+const {
+  loading,
+  serverErrors,
+  execute: submitForm,
+} = useSubmitHandler({
+  onSuccess: () => onDialogOK(true),
+  formRef: formRef,
+})
+
+const onOKClick = async () => {
+  if (selectedClient.value?.value) {
+    form.client_id = selectedClient.value.value
+  }
+
+  await submitForm(() => createProviderClientBlock({ ...form }))
+}
+
+const atualizaForm = (client) => {
+  form.client_id = client.value;
+  serverErrors.client_id = null;
+}
+
+onMounted(async () => {
+  try {
+    const blockedIds = await getBlockedClientIds(props.providerId)
+    excludedClientIds.value = props.block
+      ? blockedIds.filter(id => id !== props.block.client_id)
+      : blockedIds
+  } catch (error) {
+    console.error('Error loading blocked client ids:', error)
+  }
+})
+</script>

+ 6 - 0
src/pages/provider/components/AddEditProviderDialog.vue

@@ -9,6 +9,7 @@
         <q-tab v-if="provider" name="payment_methods" :label="$t('provider_payment_methods.header')" />
         <q-tab v-if="provider" name="working_days" :label="$t('provider_working_days.header')" />
         <q-tab v-if="provider" name="blocked_days" :label="$t('provider_blocked_days.header')" />
+        <q-tab v-if="provider" name="blocked_clients" :label="$t('provider_client_blocks.tab')" />
       </q-tabs>
 
       <q-separator v-if="provider" />
@@ -186,6 +187,10 @@
           <q-tab-panel v-if="provider" name="blocked_days">
             <ProviderBlockedDaysPanel :provider-id="provider.id" />
           </q-tab-panel>
+
+          <q-tab-panel v-if="provider" name="blocked_clients">
+            <ProviderClientsBlocksPanel :provider-id="provider.id" />
+          </q-tab-panel>
         </q-tab-panels>
       </div>
     </q-card>
@@ -210,6 +215,7 @@ import ProviderServicesTypesPanel from "./ProviderServicesTypesPanel.vue";
 import ProviderPaymentMethodsPanel from "./ProviderPaymentMethodsPanel.vue";
 import ProviderWorkingDaysPanel from "./ProviderWorkingDaysPanel.vue";
 import ProviderBlockedDaysPanel from "./ProviderBlockedDaysPanel.vue";
+import ProviderClientsBlocksPanel from "./ProviderClientsBlocksPanel.vue";
 
 defineEmits([
   ...useDialogPluginComponent.emits,

+ 89 - 0
src/pages/provider/components/ProviderClientsBlocksPanel.vue

@@ -0,0 +1,89 @@
+<template>
+  <DefaultTable
+    ref="tableRef"
+    :columns="columns"
+    :loading="loading"
+    :api-call="() => getProviderClientBlocks(props.providerId)"
+    :add-button-label="$t('provider_client_blocks.add_button')"
+    :empty-message="$t('provider_client_blocks.empty_state')"
+    :delete-function="deleteProviderClientBlock"
+    :mostrar-selecao-de-colunas="false"
+    :mostrar-botao-fullscreen="false"
+    :mostrar-toggle-inativos="false"
+    :open-item="false"
+    @on-add-item="onAddItem"
+  >
+    <template #body-cell-blocked_at="template_props">
+      <q-td :props="template_props">
+        {{ format(parseISO(template_props.row.created_at)) }}
+      </q-td>
+    </template>
+  </DefaultTable>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useQuasar } from 'quasar'
+import { getProviderClientBlocks, deleteProviderClientBlock } from 'src/api/providerClientBlock'
+import DefaultTable from 'src/components/defaults/DefaultTable.vue'
+import AddEditProviderClientBlockDialog from './AddEditProviderClientBlockDialog.vue'
+import { format, parseISO } from 'date-fns'
+
+const props = defineProps({
+  providerId: {
+    type: Number,
+    required: true
+  }
+})
+
+const { t } = useI18n()
+const $q = useQuasar()
+const tableRef = ref(null)
+const loading = ref(false)
+
+const columns = computed(() => [
+  {
+    name: 'client_name',
+    label: t('provider_client_blocks.client'),
+    align: 'left',
+    field: 'client_name',
+    sortable: true
+  },
+  {
+    name: 'client_email',
+    label: t('common.terms.email'),
+    align: 'left',
+    field: 'client_email',
+    sortable: false
+  },
+  {
+    name: 'blocked_at',
+    label: t('provider_client_blocks.blocked_at'),
+    align: 'left',
+    field: 'created_at',
+    sortable: true
+  },
+  {
+    name: 'actions',
+    label: t('common.terms.actions'),
+    align: 'center',
+    field: 'actions'
+  }
+])
+
+const onAddItem = () => {
+  $q.dialog({
+    component: AddEditProviderClientBlockDialog,
+    componentProps: {
+      providerId: props.providerId,
+      title: () => t('provider_client_blocks.add_button')
+    }
+  }).onOk((success) => {
+    if (success) {
+      tableRef.value.refresh()
+    }
+  })
+}
+
+</script>