Quellcode durchsuchen

Merge branch 'feature/diariaapp-kay-agendamentos-sob-medida-apps' of Softpar/sfp_front_vue_diarista_prestador into development

zntt vor 1 Tag
Ursprung
Commit
dd8cb77127

+ 36 - 3
src/api/opportunities.js

@@ -1,7 +1,40 @@
 import api from 'src/api'
 
 export const getProviderOpportunities = async (providerId) => {
-  const { data } = await api.get(`/custom-schedule-available?provider_id=${providerId}`)
+  try {
+    const { data } = await api.get(
+      `/custom-schedule-available`,
+      {
+        params: {
+          provider_id: providerId
+        }
+      }
+    )
 
-  return data.payload
-}
+    return data?.payload || []
+  } catch (error) {
+    console.error('[API] getProviderOpportunities error:', error)
+    return []
+  }
+}
+
+export const getOpportunityById = async (id) => {
+  try {
+    const { data } = await api.get(`/custom-schedule/${id}`)
+    return data?.payload || null
+  } catch (error) {
+    console.error('[API] getOpportunityById error:', error)
+    return null
+  }
+}
+
+
+export const proposalOpportunity = async (scheduleId, providerId) => {
+  try {
+    const { data } = await api.post(`/custom-schedule/${scheduleId}/propose`, { provider_id: providerId })
+    return data.payload || null
+  } catch (error) {
+    console.error('[API] proposalOpportunity error:', error)
+    return null
+  }
+} 

+ 37 - 1
src/helpers/utils.js

@@ -225,6 +225,40 @@ const formatAddress = (address) => {
   return parts.join(', ');
 };
 
+const calculateDailyPrices = (dailyPrice8h) => {
+  if (!dailyPrice8h || dailyPrice8h <= 0) {
+    return {
+      daily_price_8h: null,
+      daily_price_6h: null,
+      daily_price_4h: null,
+      daily_price_2h: null,
+    };
+  }
+
+  return {
+    daily_price_8h: dailyPrice8h, 
+    daily_price_6h: dailyPrice8h * 0.85,
+    daily_price_4h: dailyPrice8h * 0.55,
+    daily_price_2h: dailyPrice8h * 0.30,
+  };
+};
+
+const chooseprice = (periodType, daily_price_8h) => {
+  let alldaily_prices = calculateDailyPrices(daily_price_8h);
+  switch (periodType) {
+    case "8":
+      return daily_price_8h
+    case "6":
+      return alldaily_prices.daily_price_6h
+    case "4":
+      return alldaily_prices.daily_price_4h
+    case "2":
+      return alldaily_prices.daily_price_2h
+    default:
+      return 0
+  }
+}
+
 export {
   formatDateDMYtoYMD,
   formatDateYMDtoDMY,
@@ -238,5 +272,7 @@ export {
   validaDataHora,
   formatQuantity,
   formatCurrency,
-  formatAddress
+  formatAddress,
+  calculateDailyPrices,
+  chooseprice,
 };

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

@@ -281,6 +281,23 @@
         "address_not_found": "Address not available",
         "currency": "$ {value}"
       },
+      "opportunity_details": {
+        "title": "Service details",
+        "client_default": "Client",
+        "price_label": "Full day (up to 8h)",
+        "distance_text": "It is {distance} away from your registered address.",
+        "distance_default": "0 km",
+        "sob_medida": "Request made",
+        "sob_medida_highlight": "custom",
+        "para": "for",
+        "info_title": "Service information",
+        "description_not_found": "No description provided",
+        "address_not_found": "Address not provided",
+        "hour_not_found": "Time not provided",
+        "button_accept": "accept job",
+        "alert_text": "If your request is accepted by the client, you will receive a notification confirming the booking.",
+        "offers_meal": "On-site meal"
+      },
       "favorites": {
         "title": "Your favorites",
         "view_schedule": "View schedule"

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

@@ -279,6 +279,23 @@
         "address_not_found": "Dirección no informada",
         "currency": "R$ {value}"
       },
+      "opportunity_details": {
+        "title": "Detalles del servicio",
+        "client_default": "Cliente",
+        "price_label": "Jornada completa (hasta 8h)",
+        "distance_text": "Está a {distance} de distancia de su dirección registrada.",
+        "distance_default": "0 km",
+        "sob_medida": "Solicitud hecha",
+        "sob_medida_highlight": "a medida",
+        "para": "para",
+        "info_title": "Información del servicio",
+        "description_not_found": "Sin descripción proporcionada",
+        "address_not_found": "Dirección no informada",
+        "hour_not_found": "Horario no informado",
+        "button_accept": "quiero atender",
+        "alert_text": "Si el cliente acepta tu solicitud, recibirás una notificación confirmando el servicio.",
+        "offers_meal": "Comida en el lugar"
+      },
       "favorites": {
         "title": "Tus favoritos",
         "view_schedule": "Ver agenda"

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

@@ -293,6 +293,23 @@
         "address_not_found": "Endereço não informado",
         "currency": "R$ {value}"
       },
+      "opportunity_details": {
+        "title": "Detalhes do serviço",
+        "client_default": "Cliente",
+        "price_label": "Integral (até 8h)",
+        "distance_text": "Há {distance} de distância do seu endereço cadastrado.",
+        "distance_default": "0 km",
+        "sob_medida": "Pedido feito",
+        "sob_medida_highlight": "sob medida",
+        "para": "para",
+        "info_title": "Informações do serviço",
+        "description_not_found": "Sem descrição informada",
+        "address_not_found": "Endereço não informado",
+        "hour_not_found": "Horário não informado",
+        "button_accept": "quero atender",
+        "alert_text": "Se seu pedido for aceito pelo cliente você receberá um aviso confirmando o agendamento.",
+        "offers_meal": "Refeição no local"
+      },
       "favorites": {
         "title": "Seus favoritos",
         "view_schedule": "Ver agenda"

+ 48 - 71
src/pages/opportunities/OpportunitiesPage.vue

@@ -1,14 +1,7 @@
 <template>
   <q-page class="opportunities-page">
     <div class="page-header">
-      <q-btn
-        flat
-        round
-        dense
-        icon="chevron_left"
-        class="back-btn"
-        @click="router.back()"
-      />
+      <q-btn flat round dense icon="chevron_left" class="back-btn" @click="router.back()" />
       <div class="page-title">
         {{ $t('provider.dashboard.opportunities.title') }}
       </div>
@@ -25,68 +18,60 @@
       <q-spinner-dots color="secondary" size="32px" />
     </div>
 
-    <div
-      v-else-if="!opportunities.length"
-      class="text-center q-pa-md text-grey"
-    >
+    <div v-else-if="!opportunities.length" class="text-center q-pa-md text-grey">
       {{ $t('provider.dashboard.opportunities.empty') }}
     </div>
 
     <div v-else class="opportunity-list">
-      <q-card
-        v-for="item in opportunities"
-        :key="item.id"
-        flat
-        class="opportunity-card"
-      >
+      <q-card v-for="item in opportunities" :key="item.id" flat class="opportunity-card">
         <div class="avatar-column">
           <img :src="item.avatar" class="client-avatar" />
           <div class="service-type">
-            {{ item.serviceType }}
+            {{ item.custom_schedule?.service_type.description }}
           </div>
         </div>
 
         <div class="center-content">
           <div class="client-name-row">
-            <span class="client-name">{{ item.clientName }}</span>
+            <span class="client-name">{{ item.client?.user.name }}</span>
 
+            <!-- campo de avaliação -->
             <span class="rating">
               <q-icon name="star" size="11px" />
-              {{ item.rating }}
+              {{ item.client?.average_rating }}
             </span>
           </div>
 
           <div class="service-date">
-            {{ item.date }}
+            {{ formatDate(item.date) }}
           </div>
 
           <div class="service-hour">
-            {{ item.hour }}
+           {{ `Das ${formatHour(item.start_time)} às ${formatHour(item.end_time)}` }}
           </div>
         </div>
 
         <div class="right-content">
           <div class="price">
-            {{ $t('provider.dashboard.opportunities.currency', { value: item.price }) }}
+            {{ $t('provider.dashboard.opportunities.currency', { value: chooseprice(item.period_type, user.user.provider.daily_price_8h) }) }}
           </div>
 
           <div class="service-address">
-            {{ item.address }}
+            {{ item.custom_schedule?.address_type }}
           </div>
 
+          <div class="district">
+             {{ item.address?.district }}
+          </div>
+
+
           <div class="distance">
             {{ $t('provider.dashboard.opportunities.distance_km', { distance: item.distance }) }}
           </div>
 
-          <q-btn
-            unelevated
-            rounded
-            no-caps
-            color="secondary"
-            :label="$t('provider.dashboard.opportunities.details')"
-            class="details-btn"
-            @click="goToOpportunityDetails(item)"
-          />
+          
+
+          <q-btn unelevated rounded no-caps color="secondary" :label="$t('provider.dashboard.opportunities.details')" class="details-btn" @click="goToOpportunityDetails(item)" />
         </div>
       </q-card>
     </div>
@@ -95,61 +80,47 @@
 <script setup>
 import { ref, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
-import { useI18n } from 'vue-i18n'
-
+import { chooseprice } from 'src/helpers/utils'
 import { getProviderOpportunities } from 'src/api/opportunities'
 import { userStore } from 'src/stores/user'
 
 const router = useRouter()
-const { t } = useI18n()
 const user = userStore()
 
 const opportunities = ref([])
 const loading = ref(false)
 
-const formatHour = (time) =>
-  time ? time.slice(0, 5).replace(':', 'h') : ''
-
-
-const normalizeOpportunity = (item) => ({
-  id: item.id,
 
-  avatar: item.client?.user?.photo || '/icons/avatar.svg',
-
-  clientName:
-    item.client?.user?.name ||
-    t('provider.dashboard.opportunities.client_default'),
-
-  rating: item.client?.average_rating || 5.0,
-
-  date: new Date(
-    item.custom_schedule?.created_at || item.created_at
-  ).toLocaleDateString(),
+const goToOpportunityDetails = (item) => {
 
-  hour: `${t('common.from')} ${formatHour(item.start_time)} ${t('common.to')} ${formatHour(item.end_time)}`,
+  const id = item.custom_schedule?.id || item.id
 
-  address:
-    item.address?.address ||
-    t('provider.dashboard.opportunities.address_not_found'),
+  router.push({
+    name: 'OpportunityDetailsPage',
+    params: { id },
+    state: { opportunity: item }
+  })
+}
 
-  serviceType:
-    item.custom_schedule?.service_type?.descritpion ||
-    t('provider.dashboard.opportunities.client_default'),
 
-  price: Number(
-    item.custom_schedule?.max_price || 0
-  ).toFixed(2),
 
-  distance: 0
-})
+// formatando a data
+const formatDate = (date) => {
+  if (!date) return ''
 
-const goToOpportunityDetails = (item) => {
-  router.push({
-    name: 'OpportunityDetailsPage',
-    params: { id: item.id }
+  return new Date(date).toLocaleDateString('pt-BR', {
+    weekday: 'long',
+    day: '2-digit',
+    month: '2-digit'
   })
 }
 
+// formatando hora para exibir só HH:mm
+const formatHour = (time) => {
+  if (!time) return ''
+  return time.slice(0, 5) 
+}
+
 const loadOpportunities = async () => {
   loading.value = true
 
@@ -158,7 +129,7 @@ const loadOpportunities = async () => {
       user.user.provider.id
     )
 
-    opportunities.value = (response || []).map(normalizeOpportunity)
+    opportunities.value = (response || [])
   } catch (error) {
     console.error('Erro ao buscar oportunidades:', error)
     opportunities.value = []
@@ -267,6 +238,12 @@ onMounted(loadOpportunities)
   color: #2d2d2d;
 }
 
+.district {
+  margin-top: 4px;
+  font-size: 11px;
+  color: #666;
+}
+
 .rating {
   display: flex;
   align-items: center;

+ 211 - 153
src/pages/opportunities/components/OpportunityDetailsPage.vue

@@ -1,169 +1,183 @@
 <template>
-  <q-page class="details-page">
+  <q-page v-if="details" class="details-page">
+
     <!-- HEADER -->
     <div class="page-header">
-      <q-btn
-        flat
-        round
-        dense
-        icon="chevron_left"
-        class="back-btn"
-        @click="router.back()"
-      />
-      <div class="page-title">{{ details.title }}</div>
+      <q-btn flat round dense icon="chevron_left" class="back-btn" @click="router.back()" />
+      <div class="page-title">
+        {{ $t('provider.dashboard.opportunity_details.title') }}
+      </div>
     </div>
 
     <!-- CLIENTE -->
     <div class="client-section">
-      <img :src="AvatarMock" class="client-avatar" />
-      <div class="client-name">{{ details.clientName }}</div>
-      <div class="client-price">{{ details.price }}</div>
+      <img :src="details.avatar" class="client-avatar" />
+
+      <div class="client-name">
+        {{ details.schedule?.client_name }}
+        <span class="rating"> {{ details.schedule?.rating }}</span>
+      </div>
+
+      <div class="client-price">{{ $t('provider.dashboard.opportunities.currency', { value: chooseprice(details.schedule?.period_type) }) }}</div>
+
+      <div class="date">  {{ formatDate(details.schedule?.date) }}</div>
+      <div class="hour">{{ formatHour(details.schedule?.start_time ) }} {{ formatHour(details.schedule?.end_time ) }}</div>
     </div>
 
-    <!-- INFOS -->
-    <div class="details-info">
-      <div>{{ details.date }}</div>
-      <div>{{ details.hour }}</div>
-      <div>{{ details.address }}</div>
-      <div>{{ details.distance }}</div>
+    <!-- ENDEREÇO -->
+    <div class="address">
+      <q-icon name="place" size="16px" />
+      {{ details.schedule?.address?.district }}
     </div>
 
-    <!-- TAGS -->
-    <div class="tags-row">
-      <q-chip dense color="grey-3">
-        {{ details.tags[0] }}
-      </q-chip>
+    <div class="distance">
+      {{ $t('provider.dashboard.opportunity_details.distance_text', { distance: details.schedule?.distance }) }}
+    </div>
 
-      <q-chip dense color="grey-3">
-        {{ details.tags[1] }}
+    <!-- TAGS -->
+    <div v-if="details.tags?.length" class="tags-row">
+      <q-chip v-for="(tag, index) in details.tags" :key="index" outline class="chip">
+        {{ tag }}
       </q-chip>
     </div>
 
+    <!-- INFO -->
+    <div class="service-type">
+  {{ $t('provider.dashboard.opportunity_details.sob_medida') }}
+  <span>
+    {{ $t('provider.dashboard.opportunity_details.sob_medida_highlight') }}
+  </span>
+  <br />
+  {{ $t('provider.dashboard.opportunity_details.para') }}
+  <strong class="highlight-service">
+    {{ details.service_type_name }}
+  </strong>
+</div>
+    
+
+
+    <div class="address-type">
+  <span class="chip-type">
+    {{ (details.address_type) }}
+  </span> 
+  <span v-if="details.offers_meal" class="chip-type">
+    {{ $t('provider.dashboard.opportunity_details.offers_meal') }}
+  </span>    
+</div>
+    
+
+
+     <div v-if="details.description" class="service-type gradient-diarista">
+  {{ $t('provider.dashboard.opportunity_details.info_title') }}
+  </div>
     <!-- DESCRIÇÃO -->
     <div class="description-box">
       {{ details.description }}
     </div>
 
     <!-- BOTÃO -->
-    <q-btn
-      unelevated
-      rounded
-      no-caps
-      color="secondary"
-      :label="details.buttonLabel"
-      class="full-width q-mt-md"
-      @click="goToProposalFlow"
-    />
+    <q-btn unelevated rounded no-caps class="accept-btn" :label="$t('provider.dashboard.opportunity_details.button_accept')" @click="goToProposalFlow" />
 
     <!-- ALERTA -->
-    <q-card flat class="bottom-alert">
-      {{ details.alertText }}
-    </q-card>
+    <div class="alert-box">
+      <q-icon name="warning" size="18px" class="alert-icon" />
+      <span class="alert-text">
+        {{ $t('provider.dashboard.opportunity_details.alert_text') }}
+      </span>
+    </div>
+
   </q-page>
 </template>
 
 <script setup>
 import { ref, onMounted } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
+import { userStore } from 'src/stores/user'
+import { getOpportunityById, proposalOpportunity } from 'src/api/opportunities'
 
-import AvatarMock from 'src/assets/foto_diarista_login.svg'
-import { getOpportunityById } from 'src/api/opportunities'
 
-// router
 const router = useRouter()
 const route = useRoute()
 
-// state
+const user = userStore()
 const details = ref(null)
-const loading = ref(false)
-
-// params
-const opportunityId = route.params.id
-
-// helpers
-const formatHour = (time) =>
-  time ? time.slice(0, 5).replace(':', 'h') : ''
-
-// normalize (PADRÃO EMPRESA)
-const normalizeDetails = (item) => ({
-  title: 'Detalhes do serviço',
-
-  avatar: item.client?.user?.photo || AvatarMock,
-
-  clientName:
-    item.client?.user?.name || 'Cliente',
-
-  price: `R$${Number(
-    item.custom_schedule?.max_price || 0
-  ).toFixed(2)}`,
-
-  date: new Date(
-    item.custom_schedule?.created_at || item.created_at
-  ).toLocaleDateString('pt-BR'),
-
-  hour: `Das ${formatHour(item.start_time)} às ${formatHour(item.end_time)}`,
-
-  address:
-    item.address?.address || 'Endereço não informado',
+const loading = ref(true)
+
+const chooseprice = (periodType) => {
+  switch (periodType) {
+    case "8":
+      return user.user.provider_daily_price_8h
+    case "6":
+      return user.user.provider_daily_price_6h
+    case "4":
+      return user.user.provider_daily_price_4h
+    case "2":
+      return user.user.provider_daily_price_2h
+    default:
+      return 0
+  }
+}
 
-  distance: '0 km',
+// formatando a data
+const formatDate = (date) => {
+  if (!date) return ''
 
-  tags: [
-    item.custom_schedule?.service_type?.description,
-    item.custom_schedule?.offers_meal
-      ? 'Refeição no local'
-      : null
-  ].filter(Boolean),
+  const [day, month, year] = date.split('/')
 
-  description:
-    item.custom_schedule?.description || '',
+  const parsedDate = new Date(`${year}-${month}-${day}`)
 
-  buttonLabel: 'Quero atender',
+  const formatted = parsedDate.toLocaleDateString('pt-BR', {
+    weekday: 'long',
+    day: '2-digit',
+    month: '2-digit'
+  })
 
-  alertText:
-    'Se seu pedido for aceito pelo cliente você receberá um aviso confirmando o agendamento e aparecerá nos seus próximos serviços.'
-})
+  return formatted.charAt(0).toUpperCase() + formatted.slice(1)
+}
 
-// load
-const loadDetails = async () => {
-  loading.value = true
+// formatando hora para exibir só HH:mm
+const formatHour = (time) => {
+  if (!time) return ''
+  return time.slice(0, 5) 
+}
 
+onMounted(async () => {
   try {
-    const response = await getOpportunityById(opportunityId)
-
-    console.log('DETAILS RESPONSE:', response)
-
-    details.value = normalizeDetails(response)
+    const id = route.params.id
+    const response = await getOpportunityById(id)
+
+    if (response) {
+      details.value = response
+    } else {
+      console.warn('Nenhum dado retornado')
+    }
   } catch (error) {
     console.error('Erro ao carregar detalhes:', error)
-    details.value = null
   } finally {
     loading.value = false
   }
-}
-
-// actions
-const goToProposalFlow = () => {
-  console.log('Ir para proposta', details.value)
-}
+})
 
-// lifecycle
-onMounted(loadDetails)
+const goToProposalFlow = async () => {
 
+  await proposalOpportunity(details.value.schedule_id, user.user.provider.id)
+  router.push({ name: 'DashboardPage' })
+}
 </script>
 
 <style scoped lang="scss">
 .details-page {
   padding: 16px;
-  background: #f7f7fb;
+  background: #f4f5f7;
   min-height: 100vh;
 }
 
+/* HEADER */
 .page-header {
   display: flex;
   justify-content: center;
   position: relative;
-  margin-bottom: 24px;
+  margin-bottom: 16px;
 }
 
 .back-btn {
@@ -173,108 +187,152 @@ onMounted(loadDetails)
 }
 
 .page-title {
-  font-size: 16px;
-  font-weight: 700;
+  font-size: 15px;
+  font-weight: 600;
   color: #7c5cff;
 }
 
+/* CLIENTE */
 .client-section {
   text-align: center;
-  margin-top: 8px;
 }
 
 .client-avatar {
-  width: 84px;
-  height: 84px;
+  width: 88px;
+  height: 88px;
   border-radius: 50%;
   object-fit: cover;
 }
 
 .client-name {
-  margin-top: 8px;
-  font-size: 18px;
-  font-weight: 500;
+  margin-top: 6px;
+  font-size: 14px;
   color: #666;
 }
 
+.rating {
+  color: #ffb800;
+  font-size: 12px;
+  margin-left: 4px;
+}
+
 .client-price {
   margin-top: 8px;
-  color: #7c5cff;
-  font-size: 32px;
+  font-size: 24px;
   font-weight: 700;
+  color: #7c5cff;
 }
 
-.details-info {
-  margin-top: 12px;
-  text-align: center;
-  font-size: 13px;
-  line-height: 1.6;
-  color: #666;
+/* DATA */
+.date {
+  margin-top: 6px;
+  font-size: 12px;
+  font-weight: 600;
+  color: #555;
 }
 
-.distance-info {
-  margin-top: 12px;
-  text-align: center;
+.hour {
   font-size: 12px;
-  color: #999;
+  color: #777;
 }
 
-.service-highlight {
-  margin-top: 16px;
-  text-align: center;
-  font-size: 13px;
-  color: #666;
+.highlight-service{
+  color: #7c5cff;
+  font-weight: 600;
 }
 
-.highlight-text {
+/* ENDEREÇO */
+.address {
+  margin-top: 12px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  gap: 4px;
   color: #7c5cff;
-  font-weight: 700;
+  font-size: 13px;
+  font-weight: 600;
+}
+
+.distance {
+  text-align: center;
+  font-size: 11px;
+  color: #999;
+  margin-top: 4px;
 }
 
-.tags-row {
+/* ADDRESS TYPE (CHIPS) */
+.address-type {
   display: flex;
   justify-content: center;
-  gap: 8px;
-  margin: 18px 0;
+  gap: 10px;
+  margin-top: 10px;
 }
 
-.tags-row .q-chip {
-  border: 1px solid #7c5cff;
+.chip-type {
+  border: 1.5px solid #7c5cff;
   color: #7c5cff;
+  padding: 6px 14px;
+  border-radius: 999px;
+  font-size: 12px;
+  font-weight: 600;
   background: white;
+  text-transform: lowercase;
+}
+
+/* TEXTO SOB MEDIDA */
+.service-type {
+  text-align: center;
   font-size: 12px;
+  margin-top: 12px;
+  color: #666;
 }
 
+.service-type span {
+  color: #7c5cff;
+  font-weight: 600;
+}
+
+/* INFO */
 .info-title {
   text-align: center;
-  color: #7c5cff;
-  font-size: 18px;
   font-weight: 700;
-  margin-bottom: 12px;
+  color: #7c5cff;
+  margin-top: 14px;
+  font-size: 13px;
 }
 
+/* DESCRIÇÃO */
 .description-box {
   text-align: center;
-  font-size: 13px;
-  line-height: 1.6;
+  font-size: 12px;
   color: #666;
+  margin: 10px 0 20px;
+  line-height: 1.4;
 }
 
-.full-width {
-  margin-top: 20px;
+/* BOTÃO */
+.accept-btn {
+  width: 100%;
   height: 48px;
-  font-size: 16px;
-  background: #8f6dfc !important;
+  border-radius: 25px;
+  background: linear-gradient(90deg, #7c5cff, #9f7aea);
+  color: white;
+  font-weight: 600;
+  font-size: 14px;
 }
 
-.bottom-alert {
-  margin-top: 18px;
-  padding: 14px;
-  border-radius: 14px;
-  background: #dfeeff;
-  font-size: 12px;
-  line-height: 1.5;
-  color: #5c6b8a;
+/* ALERTA */
+.alert-box {
+  margin-top: 12px;
+  background: #e9f0ff;
+  padding: 10px;
+  border-radius: 12px;
+  font-size: 11px;
   text-align: center;
+  color: #5c6b8a;
+  display: flex;
+  gap: 6px;
+  align-items: center;
+  justify-content: center;
 }
 </style>