From 0a3a5d7c821af79f2d8255461bc7937198e210b5 Mon Sep 17 00:00:00 2001 From: safavimohadeseh78 Date: Wed, 9 Sep 2026 16:57:41 +0330 Subject: [PATCH] feat(ui): desktop piggy/notifications views, support chat fixes, gift card list --- .cursor/rules/ai-team-prompt.mdc | 177 +++++++++ .../abshode-domain-lookup/learned-notes.md | 26 ++ AI_TEAM_PROMPT.md | 173 +++++++++ src/App.vue | 4 +- src/api/giftCard.ts | 26 +- src/app/authBoot.ts | 34 +- src/app/setupRouter.ts | 2 + src/layouts/config/navigation.ts | 2 +- src/layouts/desktop/desktop-layout.less | 5 + src/layouts/mobile/MobileBottomNav.vue | 2 +- src/layouts/styles/mobile-frame.less | 1 + src/router/index.ts | 2 +- .../desktop/DesktopAppNotificationsView.vue | 54 ++- src/views/base/appNotifications/index.vue | 28 ++ .../components/EducateContentPanel.vue | 2 +- .../giftCard/components/GiftCardLists.vue | 118 ++++++ .../giftCard/composables/useGiftCardPage.ts | 57 ++- .../giftCard/desktop/DesktopGiftCardView.vue | 2 + .../giftCard/mobile/MobileGiftCardView.vue | 4 +- .../base/home/composables/useHomePage.ts | 9 - .../components/DesktopQuickActionsGrid.vue | 20 +- .../components/DesktopQuickActionsPanel.vue | 20 +- .../sections/BoursecuQuickActionsSection.vue | 20 +- .../base/piggy/components/PiggyTxModal.vue | 54 +++ .../piggy/composables/usePiggyCreatePage.ts | 100 +++++ .../piggy/composables/usePiggyInfoPage.ts | 152 ++++++++ .../base/piggy/composables/usePiggyPage.ts | 64 ++++ .../piggy/desktop/DesktopPiggyCreateView.vue | 117 ++++++ .../piggy/desktop/DesktopPiggyInfoView.vue | 117 ++++++ .../base/piggy/desktop/DesktopPiggyView.vue | 126 ++++++ .../desktop/styles/desktop-piggy-create.less | 163 ++++++++ .../desktop/styles/desktop-piggy-info.less | 177 +++++++++ .../piggy/desktop/styles/desktop-piggy.less | 297 ++++++++++++++ src/views/base/piggy/index.vue | 289 ++------------ src/views/base/piggy/info.vue | 326 ++-------------- .../piggy/mobile/MobilePiggyCreateView.vue | 266 +++++++++++++ .../base/piggy/mobile/MobilePiggyInfoView.vue | 186 +++++++++ .../base/piggy/mobile/MobilePiggyView.vue | 244 ++++++++++++ src/views/base/piggy/new.vue | 361 +----------------- .../base/transactions/components/Item.vue | 10 +- .../composables/useTransactionsPageData.ts | 129 ++++++- .../base/transactions/config/boursecuTabs.ts | 21 +- .../base/user/composables/useUserPage.ts | 6 +- .../base/user/desktop/DesktopUserView.vue | 2 +- src/views/login/components/Login.vue | 13 +- src/views/login/composables/useLoginForm.ts | 4 + .../components/SupportChatComposer.vue | 14 +- .../support/components/SupportChatHeader.vue | 43 ++- .../components/SupportChatMessages.vue | 30 +- src/views/support/index.vue | 237 ++++++++++-- 50 files changed, 3242 insertions(+), 1094 deletions(-) create mode 100644 .cursor/rules/ai-team-prompt.mdc create mode 100644 .cursor/skills/abshode-domain-lookup/learned-notes.md create mode 100644 AI_TEAM_PROMPT.md create mode 100644 src/views/base/giftCard/components/GiftCardLists.vue create mode 100644 src/views/base/piggy/components/PiggyTxModal.vue create mode 100644 src/views/base/piggy/composables/usePiggyCreatePage.ts create mode 100644 src/views/base/piggy/composables/usePiggyInfoPage.ts create mode 100644 src/views/base/piggy/composables/usePiggyPage.ts create mode 100644 src/views/base/piggy/desktop/DesktopPiggyCreateView.vue create mode 100644 src/views/base/piggy/desktop/DesktopPiggyInfoView.vue create mode 100644 src/views/base/piggy/desktop/DesktopPiggyView.vue create mode 100644 src/views/base/piggy/desktop/styles/desktop-piggy-create.less create mode 100644 src/views/base/piggy/desktop/styles/desktop-piggy-info.less create mode 100644 src/views/base/piggy/desktop/styles/desktop-piggy.less create mode 100644 src/views/base/piggy/mobile/MobilePiggyCreateView.vue create mode 100644 src/views/base/piggy/mobile/MobilePiggyInfoView.vue create mode 100644 src/views/base/piggy/mobile/MobilePiggyView.vue diff --git a/.cursor/rules/ai-team-prompt.mdc b/.cursor/rules/ai-team-prompt.mdc new file mode 100644 index 0000000..2af0b21 --- /dev/null +++ b/.cursor/rules/ai-team-prompt.mdc @@ -0,0 +1,177 @@ +--- +description: دستورالعمل اجباری AI — پروژه ابشوده/خانگی (توکن کم، پچ محدود) +alwaysApply: true +--- +# دستورالعمل اجباری برای هوش مصنوعی — پروژه ابشوده / خانگی + +این فایل را کامل به AI بده (Project Rules / Custom Instructions / System prompt). +هدف: **توکن کمتر، یادگیری پایدار، حل سریع اصل کار، بدون تغییر الکی.** + +--- + +## ۱) با هر پرامپت کاربر — همین ترتیب + +1. اصل قضیه را **یک خط** مشخص کن (یک باگ / یک UI / یک فیچر). +2. مسیر را از **نقشه و یادداشت** بگیر — کل ریپو را سرچ نکن. +3. فقط **۱ تا ۳ فایل** مربوط را باز/پچ کن. +4. تمام کن؛ جواب کوتاه بده. +5. یک خط **یادداشت بخش** بنویس (پایین ببین) تا دفعهٔ بعد فراموش نشود. + +``` +پرامپت → اصل کار → نقشه/یادداشت → Grep فقط همان پوشه → پچ محدود → یادداشت → تمام +``` + +### ممنوع (سوخت توکن) + +- دوباره پیدا کردن فایلی که در نقشه/یادداشت هست +- explore / گشت سراسری برای «X کجاست؟» +- خواندن کل فایل بلند وقتی چند ده خط کافی است +- refactor یا «درست کردن» فایل‌های هم‌جوار بدون درخواست +- کپی پچ به سیستم/اپ دیگر «برای کامل بودن» + +--- + +## ۲) محدودهٔ تغییر — حساس + +- **فقط** همان چیزی که کاربر گفته؛ جای بی‌ربط را دست نزن. +- مبهم بود (هدف، اپ، برنچ، سیستم حسابداری) → **بپرس**؛ حدس نزن. +- روی **برنچ فعلی** بمان؛ checkout/merge به برنچ دیگر بدون اجازه ممنوع. +- هر زیرپروژه git جدا دارد؛ فقط همان ریپوی مربوط را عوض کن. + +| کار | فقط این ریپو | +|-----|----------------| +| ادمین | `khaneghi_admin` | +| API / SQL | `khaneghi_server` | +| اپ کاربر | `khaneghi_user` + تم + `ghatreh_user` (هر سه اگر رفتاری است) | +| سوپرادمین | `controlpanel_server` / `controlpanel_user` | +| بورس مس | فقط با درخواست صریح | + +--- + +## ۳) حسابداری — قانون سخت + +سیستم‌ها **جدا** هستند: زرین (`zarin`) / ته‌حساب (`tahesab`) / پرگار (`pargar`). + +1. اگر گفت حسابداری/سند/سینک مشتری و **نام نبرد** → اول بپرس کدام؟ +2. تا جواب ندادی کد نزن و فایل سیستم دیگر را باز نکن. +3. پچ یک سیستم را به بقیه کپی نکن مگر در همان پیام صریح بگوید. + +--- + +## ۴) یادگیری قوی — یادداشت بخش‌ها + +هر کار تمام‌شده را در دفتر پروژه ثبت کن (اگر در این ریپو هست: +`.cursor/skills/abshode-domain-lookup/learned-notes.md`): + +``` +- YYYY-MM-DD | موضوع | مسیر فایل(ها) | نکتهٔ ۱ خطی +``` + +قبل از هر Grep سراسری: **یادداشت‌ها را بخوان.** +اگر مسیر آنجا بود، دوباره سرچ نکن. + +--- + +## ۵) نقشهٔ سریع مسیرها + +| موضوع | کجا | +|-------|-----| +| تنظیمات ادمین | `khaneghi_admin/src/pages/menuPages/configuration/` | +| فلگ منو از settings | `khaneghi_admin/src/utils/config.ts` | +| مشتریان | `khaneghi_admin/src/pages/menuPages/customers/` | +| همگام ته‌حساب | `…/accounting/components/TahesabCustomerSync.vue`, `TahesabWalletBulkSync.vue` | +| کد حساب همکار | `…/colleagueOrders/components/ColleagueAccountingAccounts.vue` | +| API ته‌حساب (فرانت) | `khaneghi_admin/src/api/tahesab.ts` | +| تنظیمات سرور / overlay سوپرادمین | `SuperAdminProgramSettingsOverlay`, `SettingsController` | +| deploy settings | `khaneghi_server/database/sql/deploy_settings_const.sql` — **value موجود را UPDATE نکن** | +| قیمت ویژه مشتری | `customer_special_pricing` + منوی `customers/special-price` | + +نقشهٔ کامل‌تر (اگر موجود): `.cursor/skills/abshode-domain-lookup/project-map.md` + +--- + +## ۶) فرانت — زیاد از Ant + UnoCSS + lodash استفاده کن + +اولویت: + +1. **BasicPacks** پروژه (دکمه/آیکون/اینپوت/مدال) اگر هست +2. **Ant Design Vue** (`a-button`, `a-input`, `a-table`, `a-spin`, `a-form`, …) +3. **UnoCSS** برای layout و فاصله (`flex`, `grid`, `gap-*`, `w-full`, `min-w-0`, `sm:`) +4. **lodash** برای داده (`get`, `debounce`, `cloneDeep`, `isEmpty`, …) — helper تکراری ننویس +5. CSS/` diff --git a/src/views/base/piggy/desktop/DesktopPiggyInfoView.vue b/src/views/base/piggy/desktop/DesktopPiggyInfoView.vue new file mode 100644 index 0000000..575783a --- /dev/null +++ b/src/views/base/piggy/desktop/DesktopPiggyInfoView.vue @@ -0,0 +1,117 @@ + + + + + diff --git a/src/views/base/piggy/desktop/DesktopPiggyView.vue b/src/views/base/piggy/desktop/DesktopPiggyView.vue new file mode 100644 index 0000000..42f25ab --- /dev/null +++ b/src/views/base/piggy/desktop/DesktopPiggyView.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/src/views/base/piggy/desktop/styles/desktop-piggy-create.less b/src/views/base/piggy/desktop/styles/desktop-piggy-create.less new file mode 100644 index 0000000..3a9666b --- /dev/null +++ b/src/views/base/piggy/desktop/styles/desktop-piggy-create.less @@ -0,0 +1,163 @@ +/* Desktop piggy create */ + +.dpiggy-create { + min-height: 100%; + background: #f6f7f9; + color: #111827; +} + +.dpiggy-create__container { + max-width: 1180px; + margin: 0 auto; + padding: 28px 36px 40px; +} + +.dpiggy-create__layout { + display: grid; + grid-template-columns: 1fr; + gap: 16px; +} + +.dpiggy-create__card { + background: #fff; + border: 1px solid rgba(15, 23, 42, 0.06); + border-radius: 16px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + padding: 22px 24px; +} + +.dpiggy-create__step-head { + display: flex; + align-items: flex-start; + gap: 14px; + margin-bottom: 18px; +} + +.dpiggy-create__step-num { + flex-shrink: 0; + display: flex; + width: 36px; + height: 36px; + align-items: center; + justify-content: center; + border-radius: 999px; + background: @App-primary-color; + color: #fff; + font-size: 14px; + font-weight: 700; +} + +.dpiggy-create__step-title { + margin: 0; + font-size: 16px; + font-weight: 700; + color: #111827; +} + +.dpiggy-create__step-desc { + margin: 4px 0 0; + font-size: 13px; + color: #6b7280; + line-height: 1.6; +} + +.dpiggy-create__products { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 10px; +} + +.dpiggy-create__product { + display: flex; + min-width: 0; + align-items: center; + gap: 10px; + padding: 14px 16px; + border: 1px solid rgba(15, 23, 42, 0.08); + border-radius: 14px; + background: #fafbfc; + text-align: right; + font-size: 14px; + color: #374151; + font-family: inherit; + cursor: pointer; + transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease; + + &:hover { + border-color: color-mix(in srgb, @App-primary-color 35%, transparent); + } + + &--on { + background: @App-primary-color; + border-color: @App-primary-color; + color: #fff; + } +} + +.dpiggy-create__locked { + display: flex; + min-width: 0; + align-items: center; + gap: 10px; + padding: 14px 16px; + border-radius: 14px; + background: #f3f4f6; + font-size: 14px; + font-weight: 700; + color: #111827; +} + +.dpiggy-create__title-row { + display: flex; + min-width: 0; + align-items: center; + gap: 12px; +} + +.dpiggy-create__title-icon { + flex-shrink: 0; + display: flex; + width: 48px; + height: 48px; + align-items: center; + justify-content: center; + border-radius: 14px; + background: color-mix(in srgb, @App-primary-color 8%, #fff); + color: @App-primary-color; +} + +.dpiggy-create__presets { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 14px; +} + +.dpiggy-create__chip { + border: 1px solid rgba(15, 23, 42, 0.08); + border-radius: 999px; + background: #f3f4f6; + padding: 8px 14px; + font-size: 13px; + color: #4b5563; + font-family: inherit; + cursor: pointer; + transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease; + + &:hover { + border-color: color-mix(in srgb, @App-primary-color 30%, transparent); + } + + &--on { + background: @App-primary-color; + border-color: @App-primary-color; + color: #fff; + } +} + +.dpiggy-create__submit { + width: 100%; + min-height: 48px !important; + border-radius: 14px !important; + font-weight: 600 !important; +} diff --git a/src/views/base/piggy/desktop/styles/desktop-piggy-info.less b/src/views/base/piggy/desktop/styles/desktop-piggy-info.less new file mode 100644 index 0000000..9485270 --- /dev/null +++ b/src/views/base/piggy/desktop/styles/desktop-piggy-info.less @@ -0,0 +1,177 @@ +/* Desktop piggy info */ + +.dpiggy-info { + min-height: 100%; + background: #f6f7f9; + color: #111827; +} + +.dpiggy-info__container { + max-width: 1180px; + margin: 0 auto; + padding: 28px 36px 40px; +} + +.dpiggy-info__layout { + display: grid; + grid-template-columns: minmax(280px, 360px) minmax(0, 1fr); + gap: 20px; + align-items: start; +} + +.dpiggy-info__side { + display: flex; + flex-direction: column; + gap: 16px; +} + +.dpiggy-info__hero { + background: @App-primary-color; + color: #fff; + border-radius: 16px; + padding: 24px; + box-shadow: 0 12px 28px color-mix(in srgb, @App-primary-color 28%, transparent); +} + +.dpiggy-info__product { + margin: 0; + display: inline-flex; + max-width: 100%; + padding: 6px 12px; + border-radius: 999px; + background: rgb(255 255 255 / 0.14); + font-size: 12px; + color: rgb(255 255 255 / 0.9); +} + +.dpiggy-info__label { + margin: 18px 0 0; + font-size: 13px; + color: rgb(255 255 255 / 0.75); +} + +.dpiggy-info__value { + margin: 6px 0 0; + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 8px; + font-size: 36px; + font-weight: 700; + letter-spacing: -0.03em; + line-height: 1.1; + + span { + font-size: 14px; + font-weight: 500; + color: rgb(255 255 255 / 0.75); + } +} + +.dpiggy-info__free { + margin: 14px 0 0; + font-size: 13px; + line-height: 1.6; + color: rgb(255 255 255 / 0.72); + + strong { + color: #fff; + } +} + +.dpiggy-info__actions { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.dpiggy-info__action { + min-height: 44px !important; + border-radius: 12px !important; +} + +.dpiggy-info__action-inner { + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + font-size: 13px; + font-weight: 600; +} + +.dpiggy-info__tx { + background: #fff; + border: 1px solid rgba(15, 23, 42, 0.06); + border-radius: 16px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + padding: 20px 22px; + min-height: 320px; +} + +.dpiggy-info__tx-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 16px; +} + +.dpiggy-info__tx-title { + margin: 0; + font-size: 16px; + font-weight: 700; + color: #111827; +} + +.dpiggy-info__tx-count { + font-size: 13px; + color: #6b7280; +} + +.dpiggy-info__tx-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 48px 16px; + color: #9ca3af; + text-align: center; +} + +.dpiggy-info__tx-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.dpiggy-info__tx-row { + display: flex; + min-width: 0; + align-items: center; + gap: 12px; + padding: 14px 16px; + border-radius: 12px; + background: #f8fafc; + border: 1px solid rgba(15, 23, 42, 0.04); +} + +.dpiggy-info__tx-dot { + width: 10px; + height: 10px; + flex-shrink: 0; + border-radius: 999px; + background: @App-primary-color; +} + +.dpiggy-info__tx-amount { + flex-shrink: 0; + font-weight: 700; + color: @App-primary-color; +} + +@media (max-width: 960px) { + .dpiggy-info__layout { + grid-template-columns: 1fr; + } +} diff --git a/src/views/base/piggy/desktop/styles/desktop-piggy.less b/src/views/base/piggy/desktop/styles/desktop-piggy.less new file mode 100644 index 0000000..7e594bd --- /dev/null +++ b/src/views/base/piggy/desktop/styles/desktop-piggy.less @@ -0,0 +1,297 @@ +/* Desktop piggy — aligned with fund/wallet desktop pages */ + +.dpiggy { + min-height: 100%; + background: #f6f7f9; + color: #111827; +} + +.dpiggy__container { + max-width: 1180px; + margin: 0 auto; + padding: 28px 36px 40px; +} + +.dpiggy-toolbar { + display: flex; + flex-wrap: wrap; + align-items: stretch; + gap: 16px; + margin-bottom: 24px; +} + +.dpiggy-summary { + flex: 1 1 280px; + min-width: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 20px 22px; + border-radius: 16px; + background: @App-primary-color; + color: #fff; + box-shadow: 0 10px 28px color-mix(in srgb, @App-primary-color 28%, transparent); + + &__label { + margin: 0; + font-size: 13px; + font-weight: 500; + color: rgb(255 255 255 / 0.8); + } + + &__value { + margin: 6px 0 0; + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 8px; + font-size: 28px; + font-weight: 700; + letter-spacing: -0.02em; + line-height: 1.15; + } + + &__unit { + font-size: 13px; + font-weight: 500; + color: rgb(255 255 255 / 0.75); + } + + &__meta { + margin-top: 10px; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; + font-size: 12px; + color: rgb(255 255 255 / 0.72); + } + + &__dot { + width: 4px; + height: 4px; + border-radius: 999px; + background: rgb(255 255 255 / 0.45); + } + + &__badge { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 48px; + height: 48px; + border-radius: 14px; + background: rgb(255 255 255 / 0.14); + } +} + +.dpiggy-add { + flex: 0 0 auto; + align-self: stretch; + min-width: 180px; + height: auto !important; + min-height: 100%; + padding: 0 22px !important; + border-radius: 16px !important; + font-weight: 600 !important; +} + +.dpiggy-list { + &__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-bottom: 14px; + } + + &__title { + margin: 0; + font-size: 16px; + font-weight: 700; + color: #111827; + } + + &__count { + font-size: 13px; + color: #6b7280; + } +} + +.dpiggy-groups { + display: flex; + flex-direction: column; + gap: 16px; +} + +.dpiggy-group { + overflow: hidden; + background: #fff; + border: 1px solid rgba(15, 23, 42, 0.06); + border-radius: 16px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + + &__head { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + padding: 16px 20px; + border-bottom: 1px solid rgba(15, 23, 42, 0.06); + background: #fafbfc; + } + + &__name { + margin: 0; + font-size: 15px; + font-weight: 700; + color: #111827; + } + + &__count { + margin: 4px 0 0; + font-size: 12px; + color: #6b7280; + } + + &__total { + margin: 0; + display: flex; + align-items: baseline; + gap: 6px; + font-size: 15px; + font-weight: 700; + color: @App-primary-color; + + span { + font-size: 12px; + font-weight: 500; + color: #6b7280; + } + } + + &__list { + display: flex; + flex-direction: column; + } +} + +.dpiggy-row { + display: flex; + width: 100%; + min-width: 0; + align-items: center; + gap: 14px; + padding: 14px 20px; + border: none; + border-bottom: 1px solid rgba(15, 23, 42, 0.05); + background: transparent; + text-align: right; + font-family: inherit; + cursor: pointer; + transition: background 0.15s ease; + + &:last-child { + border-bottom: none; + } + + &:hover { + background: #f8fafc; + } + + &__icon { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + width: 42px; + height: 42px; + border-radius: 12px; + background: color-mix(in srgb, @App-primary-color 8%, #fff); + color: @App-primary-color; + } + + &__main { + flex: 1; + min-width: 0; + } + + &__title { + font-size: 14px; + font-weight: 700; + color: #111827; + } + + &__sub { + margin-top: 3px; + font-size: 12px; + color: #9ca3af; + } + + &__amount { + display: flex; + flex-direction: column; + align-items: flex-end; + flex-shrink: 0; + line-height: 1.25; + + strong { + font-size: 14px; + font-weight: 700; + color: #111827; + } + + span { + font-size: 11px; + color: #6b7280; + } + } + + &__chevron { + flex-shrink: 0; + color: #d1d5db; + } +} + +.dpiggy-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8px; + padding: 48px 24px; + text-align: center; + background: #fff; + border: 1px solid rgba(15, 23, 42, 0.06); + border-radius: 16px; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04); + + &__icon { + display: flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + margin-bottom: 8px; + border-radius: 16px; + background: #f3f4f6; + color: @App-primary-color; + } + + &__title { + margin: 0; + font-size: 16px; + font-weight: 700; + color: #111827; + } + + &__desc { + margin: 0; + max-width: 28rem; + font-size: 14px; + line-height: 1.7; + color: #6b7280; + } +} diff --git a/src/views/base/piggy/index.vue b/src/views/base/piggy/index.vue index b6be005..3c8f614 100644 --- a/src/views/base/piggy/index.vue +++ b/src/views/base/piggy/index.vue @@ -1,280 +1,35 @@ - diff --git a/src/views/base/piggy/info.vue b/src/views/base/piggy/info.vue index acc8b00..3eb05c3 100644 --- a/src/views/base/piggy/info.vue +++ b/src/views/base/piggy/info.vue @@ -1,317 +1,35 @@ - diff --git a/src/views/base/piggy/mobile/MobilePiggyCreateView.vue b/src/views/base/piggy/mobile/MobilePiggyCreateView.vue new file mode 100644 index 0000000..119b4aa --- /dev/null +++ b/src/views/base/piggy/mobile/MobilePiggyCreateView.vue @@ -0,0 +1,266 @@ + + + + + diff --git a/src/views/base/piggy/mobile/MobilePiggyInfoView.vue b/src/views/base/piggy/mobile/MobilePiggyInfoView.vue new file mode 100644 index 0000000..976ff01 --- /dev/null +++ b/src/views/base/piggy/mobile/MobilePiggyInfoView.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/views/base/piggy/mobile/MobilePiggyView.vue b/src/views/base/piggy/mobile/MobilePiggyView.vue new file mode 100644 index 0000000..cf67563 --- /dev/null +++ b/src/views/base/piggy/mobile/MobilePiggyView.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/views/base/piggy/new.vue b/src/views/base/piggy/new.vue index 3a914c0..dbc20c7 100644 --- a/src/views/base/piggy/new.vue +++ b/src/views/base/piggy/new.vue @@ -1,356 +1,35 @@ - diff --git a/src/views/base/transactions/components/Item.vue b/src/views/base/transactions/components/Item.vue index 2a7586b..832e674 100644 --- a/src/views/base/transactions/components/Item.vue +++ b/src/views/base/transactions/components/Item.vue @@ -26,7 +26,7 @@ class="dtxn-row__amount" :class="props.item.cash > 0 ? 'dtxn-row__amount--up' : 'dtxn-row__amount--down'" > - {{ usePriceWithoutType(props.item.cash ?? 0) }} ریال + {{ usePriceWithoutType(props.item.cash ?? 0) }}{{ amountSuffix }}
@@ -56,7 +56,7 @@
{{ usePriceWithoutType(props.item.cash ?? 0) }}
- ریال + {{ amountSuffix.trim() }}
@@ -130,6 +130,12 @@ const props = withDefaults( const tradePrice = computed(() => getTransactionTradePrice(props.item)) const statusLabel = computed(() => resolveTransactionStatusLabel(props.item)) +const amountSuffix = computed(() => { + if (props.item.unit === 'تخفیف') return '٪' + if (props.item.unit === 'طلا') return ' طلا' + return ' ریال' +}) + const isUp = computed( () => props.item.type === 3 diff --git a/src/views/base/transactions/composables/useTransactionsPageData.ts b/src/views/base/transactions/composables/useTransactionsPageData.ts index 34d8704..79cc191 100644 --- a/src/views/base/transactions/composables/useTransactionsPageData.ts +++ b/src/views/base/transactions/composables/useTransactionsPageData.ts @@ -1,6 +1,10 @@ import { ref } from 'vue' import { getTransactionTable, getTransactionTable2 } from '@/api/transaction' import { getPhysicalReceiveList } from '@/api/physicalreceive' +import { + fetchMyGiftCards, + type CustomerGiftCardItem, +} from '@/api/giftCard' import type { TableEngineInput, TableEngineOutput } from '@/models/table' import type { Transaction } from '@/models/transaction' import type { Physicalreceive } from '@/models/physicalreceive' @@ -13,6 +17,54 @@ import { type BoursecuTransactionTabId, } from '../config/boursecuTabs' +function mapGiftHistoryToTransaction(item: CustomerGiftCardItem): Transaction { + const amount = Number(item.amount) + const isDiscount = item.kind === 'discount' + const baseTitle = item.name?.trim() || (isDiscount ? 'کد تخفیف' : 'کارت هدیه') + const codePart = item.code ? ` · ${item.code}` : '' + + if (isDiscount) { + const percent = Number.isFinite(amount) ? (amount <= 1 ? amount * 100 : amount) : 0 + return { + id: item.id, + title: `${baseTitle}${codePart}`, + cash: percent, + expire_at: item.used_at || item.expiry_date || undefined, + sts_str: 'مصرف‌شده', + note: item.code, + unit: 'تخفیف', + type: 3, + tracked_number: 0, + } + } + + if (item.unit === 2) { + return { + id: item.id, + title: `${baseTitle}${codePart}`, + cash: Number.isFinite(amount) ? amount : 0, + expire_at: item.used_at || item.expiry_date || undefined, + sts_str: 'مصرف‌شده', + note: item.code, + unit: 'طلا', + type: 3, + tracked_number: 0, + } + } + + return { + id: item.id, + title: `${baseTitle}${codePart}`, + cash: Number.isFinite(amount) ? amount : 0, + expire_at: item.used_at || item.expiry_date || undefined, + sts_str: 'مصرف‌شده', + note: item.code, + unit: 'ریال', + type: 3, + tracked_number: 0, + } +} + /** Shared transactions page state — refetched on every page visit (no list cache). */ let activeFetchId = 0 @@ -60,10 +112,72 @@ function resolveTransactionFetcher() { return getTransactionTable } +async function fetchGiftCodeTransactionsPage( + fetchId: number, + kind: 'gift' | 'discount', +): Promise { + try { + const res = await fetchMyGiftCards() + if (fetchId !== activeFetchId) { + return false + } + + const rows = (res?.history ?? []) + .filter((item) => item.kind === kind) + .map(mapGiftHistoryToTransaction) + + transactionTableOutput.value = { + rows, + query: { + filters: [], + filtersOpt: null, + key: null, + limit: rows.length || 10, + page: 0, + search: '', + select: null, + sort: null, + total: rows.length, + }, + } + transactionItems.value = rows + physicalReceiveItems.value = [] + return true + } catch (error) { + if (fetchId === activeFetchId) { + console.error('Error fetching gift/discount transactions:', error) + transactionItems.value = [] + physicalReceiveItems.value = [] + transactionTableOutput.value = { + rows: [], + query: { + filters: [], + filtersOpt: null, + key: null, + limit: 10, + page: 0, + search: '', + select: null, + sort: null, + total: 0, + }, + } + } + return false + } +} + async function fetchTransactionsPage(append: boolean): Promise { const fetchId = ++activeFetchId const isFirstPage = transactionPagination.value.page === 0 && !append - const isPhysical = getActiveBoursecuTab().fetchMode === 'physical' + const activeTab = getActiveBoursecuTab() + const isPhysical = activeTab.fetchMode === 'physical' + const giftKind = + activeTab.fetchMode === 'giftcard' + ? 'gift' + : activeTab.fetchMode === 'discount' + ? 'discount' + : null if (isFirstPage) { loading.value = true @@ -73,6 +187,14 @@ async function fetchTransactionsPage(append: boolean): Promise { } try { + if (giftKind) { + // لیست کامل از mine؛ صفحه‌بندی سمت سرور ندارد + if (append) { + return false + } + return await fetchGiftCodeTransactionsPage(fetchId, giftKind) + } + const output = await resolveTransactionFetcher()(transactionPagination.value) if (fetchId !== activeFetchId) { return false @@ -128,6 +250,11 @@ export async function loadMoreTransactions() { return } + const fetchMode = getActiveBoursecuTab().fetchMode + if (fetchMode === 'giftcard' || fetchMode === 'discount') { + return + } + if ( !transactionTableOutput.value || (transactionPagination.value.page + 1) * transactionPagination.value.limit diff --git a/src/views/base/transactions/config/boursecuTabs.ts b/src/views/base/transactions/config/boursecuTabs.ts index feaf113..7043af6 100644 --- a/src/views/base/transactions/config/boursecuTabs.ts +++ b/src/views/base/transactions/config/boursecuTabs.ts @@ -5,10 +5,17 @@ export type BoursecuTransactionTabId = | 'deposit' | 'withdrawal' | 'physical' + | 'giftcard' + | 'discount' export type BoursecuTradeSide = 'buy' | 'sell' -export type BoursecuTransactionFetchMode = 'transaction' | 'deposit' | 'physical' +export type BoursecuTransactionFetchMode = + | 'transaction' + | 'deposit' + | 'physical' + | 'giftcard' + | 'discount' export type BoursecuTradeStatusFilter = 'completed' | 'pending' | 'cancelled' export type BoursecuFulfillmentStatusFilter = 'delivered' | 'registered' | 'cancelled' @@ -78,6 +85,18 @@ export const BOURSECU_TRANSACTION_TABS: BoursecuTransactionTab[] = [ fetchMode: 'physical', showAdvancedFilters: true, }, + { + id: 'giftcard', + label: 'کارت هدیه', + emptyText: 'کارت هدیه‌ای برای شما ثبت نشده است', + fetchMode: 'giftcard', + }, + { + id: 'discount', + label: 'کد تخفیف', + emptyText: 'کد تخفیفی برای شما ثبت نشده است', + fetchMode: 'discount', + }, ] const TRADE_TYPE_BY_SIDE: Record = { diff --git a/src/views/base/user/composables/useUserPage.ts b/src/views/base/user/composables/useUserPage.ts index e07796a..a203e67 100644 --- a/src/views/base/user/composables/useUserPage.ts +++ b/src/views/base/user/composables/useUserPage.ts @@ -20,7 +20,6 @@ import { getBoursecuActionIconSrc } from '@/utils/config' import { getReferralShareText, getReferralShareTitle, shareReferralText } from '@/utils/referralShare' import { normalizeAuthFormFields } from '@/utils/persianDigits' import { useSettingsStore } from '@/store/settingsStore' -import { useSupportMessenger } from '@/composables/useSupportMessenger' import { useTrackingSystemsFeature } from '@/composables/useTrackingSystemsFeature' import { openGoldDayCalculator } from '@/composables/useGoldDayCalculator' import { isUserSupportChatEnabled, unreadSupportCount } from '@/utils/chat' @@ -88,7 +87,6 @@ export function useUserPage() { const editPasswordVisible = ref(false) const showLogoutConfirm = ref(false) const localAvatarUrl = ref(null) - const { openSupportMessenger } = useSupportMessenger() const { trackingSystemsEnabled } = useTrackingSystemsFeature() const profileAvatarUrl = computed( @@ -159,9 +157,7 @@ export function useUserPage() { id: 'support', label: 'پشتیبانی', icon: 'vuesax-linear:messages-2', - onClick: () => { - void openSupportMessenger() - }, + onClick: () => router.push({ name: 'support' }), visible: isUserSupportChatEnabled(), badge: unreadSupportCount.value > 0 ? unreadSupportCount.value : undefined, }, diff --git a/src/views/base/user/desktop/DesktopUserView.vue b/src/views/base/user/desktop/DesktopUserView.vue index 16707be..f5b6708 100644 --- a/src/views/base/user/desktop/DesktopUserView.vue +++ b/src/views/base/user/desktop/DesktopUserView.vue @@ -64,7 +64,7 @@
-

تنظیمات و خدمات

+

امکانات و خدمات