fix: bugs
Deploy banu-front / deploy (push) Successful in 1m28s

This commit is contained in:
sajjadtalkhabi
2026-07-09 13:25:59 +03:30
parent 38a396ba21
commit da34813b1d
45 changed files with 2486 additions and 1048 deletions
@@ -1,174 +0,0 @@
<template>
<div class="mdis" @click="emit('show-details', dispatch)">
<div class="mdis__heading">
<span class="mdis__code">{{ dispatch.code }}</span>
<div class="mdis__title-wrap">
<p class="mdis__title">{{ dispatch.title }}</p>
<div class="mdis__location">
<SvgIcon name="paper-plane-right" :size="11" color="#989898" />
<span>{{ dispatch.location }}</span>
</div>
</div>
</div>
<div class="mdis__meta">
<div class="mdis__pill">
<span class="mdis__pill-label">تاریخ اعزام :</span>
<span class="mdis__pill-value">{{ dispatch.dispatchDate }}</span>
</div>
</div>
<div class="mdis__actions" @click.stop>
<CircleButton bg-color="rgba(0, 112, 116, 0.08)" size="2rem">
<template #icon>
<SvgIcon name="pencil" :size="14" color="#007074" />
</template>
</CircleButton>
<BaseButton
text="جزئیات درخواست"
custom-class="mdis__btn"
@click="emit('show-details', dispatch)"
>
<template #appendIcon>
<SvgIcon name="arrow-left" :size="16" color="#fff" />
</template>
</BaseButton>
</div>
</div>
</template>
<script setup>
import BaseButton from '@/components/BaseButton.vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import CircleButton from '@/components/CircleButton.vue'
defineProps({
dispatch: { type: Object, required: true },
})
const emit = defineEmits(['show-details'])
</script>
<style lang="scss" scoped>
.mdis {
position: relative;
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem 1.25rem;
background: rgba(255, 255, 255, 58%);
box-shadow: 0 6.75px 19.425px rgba(0, 0, 0, 4%);
border-radius: 0.75rem;
margin-bottom: 0.625rem;
cursor: pointer;
transition: background 0.15s ease;
&:hover {
background: rgba(255, 255, 255, 75%);
}
@media (min-width: 1024px) {
flex-direction: row;
align-items: center;
gap: 1.5rem;
padding: 1rem 2rem;
}
&__heading {
display: flex;
align-items: center;
gap: 0.625rem;
@media (min-width: 1024px) {
min-width: 8rem;
}
}
&__title-wrap {
display: flex;
flex-direction: column;
align-items: flex-end;
text-align: end;
gap: 0.35rem;
}
&__title {
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 1rem;
color: #4b4b4b;
margin: 0;
}
&__location {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 0.7rem;
color: #989898;
}
&__code {
font-family: var(--font-family-en);
font-weight: 800;
font-size: 2.75rem;
line-height: 1;
color: #f0f0f0;
}
&__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
@media (min-width: 1024px) {
flex: 1;
justify-content: center;
}
}
&__pill {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.3rem 0.875rem;
border-radius: 0.75rem;
background: rgba(107, 107, 107, 4%);
font-family: var(--font-family-fa);
white-space: nowrap;
}
&__pill-label {
font-weight: 400;
font-size: 0.65rem;
color: #535353;
}
&__pill-value {
font-family: var(--font-family-en);
font-weight: 400;
font-size: 0.75rem;
color: #5d5d5d;
}
&__actions {
display: flex;
justify-content: center;
gap: 6px;
@media (min-width: 1024px) {
justify-content: flex-start;
}
}
&__btn {
min-width: 8rem;
height: 2rem;
padding: 0 1.25rem;
}
}
</style>
@@ -1,177 +0,0 @@
<template>
<div class="mnar" @click="emit('show-details', narrative)">
<div class="mnar__heading">
<span class="mnar__code">{{ narrative.code }}</span>
<div class="mnar__title-wrap">
<p class="mnar__title">{{ narrative.title }}</p>
<p class="mnar__subtitle">تاریخ ثبت: {{ narrative.submittedAt }}</p>
</div>
</div>
<div class="mnar__meta">
<div class="mnar__pill" :class="`mnar__pill--${narrative.kindTone}`">
<span class="mnar__pill-label">روایت :</span>
<span class="mnar__pill-value">{{ narrative.kindLabel }}</span>
</div>
</div>
<div class="mnar__actions">
<CircleButton bg-color="rgba(0, 112, 116, 0.08)" size="2rem">
<template #icon>
<SvgIcon name="check" :size="14" color="#007074" />
</template>
</CircleButton>
<CircleButton bg-color="rgba(243, 102, 117, 0.06)" size="2rem">
<template #icon>
<SvgIcon name="close" :size="14" color="var(--color-error)" />
</template>
</CircleButton>
<CircleButton bg-color="rgba(107, 107, 107, 0.04)" size="2rem">
<template #icon>
<SvgIcon name="pencil" :size="14" color="#535353" />
</template>
</CircleButton>
</div>
</div>
</template>
<script setup>
import SvgIcon from '@/components/icons/SvgIcon.vue'
import CircleButton from '@/components/CircleButton.vue'
defineProps({
narrative: { type: Object, required: true },
})
const emit = defineEmits(['show-details'])
</script>
<style lang="scss" scoped>
.mnar {
position: relative;
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem 1.25rem;
background: rgba(255, 255, 255, 58%);
box-shadow: 0 6.75px 19.425px rgba(0, 0, 0, 4%);
border-radius: 0.75rem;
margin-bottom: 0.625rem;
@media (min-width: 1024px) {
flex-direction: row;
align-items: center;
gap: 1.5rem;
padding: 1rem 2rem;
}
&__heading {
display: flex;
align-items: center;
gap: 0.625rem;
@media (min-width: 1024px) {
min-width: 9rem;
}
}
&__title-wrap {
display: flex;
flex-direction: column;
align-items: flex-end;
text-align: end;
gap: 0.2rem;
}
&__title {
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 1rem;
color: #4b4b4b;
margin: 0;
}
&__subtitle {
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 0.55rem;
color: #4b4b4b;
margin: 0;
}
&__code {
font-family: var(--font-family-en);
font-weight: 800;
font-size: 2.75rem;
line-height: 1;
color: #f0f0f0;
}
&__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
@media (min-width: 1024px) {
flex: 1;
justify-content: center;
}
}
&__pill {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.3rem 0.875rem;
border-radius: 0.75rem;
font-family: var(--font-family-fa);
white-space: nowrap;
}
&__pill-label {
font-weight: 400;
font-size: 0.65rem;
}
&__pill-value {
font-weight: 400;
font-size: 0.75rem;
}
&__pill--pink {
background: rgba(243, 102, 117, 7%);
.mnar__pill-label,
.mnar__pill-value {
color: #f55d6d;
}
}
&__pill--gold {
background: rgba(116, 102, 0, 4%);
.mnar__pill-label,
.mnar__pill-value {
color: #b6842d;
}
}
&__actions {
display: flex;
justify-content: center;
gap: 6px;
@media (min-width: 1024px) {
justify-content: flex-start;
}
}
&__btn {
min-width: 7rem;
height: 2rem;
padding: 0 1.25rem;
}
}
</style>
@@ -1,257 +0,0 @@
<template>
<div class="mreq" @click="emit('show-details', request)">
<div class="mreq__heading">
<span class="mreq__code">{{ request.code }}</span>
<div class="mreq__title-wrap">
<p class="mreq__title">{{ request.title }}</p>
<p class="mreq__subtitle">شماره درخواست : {{ request.requestNumber }}</p>
</div>
</div>
<div class="mreq__meta">
<div class="mreq__pill mreq__pill--date">
<span class="mreq__pill-label">تاریخ درخواست :</span>
<span class="mreq__pill-value">{{ request.requestDate }}</span>
</div>
<div class="mreq__pill mreq__pill--name">
<span class="mreq__pill-label">نام درخواست کننده :</span>
<span class="mreq__pill-value">{{ request.requesterName }}</span>
</div>
<div class="mreq__pill mreq__pill--status" :class="`mreq__pill--${tone}`">
<span class="mreq__dot" />
<span class="mreq__pill-value mreq__pill-value--status">{{ request.statusLabel }}</span>
</div>
</div>
<div class="mreq__actions" @click.stop>
<CircleButton bg-color="rgba(0, 112, 116, 0.08)" size="2rem">
<template #icon>
<SvgIcon name="check" :size="14" color="#007074" />
</template>
</CircleButton>
<CircleButton bg-color="rgba(243, 102, 117, 0.06)" size="2rem">
<template #icon>
<SvgIcon name="close" :size="14" color="var(--color-error)" />
</template>
</CircleButton>
<BaseButton
text="جزئیات درخواست"
custom-class="mreq__btn"
@click="emit('show-details', request)"
>
<template #appendIcon>
<SvgIcon name="arrow-left" :size="16" color="#fff" />
</template>
</BaseButton>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import BaseButton from '@/components/BaseButton.vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import CircleButton from '@/components/CircleButton.vue'
const TONE_MAP = {
rejected: 'danger',
approved: 'success',
pending: 'warning',
cancelled: 'neutral',
}
const props = defineProps({
request: { type: Object, required: true },
})
const emit = defineEmits(['show-details'])
const tone = computed(() => TONE_MAP[props.request.status] || 'neutral')
</script>
<style lang="scss" scoped>
.mreq {
position: relative;
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem 1.25rem;
background: rgba(255, 255, 255, 58%);
box-shadow: 0 6.75px 19.425px rgba(0, 0, 0, 4%);
border-radius: 0.75rem;
margin-bottom: 0.625rem;
cursor: pointer;
transition: background 0.15s ease;
&:hover {
background: rgba(255, 255, 255, 75%);
}
@media (min-width: 1024px) {
flex-direction: row;
align-items: center;
gap: 1.5rem;
padding: 1rem 2rem;
}
&__heading {
display: flex;
align-items: center;
gap: 0.625rem;
@media (min-width: 1024px) {
min-width: 9rem;
}
}
&__title-wrap {
display: flex;
flex-direction: column;
align-items: flex-end;
text-align: end;
gap: 0.125rem;
}
&__title {
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 0.95rem;
color: #4b4b4b;
margin: 0;
}
&__subtitle {
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 0.7rem;
color: #4b4b4b;
margin: 0;
}
&__code {
font-family: var(--font-family-en);
font-weight: 800;
font-size: 2.75rem;
line-height: 1;
color: #f0f0f0;
}
&__meta {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 0.5rem;
@media (min-width: 1024px) {
flex: 1;
flex-wrap: nowrap;
}
}
&__pill {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.3rem 0.875rem;
border-radius: 0.75rem;
background: rgba(107, 107, 107, 4%);
font-family: var(--font-family-fa);
white-space: nowrap;
}
&__pill-label {
font-weight: 400;
font-size: 0.65rem;
color: #535353;
}
&__pill-value {
font-family: var(--font-family-en);
font-weight: 400;
font-size: 0.75rem;
color: #5d5d5d;
}
&__pill-value--status {
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 0.65rem;
}
&__dot {
width: 0.32rem;
height: 0.32rem;
border-radius: 9999px;
flex-shrink: 0;
}
&__pill--status {
padding: 0.3rem 0.75rem;
}
&__pill--danger {
background: rgba(243, 102, 117, 6%);
.mreq__pill-value {
color: #cc2831;
}
.mreq__dot {
background: #cc2831;
}
}
&__pill--success {
background: rgba(0, 112, 116, 6%);
.mreq__pill-value {
color: #007074;
}
.mreq__dot {
background: #007074;
}
}
&__pill--warning {
background: rgba(182, 132, 45, 7%);
.mreq__pill-value {
color: #b6842d;
}
.mreq__dot {
background: #b6842d;
}
}
&__pill--neutral {
background: rgba(107, 107, 107, 6%);
.mreq__pill-value {
color: #535353;
}
.mreq__dot {
background: #989898;
}
}
&__actions {
display: flex;
justify-content: center;
gap: 6px;
@media (min-width: 1024px) {
justify-content: flex-start;
}
}
&__btn {
min-width: 8rem;
height: 2rem;
padding: 0 1.25rem;
}
}
</style>
@@ -1,257 +0,0 @@
<template>
<div class="muc">
<div class="muc__profile">
<div class="muc__avatar">
<img v-if="avatar" :src="avatar" alt="" />
<SvgIcon v-else name="user" :size="40" color="#c2c2c2" />
</div>
<div class="muc__profile-info">
<div class="muc__level">
<span class="muc__level-label">سطح کاربر :</span>
<span class="muc__level-value">{{ level }}</span>
</div>
<p class="muc__name">{{ fullName }}</p>
<div class="muc__location">
<SvgIcon name="map-pin-simple-area" :size="12" />
<span>{{ city }}</span>
</div>
</div>
</div>
<div class="muc__stats">
<div class="muc__stat">
<span class="muc__stat-label">مدت عضویت</span>
<div class="muc__stat-value">
<span class="muc__stat-num">{{ membershipDays }}</span>
<span class="muc__stat-unit">روز</span>
</div>
</div>
<span class="muc__divider" />
<div class="muc__stat">
<span class="muc__stat-label">امتیاز کاربر</span>
<div class="muc__stat-value">
<span class="muc__stat-num">{{ rating }}</span>
</div>
</div>
<span class="muc__divider" />
<div class="muc__stat">
<span class="muc__stat-label">تعداد درخواستها</span>
<div class="muc__stat-value">
<span class="muc__stat-num">{{ requestsCount }}</span>
<span class="muc__stat-unit">درخواست</span>
</div>
</div>
</div>
<div class="muc__actions">
<CircleButton bg-color="rgba(0, 112, 116, 0.08)" size="2.5rem">
<template #icon>
<SvgIcon name="pencil" :size="20" color="#007074" />
</template>
</CircleButton>
<BaseButton text="تکمیل اطلاعات" custom-class="muc__btn" @click="emit('complete-info')">
<template #appendIcon>
<SvgIcon name="arrow-left" :size="16" color="#fff" />
</template>
</BaseButton>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import BaseButton from '@/components/BaseButton.vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import CircleButton from '@/components/CircleButton.vue'
const props = defineProps({
profile: { type: Object, default: () => ({}) },
})
const emit = defineEmits(['complete-info', 'settings'])
const fullName = computed(() => props.profile.fullName || '—')
const city = computed(() => props.profile.city || '—')
const level = computed(() => props.profile.level || '—')
const avatar = computed(() => props.profile.avatar || '')
const membershipDays = computed(() => props.profile.membershipDays ?? '—')
const rating = computed(() => props.profile.rating ?? '—')
const requestsCount = computed(() => props.profile.requestsCount ?? '—')
</script>
<style lang="scss" scoped>
.muc {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1.25rem;
background: #fff;
box-shadow: 0 6.75px 19.425px rgba(0, 0, 0, 4%);
border-radius: 0.75rem;
margin-bottom: 0.75rem;
@media (min-width: 1024px) {
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 1.25rem 2rem;
}
&__actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
&__btn {
min-width: 9rem;
height: 2.5rem;
padding: 0 1.25rem;
}
&__icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
border-radius: 9999px;
background: rgba(0, 112, 116, 4%);
cursor: pointer;
transition: background 0.15s ease;
&:hover {
background: rgba(0, 112, 116, 8%);
}
}
&__stats {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
@media (min-width: 1024px) {
gap: 1.25rem;
}
}
&__stat {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.125rem;
min-width: 3rem;
}
&__stat-label {
font-family: var(--font-family-fa);
font-weight: 500;
font-size: 0.7rem;
color: #a7a7a7;
white-space: nowrap;
}
&__stat-value {
display: inline-flex;
align-items: baseline;
gap: 0.25rem;
}
&__stat-num {
font-family: var(--font-family-en);
font-weight: 400;
font-size: 1.2rem;
color: #818181;
line-height: 1.4;
}
&__stat-unit {
font-family: var(--font-family-fa);
font-weight: 300;
font-size: 0.6rem;
color: #929292;
}
&__divider {
width: 1px;
height: 1.5rem;
background: #eee;
}
&__profile {
display: flex;
align-items: center;
gap: 0.875rem;
}
&__profile-info {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.375rem;
text-align: center;
}
&__name {
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 1.15rem;
color: #4b4b4b;
margin: 0;
}
&__location {
display: inline-flex;
align-items: center;
gap: 0.3rem;
font-family: var(--font-family-fa);
font-weight: 400;
font-size: 0.7rem;
color: #989898;
}
&__level {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.25rem 0.875rem;
border-radius: 0.75rem;
background: rgba(0, 112, 116, 4%);
font-family: var(--font-family-fa);
color: #007074;
}
&__level-label {
font-weight: 400;
font-size: 0.65rem;
}
&__level-value {
font-weight: 500;
font-size: 0.75rem;
}
&__avatar {
width: 5.625rem;
height: 5.625rem;
border-radius: 9999px;
background: #fff;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
}
</style>
@@ -1,72 +0,0 @@
<template>
<BasicModal
title="جزئیات اعزام"
title-en="Dispatch Details"
width="95%"
max-width="56rem"
min-width="auto"
>
<div class="dispatch-details">
<div class="dispatch-details__row dispatch-details__row--summary">
<LineInfoBlock
title="نام مأموریت/اعزام"
:desc="dispatch.missionName || dispatch.title || '—'"
/>
<LineInfoBlock title="کد اعزام" :numeric-desc="dispatch.code || ''" />
<LineInfoBlock title="تاریخ اعزام" :numeric-desc="dispatch.dispatchDate || ''" />
<LineInfoBlock title="وضعیت فعلی" :desc="dispatch.statusLabel || '—'" />
<LineInfoBlock title="نام درخواست کننده" :desc="dispatch.requesterName || '—'" />
</div>
<div class="dispatch-details__row">
<LineInfoBlock title="آدرس" :desc="dispatch.address || '—'" />
</div>
<div class="dispatch-details__row dispatch-details__row--body">
<LineInfoBlock title="نتیجه یا دستاورد مأموریت" :desc="dispatch.result || '—'" />
</div>
</div>
</BasicModal>
</template>
<script setup>
import { computed } from 'vue'
import useModal from '@/composables/useModal'
import BasicModal from '@/components/BasicModal.vue'
import LineInfoBlock from '@/components/blocks/LineInfoBlock.vue'
defineOptions({ name: 'DispatchDetailsModal' })
const { getModal } = useModal()
const dispatch = computed(() => getModal('DispatchDetailsModal')?.data ?? {})
</script>
<style lang="scss" scoped>
.dispatch-details {
display: flex;
flex-direction: column;
gap: 0.5rem;
font-family: var(--font-family-fa);
&__row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1rem;
&--summary {
flex-direction: row;
justify-content: flex-start;
:deep(.line-info) {
flex: 1 1 auto;
min-width: 8rem;
}
}
&--body :deep(.line-info__desc) {
line-height: 1.7;
}
}
}
</style>
@@ -1,123 +0,0 @@
<template>
<BasicModal
title="جزئیات روایت شما"
title-en="Story Details"
width="95%"
max-width="56rem"
min-width="auto"
>
<div class="narrative-details">
<div class="narrative-details__head">
<div class="narrative-details__title-block">
<LineInfoBlock title="عنوان روایت" :desc="narrative.title || '—'" />
</div>
<div class="narrative-details__stats">
<div class="narrative-details__stat">
<span class="narrative-details__stat-value">{{ narrative.comments ?? 0 }}</span>
<SvgIcon name="chat-centered-dots" :size="20" />
</div>
<span class="narrative-details__stat-divider" />
<div class="narrative-details__stat">
<span class="narrative-details__stat-value">{{ narrative.likes ?? 0 }}</span>
<SvgIcon name="heart" :size="20" />
</div>
</div>
</div>
<div class="narrative-details__body">
<LineInfoBlock title="نتیجه یا دستاورد مأموریت" :desc="narrative.body || '—'" />
</div>
</div>
</BasicModal>
</template>
<script setup>
import { computed } from 'vue'
import useModal from '@/composables/useModal'
import BasicModal from '@/components/BasicModal.vue'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import LineInfoBlock from '@/components/blocks/LineInfoBlock.vue'
defineOptions({ name: 'NarrativeDetailsModal' })
const { getModal } = useModal()
const narrative = computed(() => getModal('NarrativeDetailsModal')?.data ?? {})
</script>
<style lang="scss" scoped>
.narrative-details {
display: flex;
flex-direction: column;
gap: 1rem;
font-family: var(--font-family-fa);
&__head {
display: flex;
flex-direction: column;
gap: 0.75rem;
@media (min-width: 768px) {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}
&__title-block {
flex: 1;
min-width: 0;
}
&__stats {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.25rem 0.5rem;
background: #fff;
box-shadow: 0 6.75px 19.425px rgba(0, 0, 0, 4%);
border-radius: 0.75rem;
align-self: flex-end;
min-height: 2.25rem;
}
&__stat {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0 0.25rem;
}
&__stat-divider {
width: 1.5px;
height: 1.5rem;
background: #efefef;
border-radius: 9999px;
}
&__stat-value {
font-family: var(--font-family-en);
font-weight: 300;
font-size: 0.95rem;
color: #b3b3b3;
}
&__body :deep(.line-info__desc) {
white-space: pre-line;
line-height: 1.7;
color: #4e4e4e;
}
&__footer {
display: flex;
justify-content: flex-end;
padding-top: 0.5rem;
}
&__close-btn {
min-width: 10rem;
height: 2.25rem;
}
}
</style>
@@ -1,71 +0,0 @@
<template>
<BasicModal
title="جزئیات درخواست"
title-en="Request Details"
width="95%"
max-width="56rem"
min-width="auto"
>
<div class="request-details">
<div class="request-details__row request-details__row--summary">
<LineInfoBlock
title="نام و نام خانوادگی درخواست کننده"
:desc="request.requesterName || '—'"
/>
<LineInfoBlock title="تاریخ ثبت درخواست" :numeric-desc="request.requestDate || ''" />
<LineInfoBlock title="شماره تماس" :numeric-desc="request.phone || ''" />
<LineInfoBlock title="کد پستی" :numeric-desc="request.postalCode || ''" />
</div>
<div class="request-details__row">
<LineInfoBlock title="آدرس" :desc="request.address || '—'" />
</div>
<div class="request-details__row request-details__row--body">
<LineInfoBlock title="توضیحات" :desc="request.description || '—'" />
</div>
</div>
</BasicModal>
</template>
<script setup>
import { computed } from 'vue'
import useModal from '@/composables/useModal'
import BasicModal from '@/components/BasicModal.vue'
import LineInfoBlock from '@/components/blocks/LineInfoBlock.vue'
defineOptions({ name: 'RequestDetailsModal' })
const { getModal } = useModal()
const request = computed(() => getModal('RequestDetailsModal')?.data ?? {})
</script>
<style lang="scss" scoped>
.request-details {
display: flex;
flex-direction: column;
gap: 0.5rem;
font-family: var(--font-family-fa);
&__row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem 1rem;
&--summary {
flex-direction: row;
justify-content: flex-start;
:deep(.line-info) {
flex: 1 1 auto;
min-width: 8rem;
}
}
&--body :deep(.line-info__desc) {
line-height: 1.7;
}
}
}
</style>
-7
View File
@@ -1,7 +0,0 @@
export { default as DispatchDetailsModal } from './components/modals/DispatchDetailsModal.vue'
export { default as MissionaryDispatchItem } from './components/MissionaryDispatchItem.vue'
export { default as MissionaryNarrativeItem } from './components/MissionaryNarrativeItem.vue'
export { default as MissionaryRequestItem } from './components/MissionaryRequestItem.vue'
export { default as MissionaryUserCard } from './components/MissionaryUserCard.vue'
export { default as NarrativeDetailsModal } from './components/modals/NarrativeDetailsModal.vue'
export { default as RequestDetailsModal } from './components/modals/RequestDetailsModal.vue'
@@ -1,203 +0,0 @@
<template>
<div class="student-missionary">
<BoxedIconTitleBlock
class="student-missionary__heading"
title="سامانه اعزام"
desc="در این قسمت شما میتوانید درخواست های اعزام، تاریخچه اعزام و... را مشاهده کنید."
>
<template #icon>
<SvgIcon name="paper-plane-right" :size="24" color="var(--color-primary)" />
</template>
</BoxedIconTitleBlock>
<SkeletonLoaderBlock v-if="profileLoading && !profile" :rows="1" :cols-per-row="1" />
<MissionaryUserCard
v-else
:profile="profile || {}"
@complete-info="onCompleteInfo"
@settings="onProfileSettings"
/>
<TabsBlock v-model="activeTab" :tabs="tabs">
<template #my-requests>
<SkeletonLoaderBlock v-if="requestsLoading" :rows="3" :cols-per-row="1" />
<div v-else-if="requests.length > 0">
<MissionaryRequestItem
v-for="request in requests"
:key="request.id"
:request="request"
@show-details="onRequestDetails"
/>
</div>
<NoItems v-else title="موردی نیست" desc="هنوز درخواستی ثبت نکرده‌اید." />
<PaginationBlock :pagination="requestsMeta" @update:page="setRequestsPage" />
</template>
<template #history>
<SkeletonLoaderBlock v-if="dispatchesLoading" :rows="3" :cols-per-row="1" />
<div v-else-if="dispatches.length > 0">
<MissionaryDispatchItem
v-for="dispatch in dispatches"
:key="dispatch.id"
:dispatch="dispatch"
@show-details="onDispatchDetails"
/>
</div>
<NoItems v-else title="موردی نیست" desc="تاریخچه اعزامی موجود نیست." />
<PaginationBlock :pagination="dispatchesMeta" @update:page="setDispatchesPage" />
</template>
<template #narratives>
<SkeletonLoaderBlock v-if="narrativesLoading" :rows="3" :cols-per-row="1" />
<div v-else-if="narratives.length > 0">
<MissionaryNarrativeItem
v-for="narrative in narratives"
:key="narrative.id"
:narrative="narrative"
@show-details="onNarrativeDetails"
/>
</div>
<NoItems v-else title="موردی نیست" desc="هنوز روایتی ثبت نکرده‌اید." />
<PaginationBlock :pagination="narrativesMeta" @update:page="setNarrativesPage" />
</template>
</TabsBlock>
<RequestDetailsModal v-if="isModal('RequestDetailsModal')" />
<DispatchDetailsModal v-if="isModal('DispatchDetailsModal')" />
<NarrativeDetailsModal v-if="isModal('NarrativeDetailsModal')" />
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import useModal from '@/composables/useModal'
import SvgIcon from '@/components/icons/SvgIcon.vue'
import NoItems from '@/components/blocks/NoItems.vue'
import TabsBlock from '@/components/blocks/TabsBlock.vue'
import { usePagination } from '@/composables/usePagination'
import PaginationBlock from '@/components/blocks/PaginationBlock.vue'
import BoxedIconTitleBlock from '@/components/blocks/BoxedIconTitleBlock.vue'
import SkeletonLoaderBlock from '@/components/blocks/SkeletonLoaderBlock.vue'
import MissionaryUserCard from '@/features/student/missionary/components/MissionaryUserCard.vue'
import MissionaryRequestItem from '@/features/student/missionary/components/MissionaryRequestItem.vue'
import MissionaryDispatchItem from '@/features/student/missionary/components/MissionaryDispatchItem.vue'
import RequestDetailsModal from '@/features/student/missionary/components/modals/RequestDetailsModal.vue'
import MissionaryNarrativeItem from '@/features/student/missionary/components/MissionaryNarrativeItem.vue'
import DispatchDetailsModal from '@/features/student/missionary/components/modals/DispatchDetailsModal.vue'
import NarrativeDetailsModal from '@/features/student/missionary/components/modals/NarrativeDetailsModal.vue'
import {
useMissionaryProfileQuery,
useMissionaryDispatchesQuery,
useMissionaryNarrativesQuery,
useMissionaryRequestsQuery,
} from '@/services/query/student-missionary'
const router = useRouter()
const { openModal, isModal } = useModal()
const tabs = [
{ name: 'my-requests', label: 'درخواست های من', icon: 'list-bullets' },
{ name: 'history', label: 'تاریخچه اعزام ها', icon: 'paper-plane-right' },
{ name: 'narratives', label: 'روایت های من', icon: 'pencil' },
]
const activeTab = ref('my-requests')
const { data: profile, isLoading: profileLoading } = useMissionaryProfileQuery()
const requestsFilters = ref({})
const dispatchesFilters = ref({})
const narrativesFilters = ref({})
const { pagination: requestsPagination, setPage: setRequestsPage } = usePagination({
page: 1,
perPage: 10,
})
const { pagination: dispatchesPagination, setPage: setDispatchesPage } = usePagination({
page: 1,
perPage: 10,
})
const { pagination: narrativesPagination, setPage: setNarrativesPage } = usePagination({
page: 1,
perPage: 10,
})
const { data: requestsData, isLoading: requestsLoading } = useMissionaryRequestsQuery(
requestsFilters,
requestsPagination,
{
enabled: () => activeTab.value === 'my-requests',
keepPreviousData: true,
}
)
const { data: dispatchesData, isLoading: dispatchesLoading } = useMissionaryDispatchesQuery(
dispatchesFilters,
dispatchesPagination,
{
enabled: () => activeTab.value === 'history',
keepPreviousData: true,
}
)
const { data: narrativesData, isLoading: narrativesLoading } = useMissionaryNarrativesQuery(
narrativesFilters,
narrativesPagination,
{
enabled: () => activeTab.value === 'narratives',
keepPreviousData: true,
}
)
const requests = computed(() => requestsData.value?.data ?? [])
const dispatches = computed(() => dispatchesData.value?.data ?? [])
const narratives = computed(() => narrativesData.value?.data ?? [])
const requestsMeta = computed(() => ({
page: requestsPagination.value.page,
perPage: requestsPagination.value.perPage,
...requestsData.value?.meta,
}))
const dispatchesMeta = computed(() => ({
page: dispatchesPagination.value.page,
perPage: dispatchesPagination.value.perPage,
...dispatchesData.value?.meta,
}))
const narrativesMeta = computed(() => ({
page: narrativesPagination.value.page,
perPage: narrativesPagination.value.perPage,
...narrativesData.value?.meta,
}))
const onCompleteInfo = () => {
router.push({ name: 'student-profile' }).catch(() => {})
}
const onProfileSettings = () => {
router.push({ name: 'student-profile' }).catch(() => {})
}
const onRequestDetails = (request) => {
openModal('RequestDetailsModal', request)
}
const onDispatchDetails = (dispatch) => {
openModal('DispatchDetailsModal', dispatch)
}
const onNarrativeDetails = (narrative) => {
openModal('NarrativeDetailsModal', narrative)
}
</script>
<style lang="scss" scoped>
.student-missionary {
display: flex;
flex-direction: column;
gap: 0.5rem;
&__heading {
margin-bottom: 0.75rem;
}
}
</style>
-11
View File
@@ -109,17 +109,6 @@ export default [
subtitle: 'مهارت‌ها و دوره‌های خود را دنبال کنید.',
},
},
{
path: '/missionary',
name: 'student-missionary',
component: () => import('@/features/student/missionary/pages/StudentMissionaryPage.vue'),
meta: {
layout: 'student',
role: 'student',
title: 'سیستم مدیریت اعزام',
subtitle: 'ساده‌ترین راه برای برنامه‌ریزی و پایش مأموریت‌ها',
},
},
{
path: '/user/services',
name: 'student-services',
@@ -31,7 +31,10 @@
/>
</div>
<div v-else-if="contentKind === 'audio'" class="ssd__audio">
<VoiceRecorder :model-value="session.audioUrl" disabled />
<VoiceRecorder
model-value="http://api.tripwisedaily.ir/storage/pending/3/test2-AmQiegjc.mp3?expires=1783677165&signature=38104395400100f1bd74594bfc64ac462c14634a8c2bc4541c988766a08ea2bd"
disabled
/>
</div>
<div v-else-if="contentKind === 'text'" class="ssd__text">
<SvgIcon name="warning" :size="22" color="#b8b8b8" class="ssd__text-icon" />