Files
CRM_Montzar/apps/web/src/components/dashboard/HomeHero.vue
T

408 lines
9.2 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed } from 'vue';
import { SearchOutlined, SendOutlined } from '@/icons/ant-bridge';
import {
Button as AButton,
Input as AInput,
Typography,
} from 'ant-design-vue';
import { brand } from '@mento/shared';
2026-09-15 14:10:52 +03:30
import { useAuthStore } from '@/stores/auth';
const props = defineProps<{
greeting: string;
firstName: string;
userInitial: string;
askQuery: string;
asking?: boolean;
askAnswer?: string;
askError?: string;
statusText?: string;
}>();
const emit = defineEmits<{
'update:askQuery': [value: string];
ask: [];
}>();
2026-09-15 14:10:52 +03:30
const auth = useAuthStore();
const { Title: ATypographyTitle, Text: ATypographyText, Paragraph: ATypographyParagraph } =
Typography;
2026-09-15 14:10:52 +03:30
/** فقط عکس واقعی کاربر؛ پیش‌فرض عمومی را نشان نده */
const hasCustomPhoto = computed(() => Boolean(auth.user?.hasAvatar));
const shortcutLabel = computed(() =>
typeof navigator !== 'undefined' && /Mac|iPhone|iPad/i.test(navigator.platform)
? '⌘K'
: 'Ctrl+K',
);
const statusMessage = computed(() => {
if (props.asking) return props.statusText || 'در حال پاسخ…';
if (props.askError) return props.askError;
if (props.askAnswer) return props.askAnswer;
return '';
});
</script>
<template>
<div class="flex flex-col gap-3">
<div class="home-hero">
<div class="home-hero__gradient" aria-hidden="true" />
<div class="home-hero__pattern" aria-hidden="true" />
<div class="home-hero__orb home-hero__orb--start" aria-hidden="true" />
<div class="home-hero__orb home-hero__orb--end" aria-hidden="true" />
<div class="home-hero__row">
<div class="home-hero__greet">
<div class="home-hero__identity" :aria-label="firstName">
<div class="home-hero__frame">
<img
v-if="hasCustomPhoto"
:src="auth.avatarSrc"
:alt="firstName"
class="home-hero__photo"
/>
<span v-else class="home-hero__monogram" aria-hidden="true">
{{ userInitial }}
</span>
</div>
<span class="home-hero__online" title="آنلاین" aria-hidden="true" />
</div>
<div class="home-hero__copy">
<p class="home-hero__brand">{{ brand.nameFa }}</p>
<a-typography-title :level="4" class="home-hero__title">
{{ greeting }}، {{ firstName }}
</a-typography-title>
<a-typography-text class="home-hero__subtitle">
از داده‌ها بپرسید یا کار امروز را از همین‌جا شروع کنید
</a-typography-text>
</div>
</div>
<div class="home-hero__ask">
<span class="home-hero__ask-ico" aria-hidden="true">
<SearchOutlined />
</span>
<a-input
id="home-hero-ask"
:value="askQuery"
:placeholder="brand.askPlaceholderFa"
:disabled="asking"
:bordered="false"
class="home-hero__ask-input"
@update:value="emit('update:askQuery', $event)"
@press-enter="emit('ask')"
/>
<kbd class="home-hero__ask-kbd">{{ shortcutLabel }}</kbd>
<a-button
type="primary"
shape="round"
:disabled="asking"
:loading="asking"
aria-label="ارسال"
@click="emit('ask')"
>
<template #icon><SendOutlined /></template>
<span class="home-hero__ask-label">بپرس</span>
</a-button>
</div>
</div>
</div>
<div
v-if="statusMessage"
class="rounded-xl border px-3.5 py-2.5"
:class="
askError
? 'border-rose-200 bg-rose-50/70'
: asking
? 'border-emerald-200/70 bg-emerald-50/50'
: 'border-slate-200 bg-slate-50/80'
"
>
<a-typography-paragraph
:type="askError ? 'danger' : 'secondary'"
:ellipsis="{ rows: 3 }"
class="!mb-0 !text-sm"
>
{{ statusMessage }}
</a-typography-paragraph>
</div>
</div>
</template>
<style scoped>
.home-hero {
position: relative;
display: block;
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
overflow: hidden;
border-radius: 1rem;
border: 1px solid rgb(110 231 183 / 0.5);
box-shadow: 0 1px 2px rgb(15 23 42 / 0.04);
}
.home-hero__gradient,
.home-hero__pattern,
.home-hero__orb {
pointer-events: none;
position: absolute;
}
.home-hero__gradient,
.home-hero__pattern {
inset: 0;
}
.home-hero__gradient {
background:
linear-gradient(
115deg,
#ecfdf5 0%,
#d1fae5 28%,
#ccfbf1 55%,
#f0fdfa 78%,
#ffffff 100%
);
}
.home-hero__pattern {
opacity: 0.45;
background-image:
radial-gradient(circle at 1px 1px, rgba(16, 185, 129, 0.28) 1px, transparent 0),
linear-gradient(
135deg,
rgba(20, 184, 166, 0.08) 25%,
transparent 25%,
transparent 50%,
rgba(20, 184, 166, 0.08) 50%,
rgba(20, 184, 166, 0.08) 75%,
transparent 75%,
transparent
);
background-size:
18px 18px,
28px 28px;
mask-image: linear-gradient(to left, rgba(0, 0, 0, 0.85), rgba(0, 0, 0, 0.25));
}
.home-hero__orb {
border-radius: 999px;
filter: blur(48px);
}
.home-hero__orb--start {
inset-inline-end: -5rem;
top: -6rem;
width: 13rem;
height: 13rem;
background: rgb(52 211 153 / 0.2);
}
.home-hero__orb--end {
inset-inline-start: -4rem;
bottom: -5rem;
width: 12rem;
height: 12rem;
background: rgb(20 184 166 / 0.15);
}
.home-hero__row {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: auto minmax(0, 1fr);
align-items: center;
gap: 1rem;
width: 100%;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
padding: 1rem 1.25rem;
}
.home-hero__greet {
display: flex;
align-items: center;
gap: 0.875rem;
min-width: 0;
max-width: 22rem;
}
.home-hero__identity {
position: relative;
flex-shrink: 0;
width: 3.25rem;
height: 3.25rem;
}
.home-hero__frame {
width: 100%;
height: 100%;
border-radius: 1rem;
padding: 2px;
background: linear-gradient(145deg, #34d399 0%, #0d9488 48%, #059669 100%);
box-shadow:
0 0 0 3px rgba(255, 255, 255, 0.85),
0 8px 18px -10px rgba(4, 120, 87, 0.55);
}
.home-hero__photo,
.home-hero__monogram {
display: flex;
width: 100%;
height: 100%;
border-radius: calc(1rem - 2px);
object-fit: cover;
}
.home-hero__monogram {
align-items: center;
justify-content: center;
font-size: 1.15rem;
font-weight: 800;
color: #ecfdf5;
background:
radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.22), transparent 45%),
linear-gradient(160deg, #10b981 0%, #0f766e 100%);
letter-spacing: -0.02em;
user-select: none;
}
.home-hero__online {
position: absolute;
inset-inline-start: -1px;
bottom: -1px;
width: 0.85rem;
height: 0.85rem;
border-radius: 999px;
background: #10b981;
border: 2px solid #fff;
box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.25);
}
.home-hero__copy {
min-width: 0;
}
.home-hero__brand {
margin: 0;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.02em;
color: rgb(4 120 87 / 0.8);
}
.home-hero__title {
margin: 0.125rem 0 0 !important;
color: #1e293b !important;
font-size: 1.125rem !important;
font-weight: 800 !important;
line-height: 1.75rem !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.home-hero__subtitle {
display: block !important;
margin-top: 0.125rem !important;
color: #64748b !important;
font-size: 0.875rem !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.home-hero__ask {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
min-width: 0;
box-sizing: border-box;
padding-block: 0.375rem;
padding-inline: 0.75rem 0.5rem;
background: #fff;
border: 1px solid rgb(255 255 255 / 0.8);
border-radius: 1rem;
box-shadow:
0 4px 12px rgb(6 78 59 / 0.05),
0 1px 2px rgb(15 23 42 / 0.04);
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.home-hero__ask:focus-within {
border-color: #6ee7b7;
box-shadow:
0 0 0 2px rgb(167 243 208 / 0.7),
0 4px 12px rgb(6 78 59 / 0.05);
}
.home-hero__ask-ico {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: 2rem;
height: 2rem;
border-radius: 0.75rem;
background: #ecfdf5;
color: #059669;
}
.home-hero__ask-input {
flex: 1 1 auto;
min-width: 0;
background: transparent !important;
}
.home-hero__ask-kbd {
display: none;
flex-shrink: 0;
align-items: center;
padding: 0.25rem 0.5rem;
border-radius: 0.5rem;
border: 1px solid #e2e8f0;
background: #f8fafc;
color: #94a3b8;
font-size: 11px;
font-family: ui-sans-serif, system-ui, sans-serif;
}
.home-hero__ask-label {
display: none;
}
@media (min-width: 640px) {
.home-hero__ask-kbd {
display: inline-flex;
}
.home-hero__ask-label {
display: inline;
}
.home-hero__title {
font-size: 1.25rem !important;
}
}
@media (max-width: 1023px) {
.home-hero__row {
grid-template-columns: minmax(0, 1fr);
}
.home-hero__greet {
max-width: none;
}
}
</style>