فهرست منبع

feat: :sparkles: feat (recusa-sobmedida-presatdor) Foi criada a opção de recusa para o prestador

Foi criada no dialog de quero a tender a oportuidade o botão de recusa-la tambem, caso o presatdor não queira atender aquela diaria em especifico

fase:dev | origin:escopo
kayo henrique 1 هفته پیش
والد
کامیت
0a779eb834

+ 1 - 0
.gitignore

@@ -37,3 +37,4 @@ Thumbs.db
 
 /dist-android
 /dist-ios
+/src-capacitor

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 710 - 4
package-lock.json


+ 1 - 0
package.json

@@ -42,6 +42,7 @@
   "devDependencies": {
     "@bufbuild/buf": "^1.54.0",
     "@bufbuild/protoc-gen-es": "^2.5.1",
+    "@capacitor/cli": "^8.3.4",
     "@capacitor/keyboard": "^7.0.1",
     "@capacitor/preferences": "^7.0.1",
     "@capacitor/status-bar": "^7.0.1",

+ 1 - 0
src-capacitor/package-lock.json

@@ -73,6 +73,7 @@
       "resolved": "https://registry.npmjs.org/@capacitor/core/-/core-7.2.0.tgz",
       "integrity": "sha512-2zCnA6RJeZ9ec4470o8QMZEQTWpekw9FNoqm5TLc10jeCrhvHVI8MPgxdZVc3mOdFlyieYu4AS1fNxSqbS57Pw==",
       "license": "MIT",
+      "peer": true,
       "dependencies": {
         "tslib": "^2.1.0"
       }

+ 48 - 1
src/api/opportunities.js

@@ -37,4 +37,51 @@ export const proposalOpportunity = async (scheduleId, providerId) => {
     console.error('[API] proposalOpportunity error:', error)
     return null
   }
-} 
+} 
+
+export default [
+  {
+    path: 'opportunities',
+    name: 'OpportunitiesPage',
+    component: () => import('src/pages/opportunities/OpportunitiesPage.vue'),
+    meta: {
+      title: 'Oportunidades',
+      requireAuth: true,
+      breadcrumbs: [
+        {
+          name: 'DashboardPage',
+          title: 'ui.navigation.dashboard'
+        },
+        {
+          name: 'OpportunitiesPage',
+          title: 'provider.dashboard.opportunities.title'
+        }
+      ]
+    }
+  },
+]
+
+export const refuseOpportunity = async (
+  scheduleId,
+  providerId
+) => {
+  try {
+
+    const { data } = await api.post(
+      `/custom-schedule/${scheduleId}/refuse-opportunity`,
+      {
+        provider_id: providerId
+      }
+    )
+
+    return data?.payload || true
+
+  } catch (error) {
+    console.error(
+      '[API] refuseOpportunity error:',
+      error
+    )
+
+    return null
+  }
+}

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

@@ -323,6 +323,13 @@
         "message": "Se seu pedido for aceito pelo cliente você receberá um aviso confirmando o agendamento e aparecerá nos seus próximos serviços.",
         "close": "fechar"
       },
+      "refuse_opportunity_dialog": {
+        "title": "Recusar oportunidade",
+        "description": "Deseja mesmo recusar este atendimento?",
+        "warning": "Ao recusar, esta oportunidade será removida da sua listagem.",
+        "btn_cancel": "voltar",
+        "btn_confirm": "recusar atendimento"
+      },
       "favorites": {
         "title": "Seus favoritos",
         "view_schedule": "Ver agenda"

+ 51 - 4
src/pages/opportunities/components/OpportunityDetailsDialog.vue

@@ -137,6 +137,15 @@
         @click="goToProposalFlow"
       />
 
+      <q-btn
+  flat
+  rounded
+  no-caps
+  class="refuse-btn"
+  label="Recusar oportunidade"
+  @click="openRefuseDialog"
+/>
+
       <!-- ALERTA -->
       <div class="alert-box">
         <q-icon
@@ -159,10 +168,9 @@ import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
 import { useDialogPluginComponent } from 'quasar'
 import { userStore } from 'src/stores/user'
-import {
-  getOpportunityById,
-  proposalOpportunity
-} from 'src/api/opportunities'
+import {getOpportunityById,proposalOpportunity} from 'src/api/opportunities'
+import { Dialog } from 'quasar'
+import RefuseOpportunityDialog from './RefuseOpportunityDialog.vue'
 
 const props = defineProps({
   opportunityId: {
@@ -247,6 +255,25 @@ const goToProposalFlow = async () => {
   onDialogOK()
 }
 
+
+//Dialo de recusar opotunidade
+const openRefuseDialog = () => {
+
+  Dialog.create({
+    component: RefuseOpportunityDialog,
+
+    componentProps: {
+      opportunityId: details.value.schedule_id,
+      providerId: user.user.provider.id
+    }
+  }).onOk(() => {
+
+   window.location.reload()
+   
+  })
+}
+
+
 onMounted(async () => {
   try {
     const response = await getOpportunityById(
@@ -456,4 +483,24 @@ onMounted(async () => {
   align-items: center;
   justify-content: center;
 }
+
+.refuse-btn {
+  width: 100%;
+
+  height: 52px;
+
+  border-radius: 28px;
+
+  margin-top: 12px;
+
+  border: 1px solid #ff4d67;
+
+  color: #ff4d67;
+
+  font-weight: 600;
+
+  font-size: 15px;
+
+  background: white;
+}
 </style>

+ 156 - 0
src/pages/opportunities/components/RefuseOpportunityDialog.vue

@@ -0,0 +1,156 @@
+<template>
+  <q-dialog ref="dialogRef">
+
+    <q-card class="dialog-card">
+
+      <div class="dialog-title">
+        {{ t('provider.dashboard.refuse_opportunity_dialog.title') }}
+      </div>
+
+      <div class="dialog-description">
+        {{ t('provider.dashboard.refuse_opportunity_dialog.description') }}
+      </div>
+
+      <div class="buttons">
+
+        <q-btn
+          flat
+          no-caps
+          :label="t('provider.dashboard.refuse_opportunity_dialog.btn_cancel')"
+          class="cancel-btn"
+          @click="onDialogCancel"
+        />
+
+        <q-btn
+          unelevated
+          no-caps
+          :label="t('provider.dashboard.refuse_opportunity_dialog.btn_confirm')"
+          class="refuse-btn"
+          @click="refuseOpportunityHandler"
+        />
+
+      </div>
+
+    </q-card>
+
+  </q-dialog>
+</template>
+
+<script setup>
+import { useDialogPluginComponent } from 'quasar'
+import { useI18n } from 'vue-i18n'
+
+import { refuseOpportunity } from 'src/api/opportunities'
+
+const { t } = useI18n()
+
+const props = defineProps({
+  opportunityId: {
+    type: Number,
+    required: true
+  },
+
+  providerId: {
+    type: Number,
+    required: true
+  }
+})
+
+defineEmits([
+  ...useDialogPluginComponent.emits
+])
+
+const {
+  dialogRef,
+  onDialogOK,
+  onDialogCancel
+} = useDialogPluginComponent()
+
+const refuseOpportunityHandler = async () => {
+  try {
+
+    await refuseOpportunity(
+      props.opportunityId,
+      props.providerId
+    )
+
+    onDialogOK()
+
+  } catch (error) {
+    console.error(error)
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.dialog-card {
+  width: 90%;
+  max-width: 360px;
+
+  border-radius: 28px;
+
+  padding: 28px 22px;
+
+  background: white;
+}
+
+.dialog-title {
+  font-size: 20px;
+  font-weight: 700;
+
+  text-align: center;
+
+  color: #4b4b4b;
+}
+
+.dialog-description {
+  margin-top: 14px;
+
+  font-size: 14px;
+  line-height: 1.6;
+
+  text-align: center;
+
+  color: #7a7a7a;
+}
+
+.buttons {
+  display: flex;
+
+  gap: 14px;
+
+  margin-top: 30px;
+}
+
+.cancel-btn,
+.refuse-btn {
+  flex: 1;
+
+  height: 50px;
+
+  border-radius: 18px;
+
+  font-size: 14px;
+  font-weight: 700;
+}
+
+.cancel-btn {
+  background: #f4f4f5;
+
+  color: #666;
+
+  border: 1px solid #ececec;
+}
+
+.refuse-btn {
+  background: linear-gradient(
+    90deg,
+    #7c5cff,
+    #9f7aea
+  );
+
+  color: white;
+
+  box-shadow: 0 6px 18px rgba(124, 92, 255, 0.25);
+}
+</style>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است