60 Commits
Author SHA1 Message Date
s.esmaeili 81987ceae8 admin panel beter ui 2026-09-19 12:05:59 +03:30
s.esmaeili 80ccf1a2aa feat build 2026-09-18 19:58:29 +03:30
s.esmaeili e8c67bead0 fix(user-app): rebuild product gallery with fullscreen preview, fix icon reactivity bug
- Extract product image gallery into a dedicated GallerySlider component
  with swipe/tap support
- Add a teleported fullscreen image preview (tap to open, thumbnail strip,
  body scroll lock) to SingleProduct
- Restore the Trust section that was accidentally commented out
- Swap back/favorite button positions and recenter the image counter
- Fix Icon.vue: tempIcon was a static ref set once at setup, so it never
  updated on prop change when Vue reused the component instance (e.g. cart
  qty steppers toggling trash/minus icons); switched to computed + watch
- Rename brand text from "منتزر" to "هنزاگلد"
- Fix a CSS typo in user-app-v2 SingleProduct (rgba alpha value)
- Rebuild dist assets
2026-09-18 19:54:26 +03:30
mahmood 062fe80a68 feat build 2026-09-18 17:30:32 +03:30
mahmood ffd952d754 Merge remote-tracking branch 'origin/main' 2026-09-18 17:16:44 +03:30
mahmood 7a2b8b3109 fix add new version of user in app 2026-09-18 17:16:36 +03:30
s.esmaeili b80fa9bcfb Redesign product detail page and fix icon/cart reactivity bugs
- SingleProduct.vue: rebuild gallery/header/action bar as a self-scrolling
  layout with a sticky photo, solid-on-scroll header matching PageBar, and
  a full-screen image preview (blurred overlay, centered thumbnail strip)
- Add GallerySlider.vue: pointer-based swipe carousel isolated in an
  LTR subtree, fixing RTL/transform sign mismatch that caused slide jumps
- Fix Icon.vue: icon was read into a plain ref once at setup and never
  updated on prop change; switch to computed so icon swaps (e.g. cart
  qty stepper trash/minus toggle) actually re-render
- Fix quantity stepper DOM order in SingleProduct.vue/CartItem.vue so
  plus/minus render correctly under RTL flex
- prisma/assign-downloaded-images.js: seed real product photos (FEATURED/
  FEATURED_MOBILE/GALLERY/GALLERY_MOBILE) onto the 5 placeholder products
2026-09-18 16:22:31 +03:30
s.esmaeili 53a304b5d5 SearchView: Removed the dynamic collection-tile grid (e.g. "پرفروش‌های این هفته") and the "نتایج / N محصول" section header from the UI. Underlying data/filtering logic untouched.
DashboardView ("میز کار"): Rebuilt from scratch with a new structure, distinct from the old card+grid layout but visually aligned with HomeView (same blurred bar, @ink/@wine palette, staged reveal animation on mount):

Single hero card (ink→wine gradient) showing balance, gold/fiat debt, and identity — no quick-action icons inside it anymore.

Live gold rate folded into a slim inline row at the bottom instead of a titled section.
Iterated shadow/spacing on the hero card per feedback (softer, less clipped).
DialogVant / DialogVantOneButton (shared modal components, used in 7+ call sites: Dashboard, Cart, SingleProduct, Register, App): Replaced Vant's default dialog chrome with the app's own rounded-card language — circular icon badge for the illustration, pill-shaped primary/secondary buttons. No call-site markup changed.

LoginView: Rebuilt from scratch — full-bleed dark hero (brand + step copy) overlapped by a floating white form sheet, animated crossfade transition between the mobile-number and OTP steps, same staged entrance animation as Home/Dashboard. All auth logic (API check, OTP stub, session login) unchanged.
2026-09-18 12:54:45 +03:30
Montech 05fe780c89 Merge branch 'dev'
# Conflicts:
#	apps/admin-panel/dist/index.html
#	apps/admin-panel/nginx.conf
#	apps/admin-panel/vite.config.ts
#	apps/api/release/app.js
2026-09-16 15:45:23 +03:30
mahmoodandCursor e51a068039 chore: include full admin-panel dist assets for Dokploy copy-only deploy
index.html was already in git pointing at hashed bundles, but the assets folder was missing from the image so nginx 404ed every JS/CSS request.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 12:10:55 +03:30
mahmood 4743cfb89f fix admin and montzar 2026-09-16 11:59:35 +03:30
mahmood 262122cb3a fix build 2026-09-16 10:44:58 +03:30
s.esmaeili 2c48faf30d Merge branch 'dev' of https://get.feep.ir/s.esmaeili/gold-bank into dev
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2026-09-16 01:54:22 +03:30
s.esmaeili e756fbaf85 Redesign orders/cart/search flows and fix RTL/auth edge cases in user-app
- Fix silent redirect-to-home for unauthenticated users on protected routes;
  now redirects to login with a return path
- Restyle OrderCard, SearchInput, Filters, DropDown, and product card image
  sizing/centering to match the app's LESS-token design language
- Fix ant-design Slider handle placement/drag under RTL and seed a valid
  default range so handles actually render
- Fix chip-row edge padding and top spacing across Orders/Cart/Search lists
- Add pull-to-refresh to the dashboard and fix its header/padding to match
  the fixed PageBar pattern
- Remove the floating WhatsApp button from index.html
2026-09-16 01:15:16 +03:30
mahmood 2448627e1b fix: stop drift migration from dropping tracking table 2026-09-15 22:40:00 +03:30
mahmood 1f734bedca FIx some for nginx 2026-09-15 22:30:39 +03:30
Montech 341c7a5095 fix some for db 2026-09-15 17:25:12 +03:30
Montech 938b1c0b76 Fix some for admin 2026-09-15 17:06:27 +03:30
Montech a7e13fe64e fix update build 2026-09-15 15:41:47 +03:30
s.esmaeiliandClaude Opus 5 0e5f9ccd7b user-app: extend the redesign across the account screens
Phase 2, completing the app-wide rollout of the home/product-detail design
language (LESS colour tokens, BEM class names, hairline-bordered cards,
SectionHead, dashed empty states) onto the account side: dashboard, orders,
transactions, profile, login and register.

Reuse over re-implementation: the dashboard's live rate block is now
components/home/RateStrip.vue rather than a second hand-built strip, and
every screen uses components/ui/PageBar.vue for its chrome.

OrderCard and OrderItemCard are restyled to match. OrderItemCard drops its
always-empty note row: our OrderItem schema has no note field (legacy's
per-item note was a real column there; ours is not), so it could only ever
print a dash.

OrdersView swaps ant Tabs for the same scrolling chip rail the rest of the
redesign uses, and its full-screen Modal with hand-written CSS overrides for
a bottom Drawer, matching how every other detail surface opens.

Deleted AppHeader.vue, orphaned once every screen moved to PageBar. Its
shutter animation was driven by transitionFlag, which nothing reads any
more — the router afterEach comment claiming otherwise is corrected rather
than left asserting something untrue, and the now-inert half is flagged in
place. ScrollFetchSearch loses its isFav prop: it gated two empty-state
branches that rendered the identical image, and its only caller
(FavoritesView) now renders its own.

Behaviour is preserved throughout. Orders still reads GET
/customers/:id/orders and filters client-side; transactions still reads
GET /customers/:id/ledger-entries with legacy's CREDIT/DEBIT colour meaning
intact; profile stays read-only over real fields; login keeps the real
customer lookup plus the documented OTP stub; register stays stubbed with
copy that does not claim an account was created.

Verified in a real browser against the running API and native database:
dashboard 4 tiles with a real balance, orders 1 real order card, profile 4
real spec rows, transactions the correct empty state for a customer with no
ledger rows, and login/register confirmed with the session cleared (the
guard bounces a signed-in user away from /login, which is why they needed a
separate pass). No console errors, no horizontal overflow. vue-tsc and vite
build both clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 01:34:30 +03:30
s.esmaeiliandClaude Opus 5 cc62975dd0 user-app: extend the redesign across the purchase path
Applies the home/product-detail design language (LESS colour tokens, BEM
class names, hairline-bordered cards, SectionHead, dashed empty states) to
the screens a shopper actually walks through: search, collection, newest,
cart and favorites.

Two shared pieces are extracted so the language stays consistent rather than
re-typed per screen:
  - components/ui/PageBar.vue — the sticky translucent bar with blur and a
    hairline, lifted from the home screen's own header so every other screen
    gets identical chrome instead of the old dark AppHeader.
  - components/product/ProductCard.vue — the catalogue workhorse card. It is
    deliberately a sibling of home/ShowcaseCard (which keeps its editorial "wide"
    variant and badge) and additionally surfaces stock, which the
    catalogue needs and the home screen does not.

Deleted as orphaned once nothing imported them: ProductList,
ProductVerticalFull, ProductVertical, ProductHorizontal, CollectionCard —
all Tailwind utility soup carried over from the legacy PWA.

Behaviour is preserved throughout; only chrome and cards change. The cart
still posts to the real POST /orders with wagePercent from each item's
pinned variant, still gates on a real session, and still keeps the note
drawer and success step. Search keeps its client-side filtering, the Filters
sheet, category chips and collection tiles. No rial price is printed
anywhere, for the same reason as the rest of the redesign: a finished
piece's price is the live gold rate x weight plus wage, computed at order
time and never stored on the product.

FIXED a regression this redesign introduced before it shipped: dropping
ScrollFetchSearch from the search grid meant all 205 catalogue products
mounted at once (205 cards, 205 images). Both search and newest now render
a 20-item window that grows as the user reaches the end.

Verified in a real browser against the running API and native database, not
just a green build: home 13 cards, search 20 -> 40 -> 60 -> 80 on scroll,
newest 20 -> 40 -> 60, cart empty state, favorites 3 cards behind a seeded
session. All images loaded (0 broken), no horizontal overflow, no console
errors beyond pre-existing vue3-lazyload noise that predates this work.
vue-tsc and vite build both clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 01:01:19 +03:30
s.esmaeiliandClaude Opus 5 f2d83c53df user-app: redesigned home screen and product detail
Design language introduced here and reused across both screens:
  - LESS colour tokens (@ink/@inkSoft/@inkMuted/@inkFaint, @wine/@wineSoft,
    @canvas/@canvasSoft, @hairline/@hairlineSoft) added to
    themeConfig/colors.json and injected globally via vite's modifyVars, so
    scoped <style lang="less"> blocks resolve them with no import
  - BEM-ish scoped class names, hairline-bordered cards at 14-18px radii,
    ink surfaces with a wine accent, dashed-border empty states
  - components/home/: SectionHead, ShowcaseCard, ShowcaseSlider, CategoryRail,
    RateStrip, AccountStrip

Data honesty is deliberate throughout: no rial price is printed for a piece,
because a finished item's price is the live gold rate x weight plus wage,
computed at order time and never stored on the product. Only genuinely
present fields are shown (weight, making-charge percent, stock, category,
brand). Categories and the hero are derived from the real catalogue, so an
empty catalogue renders an empty state rather than placeholder tiles.

Also fixes a real bug in the product detail view: it ran, at module scope,
history.pushState + window.onpopstate = () => history.go(1). Because the
drawer mounts its slot eagerly, that fired for every product card on the
home screen and permanently hijacked the back button for the session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:05:29 +03:30
s.esmaeiliandClaude Opus 5 a12fb0450e api: don't replay the baseline migration on pre-fix databases
The folder-discovery fix in the previous commit surfaced a second problem.
Databases migrated BEFORE that fix recorded the baseline under the name of
the hand-added flat duplicate that used to sit beside it
(prisma/migrations/0001_init.sql). Once the runner started discovering the
real folder (20260910113720_first), it saw a name it had never recorded and
tried to apply the baseline again — against a database that already had it:

  Raw query failed. Code: 42710.
  ERROR: type "AdminRole" already exists

The two files are identical SQL (verified byte-for-byte after normalising
line endings; they differ only by one trailing blank line), so the recorded
legacy name genuinely means that migration is applied. A LEGACY_NAME_ALIASES
map now treats '0001_init.sql' as an already-applied marker for
'20260910113720_first', so upgrading an existing database is a no-op instead
of a crash.

Verified against the local dev database this actually broke on
(postgresql://localhost:5432/gold_banking, which had recorded only the old
flat name): 'npm run dev' in apps/api now logs 'No pending migrations' and
'Nest application successfully started' instead of dying. That database's
tracking table was also reconciled to record both real migration names, and
it was confirmed to already contain both migrations' schema (AdminRole,
City.percent, user_type, CustomerKind all present) — nothing was re-run
against it. The dockerised database is unaffected: it already records the
real names, so the alias is inert there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 21:44:14 +03:30
s.esmaeiliandClaude Sonnet 5 2b6795d007 api: fix migration runner, serve uploads correctly, add product photos
Three real bugs found and fixed while wiring up product images end-to-end:

1. src/prisma/migrate.ts only discovered flat prisma/migrations/<name>.sql
   files, never Prisma's own folder-per-migration layout
   (<timestamp>_<name>/migration.sql). Every migration created the normal
   way (via 'prisma migrate dev') was silently skipped; only a hand-added
   flat duplicate (prisma/migrations/0001_init.sql, now removed) ever ran.
   This meant schema.prisma had drifted well ahead of what any real deploy
   database actually had (missing columns on City, Customer, Branch,
   Product, several new tables). Fixed to discover both layouts; the
   drift is closed with a new 20260914165255_sync_schema_drift migration.

2. Neither nginx.conf (production) nor vite.config.ts (dev) proxied
   /uploads/ to the API — only /api/. UploadedFile/ProductImage URLs
   (/uploads/<file>, served by main.ts's useStaticAssets OUTSIDE the /api
   prefix) 404'd through both. Both now proxy /uploads/ the same way /api/
   already was.

3. docker-compose.yml's api service had no volume for storage/uploads, so
   every uploaded file — and by extension every ProductImage row pointing
   at one — was lost on the next container recreate/redeploy. Added a
   named volume.

Also: main.ts's static file serving now sets Content-Type: image/avif
explicitly (serve-static's bundled mime table predates .avif and served it
as application/octet-stream; browsers rendered it anyway by sniffing, but
a stricter client would not have).

Product photos: copied 15 real jewelry photos into the uploads volume,
registered them as UploadedFile rows, and assigned each of the 5 existing
Product rows a randomly picked one (duplicates allowed, per request),
replacing their /img/no-items.png placeholder ProductImage row. Verified
end-to-end in a real browser against the real containerized API+DB: logged
in, opened the admin product list, confirmed all 5 rows render their photo
(naturalWidth/Height populated, network 200s, no console errors).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 20:56:17 +03:30
s.esmaeiliandClaude Sonnet 5 a2ab9ba192 admin-panel: drop six more redundant add entries from the sidebar
Each now has an add button at the top of its own list page, so the nav
entry duplicated it:
  - 'افزودن دسته بندی'          (/category/add)
  - 'وارد کردن سند مالی مشتری'  (/add/findoc)
  - 'تعریف تخفیف  تک'           (/major/promotion/tak/add)
  - 'تعریف تخفیف  عمده'         (/major/promotion/add)
  - 'افزودن فاکتور'             (/invoice/add)
  - 'افزودن شهر'                (/city/create)

Only adminMenu is touched; all six routes still exist and still work as a
direct-link fallback. Parent submenus keep their remaining children
('تخفیف اتوماتیک' and 'لیست تخفیفات ' both still sit under 'تخفیفات', for
instance).

Verified in a real browser: menu items 26 -> 20, none of the six labels
render, all five parent submenus (دسته بندی ها، اسناد مالی، تخفیفات، فاکتور
انتقال، شهر ها) still present with their list children, icons unchanged at
16, no console errors. Committed dist/ artifact rebuilt to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 19:43:40 +03:30
s.esmaeiliandClaude Opus 5 d15129168a admin-panel: drop the four redundant add entries from the sidebar
Each of these now has an add button at the top of its own list page, so the
nav entry duplicated it:
  - 'اضافه کردن شخص'  (/customer/add)
  - 'افزودن شعب'      (/branch/add)
  - 'افزودن کارگاه'    (/workshop/add)
  - 'افزودن مدیر'      (/user/create)

Only the adminMenu tree is touched; branchMenu keeps its own /customer/add
entry, since the branch build has no converted add-modal. All four routes
still exist and still work as a direct-link fallback, and the parent
submenus keep their remaining children (including 'دسترسی ها' under مدیران).

Verified in a real browser: menu items 30 -> 26, none of the four labels
render, all four parent submenus still present with their list children,
icons unchanged at 16, no console errors. Committed dist/ artifact rebuilt
to match (force-added, as it is gitignored but deliberately tracked for
Dokploy).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:45:30 +03:30
s.esmaeiliandClaude Opus 5 4b038c0518 admin-panel: rebuild the committed deploy artifact from current source
dist/ is gitignored by apps/admin-panel/.gitignore but is deliberately
committed for Dokploy copy-only deploys (see root .gitignore's note), so it
is force-added. The previous artifact was a pre-port prototype build; the
previous commit removed its two stale assets without being able to add the
new ones, leaving index.html pointing at files absent from git. This restores
a self-consistent artifact built from current source.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:35:13 +03:30
s.esmaeiliandClaude Opus 5 9b6b54dc71 admin-panel: build into the folder the Docker image actually serves
The Dockerfile does `COPY dist /usr/share/nginx/html` and nginx serves that
folder's root, but vite's outDir was 'dist/export' (carried over from legacy's
vue.config.js). So the deployed root kept serving whatever index.html had been
committed at dist/ — a stale pre-port prototype build (lang="en", title
"Gold Bank — Admin Panel", no ant-design-vue at all, committed 2026-09-10 in
cea3b80). That artifact, not the current source, renders as an unstyled stack
of bullets and bare tables.

outDir is now 'dist' (and 'branchDist'), matching what the image serves and
matching apps/user-app's convention.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:30:04 +03:30
s.esmaeiliandClaude Opus 5 acc41c21d9 admin-panel: import ant-design-vue 4 reset.css (fixes unstyled page)
The ADV4 upgrade dropped the old `ant-design-vue/dist/antd.less` import but
never added its replacement. ADV4 injects each COMPONENT's styles at runtime
via CSS-in-JS, but NOT the global baseline — that ships separately as
dist/reset.css and must be imported explicitly. Without it the page lost
box-sizing, the body margin reset and the browser default list/heading
resets, so the layout rendered unstyled.

Verified in a real headless browser this time, not just a green build:
reset.css now resolves through LESS and its rules are present in the built
CSS, the CSS-in-JS engine injects component styles (14 <style> tags on the
login page), and buttons/inputs/cards compute real ant metrics with dir=rtl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 17:22:46 +03:30
s.esmaeiliandClaude Sonnet 5 0e1dd0c36a admin-panel: convert wholesale-customer add form into a modal
bulkCustomer.vue (reached today only via /customer/add's AddCustomer.vue tab
wrapper, and via /bulkCustomer/edit/:id from 5 different list variants) gets
open/id modal support; both routes stay as a fallback.

OmdeCustomerList.vue is shared by 5 list pages (OmdeList, BankerList,
ShopkeeperList, OmdeNonVerifiedList, OmdeArchivedList). Per user decision, the
add button and modal-based edit are wired only on OmdeList.vue (the one this
form conceptually belongs to) via a new showAddButton prop, default false —
the other four keep navigating to the edit route unchanged.

singleCustomer.vue and ReagentCustomer.vue were also planned as add-form
candidates but have no create entry point anywhere in the app (edit-only,
reachable solely via /singleCustomer/edit/:id and /reagent/edit/:id) — left
untouched, nothing to convert. offersIgnore-add-edit.vue is a standalone
settings page with no list counterpart, not a create form — left as a page.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 17:05:42 +03:30
s.esmaeiliandClaude Sonnet 5 1bb9802d1f admin-panel: convert promotion add/edit forms into modals
PromotionForm.vue (shared by the omde and tak variants) gains open/id modal
support; both thin wrappers (promotion_add_edit.vue, promotion_tak_add_edit.vue)
pass the props/emits through unchanged. promotion_list.vue gets two
ListPageHeader buttons (offers.create / offers.create.tak, matching the two
routes' rids) and picks which variant's modal to open based on the edited
row's type_customer, exactly as the old toEdit() picked which route to
navigate to.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 16:56:30 +03:30
s.esmaeiliandClaude Sonnet 5 3345c86d18 admin-panel: convert financial document add/edit form into a modal
Same open/id pattern as the previous conversions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 16:53:02 +03:30
s.esmaeiliandClaude Sonnet 5 3e24fe3fc7 admin-panel: convert invoice add/edit form into a modal
Same open/id pattern, kept at width=xl since it embeds its own product-picker
XTable plus a running cart table. Also deduped ListInvoice's confirmDelete
onto the shared useDeleteHandler composable (missed in the earlier delete-
handler pass since it wasn't byte-identical to the other 6 — it used
message.success + a different fallback string, both preserved).

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 16:51:16 +03:30
s.esmaeiliandClaude Sonnet 5 a8988c18a2 admin-panel: convert user and category add/edit forms into modals
Same pattern as branch/workshop/city: open/id props switch UserCreate.vue and
apps/category/Add.vue into BaseFormModal mode; the /user/create,
/user/edit/:id and /category/add/:id? routes are unchanged as a fallback.
ListUser.vue's pre-existing add button (previously a router.push, added
outside the original port and left there per its own comment) now opens the
modal via ListPageHeader instead.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 16:49:24 +03:30
s.esmaeiliandClaude Sonnet 5 1ac3b03ad5 admin-panel: convert branch and workshop add/edit forms into modals
Both share FacilityList.vue, which now accepts an addRid/addLabel pair and a
#form slot: when addRid is supplied, edit opens the slot's modal instead of
navigating, and a ListPageHeader add button appears above the list. Branch
and workshop each plug their own AddBranches/AddWorkshop instance into that
slot, keyed by editingId so switching rows remounts the form (fresh
onMounted load) rather than needing new watch-based reactivity.

The /branch/add, /branch/edit/:id, /workshop/add, /workshop/edit/:id routes
are unchanged and still work as a route-based fallback.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 16:46:14 +03:30
s.esmaeiliandClaude Sonnet 5 12c8195090 admin-panel: convert city create form into a modal (pilot)
Establishes the pattern the remaining 15 add/edit forms will follow:
- The form component accepts open?/id? props. When open is defined it's
  used as-is; asModal is fixed at mount (not reactive) since the parent
  decides once whether this instance is a modal or a route page.
- As a modal it renders through BaseFormModal (header=title, footer=
  buttons, body=form) and emits success + update:open instead of
  navigating; as a route page (the old /city/create still exists and still
  works) it falls back to the original <a-card> + button layout unchanged.
- The list page gets a ListPageHeader add button above the list, gated on
  the exact rid the route used to gate on (cities.create), opening the
  modal instead of navigating.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 15:22:54 +03:30
s.esmaeiliandClaude Sonnet 5 a6d8993d01 admin-panel: render sidebar icons, fix openKeys, persist collapse state
- VerticalNavMenu.vue now renders the icon: values that navMenuItems.ts
  already carried on 33 nodes but were never displayed, via the shared
  resolveIcon() map. Sub-menus and leaf items get a #icon slot each; ADV4's
  Menu shows icons only (labels hidden) once :inline-collapsed is true, which
  is now wired to the collapsed prop.
- Fixed openKeys: it previously started at [] and was only ever updated by
  user clicks, so a direct link or page refresh landed with the active
  item's parent submenu collapsed. It's now derived from the current route
  on load and on every navigation.
- Sidebar collapse state moves from a local ref in Main.vue to
  stores/layout.ts's existing (previously unused) sidebarCollapsed, persisted
  to localStorage so it survives a reload.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 15:20:47 +03:30
s.esmaeiliandClaude Sonnet 5 02d92fc465 admin-panel: add shared modal/header/icon/delete-handler components
New src/components/common/:
- BaseFormModal.vue — the standard shell every converted add/edit modal will
  use: header=title, footer=buttons, body=form. Replaces the ad-hoc
  60%/70%/80% <a-modal> widths used across the codebase with 4 named sizes.
- ListPageHeader.vue — the add button that goes above every list page,
  gated on the same rid the create route used (modelled on the one place
  this already existed, ListUser.vue's add-user button).

New src/components/icons.ts: the legacy-Feather-name -> ant-icon map,
extracted out of TooltipBtn.vue so the sidebar can resolve the same names
through the same map instead of a second copy.

New src/composables/useDeleteHandler.ts: the delete-then-refetch-then-toast
handler that was copy-pasted verbatim in 6 list views (city/cityList.vue,
user/ListUser.vue, apps/category/List.vue, finical/list.vue,
apps/product/ProductList.vue, manage/Const.vue). Preserves each site's exact
success-message wording and message.success/info choice, and ProductList's
irregular POST-based delete endpoint.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 15:18:17 +03:30
s.esmaeiliandClaude Sonnet 5 f0de478940 admin-panel: fix CSS bugs and dedupe scoped grid utilities
Approved departure from the port's byte-faithful rule (docs/ADMIN-PORT-PLAN.md
bug rows 4-6), fixed and logged rather than silently changed:
- farsi.less: bold/700 @font-face blocks now load iran-yekan-700.woff2
  instead of the 400 file, so bold text actually renders bold. No source
  file exists for weight 300, so that face is dropped rather than faked.
- format("woff") corrected to format("woff2") on all faces.
- Removed 'font-weight: inherit' from the body,* reset, which was flattening
  every heading's weight.

Also deduped 24 view files that each redeclared .grid-2/.grid-3/.grid-4 in
a scoped <style> block with drifted minmax values, though utilities.less
already defines all three globally. Removed the scoped copies; kept
PromotionForm's genuinely different 0.5rem gap as a small .grid-4--tight
modifier instead of a full redeclaration.

Verified: pnpm build passes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 15:09:11 +03:30
s.esmaeiliandClaude Sonnet 5 7e30701570 admin-panel: upgrade to ant-design-vue 4.2.6
- Bump ant-design-vue 3.2.20 -> 4.2.6, @ant-design/icons-vue 6.1.0 -> 7.0.1.
- ADV4 removed LESS theming (modifyVars) for CSS-in-JS design tokens: drop
  the vite.config.ts modifyVars block and the antd.less import; move the
  three tokens ant actually consumed (primary/link/border-radius) to
  App.vue's <a-config-provider :theme>.
- Delete src/assets/styles/ant.less (426 lines of hand-written RTL flips):
  ADV4 renders RTL natively via CSS logical properties given direction="rtl",
  which App.vue already sets. Preserve only the small residue ADV4 doesn't
  cover natively (upload filename LTR, select dropdown z-index, tabs nav
  direction) in a new rtl-overrides.css.
- Clean up the stale --primary/--accent token comment in utilities.less now
  that ant.less is gone.
- Verified: pnpm build (vue-tsc -b && vite build) passes with zero errors;
  the codebase already used v-model:open everywhere (no ADV3 visible prop
  usage to migrate).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 15:05:05 +03:30
s.esmaeili 8fac6a3f36 merge: integrate origin/main with admin panel and api migrations 2026-09-13 21:05:20 +03:30
s.esmaeili 267fc7b7d1 feat: complete admin panel and backend admin api migration 2026-09-13 21:02:25 +03:30
mahmood f19f49daeb feat: auto-run SQL migrations and one-time seed on boot 2026-09-10 17:15:17 +03:30
mahmood a4c5c9259f Fix some for docker ' 2026-09-10 14:59:25 +03:30
mahmood 1b675804ea fix: nginx dns re-resolve, db healthcheck, explicit bind 2026-09-10 14:51:57 +03:30
mahmood 7d93507089 fix: run API on bookworm with debian Prisma engine 2026-09-10 14:45:47 +03:30
mahmood 88bb7b1fbf fix: use bookworm + debian Prisma engine 2026-09-10 13:58:50 +03:30
mahmood bbbe6a5202 fix: add linux-musl Prisma engines for Alpine 2026-09-10 13:00:05 +03:30
mahmood f0f82d20ec fix: vendor Prisma into release for Docker 2026-09-10 12:53:23 +03:30
mahmood 9e8d4a017d Fix port docker 2026-09-10 12:43:30 +03:30
mahmood cea3b808d8 Fix some for production 2026-09-10 12:17:47 +03:30
mahmood 78e148a6b5 IFx some for eror on build docker 2026-09-10 01:00:00 +03:30
mahmood 3e3cb4b3c0 fix some for mirror 2026-09-10 00:48:06 +03:30
mahmood 56caedb8b2 fix use mirror 2026-09-10 00:25:43 +03:30
mahmood 05a9837695 fix some for build 2026-09-10 00:18:53 +03:30
mahmood c59ca8e1dc Fix some for deploy 2026-09-10 00:13:03 +03:30
mahmood d65c67e440 Fix db external port 2026-09-09 23:31:31 +03:30
s.esmaeili 03119cd655 docs: add comprehensive Docker usage documentation (DOCKER.md)
Includes explanation of multi-stage Turborepo builds, Nginx SPA routing, and docker-compose deployment steps.
2026-09-09 19:49:27 +03:30
s.esmaeili 78a68b3567 chore: add docker configuration 2026-09-09 19:39:13 +03:30
s.esmaeili 0ed6bed028 feat: initial commit 2026-09-09 06:46:27 +03:30
1342 changed files with 30559 additions and 173643 deletions
+3 -151
View File
@@ -1,10 +1,9 @@
# Database configuration
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=hedenagold
POSTGRES_DB=goldbank
# Host port for PostgreSQL. Each copy of this stack on the same machine
# must use a different port (the apps talk to hostname "db", not this port).
# Host port exposed for PostgreSQL (used by docker-compose db service)
EXTERNAL_DB_PORT=5432
# Prisma connection string (inside Docker network use hostname "db")
@@ -20,154 +19,7 @@ DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGR
API_PORT=3000
ADMIN_PANEL_PORT=5180
USER_APP_PORT=5181
USER_APP_V2_PORT=5182
# API Configuration (kept for any tooling that still reads bare PORT)
PORT=3000
# ---------------------------------------------------------------------------
# Authentication
# ---------------------------------------------------------------------------
# Admin JWT signing secret. REQUIRED in any real deployment — without it the
# API falls back to a well-known development string.
JWT_SECRET=
# Admin token lifetime (default 12h).
JWT_EXPIRES_IN=
# Customer JWT signing secret. Deliberately SEPARATE from JWT_SECRET so an
# admin token can never validate as a customer token (T1).
JWT_CUSTOMER_SECRET=
# Customer token lifetime (default 30d).
JWT_CUSTOMER_EXPIRES_IN=
# ---------------------------------------------------------------------------
# Accounting software integration (Tahesab / Zarrin, D27). Everything else —
# which software, address, token, mappings — is set in the admin panel
# («پیکر بندی ← اتصال حسابداری»), and the integration is off until then.
# ---------------------------------------------------------------------------
# Encrypts the accounting token at rest. Set a long random value. Blank falls
# back to a key derived from JWT_SECRET (with a boot warning); changing that
# key later means re-entering the token in the admin panel.
ACCOUNTING_SECRET_KEY=
# "off" keeps this API instance out of the background sync (only needed when
# several instances run and you want exactly one doing it; a lease already
# stops two from running the same job).
ACCOUNTING_WORKER=
# ---------------------------------------------------------------------------
# Central IAM — delegated admin login (docs/iam-sso.md)
# ---------------------------------------------------------------------------
# OFF by default. When off, nothing changes: admin login stays a local bcrypt
# check and no request ever leaves this server.
#
# When on, the admin's phone + password are verified against the central
# identity service instead. There is NO UI change: no SSO button, no redirect.
# If the identity service is unreachable or does not know the account, the
# local password check still runs (see IAM_LOGIN_DELEGATION).
IAM_ENABLED=false
IAM_ISSUER=
# The client must be registered there as confidential AND trusted.
IAM_CLIENT_ID=
IAM_CLIENT_SECRET=
# One of the two; tenant_id wins when both are set.
IAM_TENANT_ID=
IAM_TENANT_SLUG=
# off | fallback (default, safest) | exclusive (local password no longer accepted)
IAM_LOGIN_DELEGATION=fallback
IAM_HTTP_TIMEOUT_MS=8000
# صفحه callback اپ کاربر. باید در کلاینت IAM دقیقاً ثبت شود.
IAM_REDIRECT_URI=
# callbackهای اضافه (دولوپ و دامنه دیگر) با کاما. آدرس ناشناس جایگزین نمی‌شود.
IAM_REDIRECT_URIS=
# بعد از ورود محلی، مرورگر برای کوکی نشست مشترک به اینجا برمی‌گردد.
IAM_BROWSER_RETURN_URL=
# خروج سراسری فقط اگر این آدرس در post_logout کلاینت ثبت شده باشد برمی‌گردد.
IAM_POST_LOGOUT_REDIRECT_URI=
# مقصدهای مجاز خروج اپ کاربر، با کاما. باید عیناً در post_logout کلاینت باشند.
IAM_ALLOWED_RETURN_URLS=
# پنل ادمین. خالی یعنی بررسی بی‌صدای ورود در پنل انجام نمی‌شود.
IAM_ADMIN_REDIRECT_URI=
IAM_ADMIN_REDIRECT_URIS=
IAM_ADMIN_BROWSER_RETURN_URL=
IAM_ADMIN_POST_LOGOUT_REDIRECT_URI=
IAM_ADMIN_ALLOWED_RETURN_URLS=
# Revocable admin sessions (AdminSession table).
#
# An admin token is self-contained and lives for JWT_EXPIRES_IN (12h), so until
# this existed nothing could end a session early — not disabling the account, not
# a central sign-out. Every issued token now carries a `jti` recorded in the
# database, and a revoked row is refused on the next request.
#
# Unset = follows IAM_ENABLED (on with the IAM, so central revocation reaches
# here). `true` turns it on without the IAM; `false` opts out of the per-request
# lookup and central revocation then cannot end a session here.
AUTH_SESSION_TRACKING=
# To let the IAM end sessions here, register this path on the client there as its
# backchannel_logout_uri:
# https://<api-host>/api/iam/backchannel-logout
# ---------------------------------------------------------------------------
# ⚠ DELIVERY ONLY — REMOVE BEFORE THE REAL RELEASE (D20 / ISSUES REL-01)
# ---------------------------------------------------------------------------
# A fixed OTP that signs in ANY active customer, alongside the real code.
# MUST BE EXACTLY 4 DIGITS: the apps' login box has four single-character
# inputs and only submits at four (LoginView.vue:43). A longer code still works
# over the API but cannot be typed into either app.
# Leave it EMPTY or unset to disable it completely — that is the correct state
# for production. While it is set, the API prints a warning banner at boot.
OTP_MASTER_CODE=
# ---------------------------------------------------------------------------
# SMS gateway. The default "relay" driver works with everything blank (D26).
# A failed SMS never stops the server or an operation (D20).
# ---------------------------------------------------------------------------
# "relay" (default), "trez", or "none" to disable explicitly.
SMS_PROVIDER=
# relay — the owner's createsms gateway. All four are optional.
# URL and secret have built-in defaults.
SMS_RELAY_URL=
SMS_RELAY_SECRET=
# The "host" field. Blank sends this machine's first external IPv4 — inside
# Docker that is the container's address.
SMS_RELAY_HOST=
# trez — only used with SMS_PROVIDER=trez. Never commit real values.
# Override only for staging; defaults to the provider's live endpoint.
SMS_TREZ_URL=
SMS_TREZ_USERNAME=
SMS_TREZ_PASSWORD=
SMS_TREZ_SENDER_LINE=
# ---------------------------------------------------------------------------
# Uploads
# ---------------------------------------------------------------------------
# Absolute path for uploaded files; defaults to <api>/storage/uploads.
UPLOAD_DIR=
# ---------------------------------------------------------------------------
# Profile completion ("تکمیل پروفایل")
# ---------------------------------------------------------------------------
# Comma-separated admin mobiles told when a customer uploads their documents.
# The reference hardcoded four numbers in its source
# (henza_server Major/CustomerController::sendSmsToAdmin); they live here
# instead so no personal number is committed. Blank means nobody is notified —
# the upload and the customer's own SMS still go through (D20).
PROFILE_ADMIN_MOBILES=
# ---------------------------------------------------------------------------
# Order placement
# ---------------------------------------------------------------------------
# Comma-separated admin mobiles told when a customer places an order. The
# reference hardcoded four numbers (henza_server
# Api/ApiMobileController::setOrder:740-743). Blank means nobody is notified —
# the order, the customer's SMS and the sales expert's SMS still go through.
ORDER_ADMIN_MOBILES=
# ---------------------------------------------------------------------------
# Brand
# ---------------------------------------------------------------------------
# The name at the top of every SMS («هدنا گلد\n…») and the site link in the
# welcome SMS. Blank = the defaults in apps/api/src/common/brand.ts. The
# frontends take their brand from the repo-root brand.ts instead; the full
# rebrand checklist is docs/BRANDING.md.
BRAND_NAME=
BRAND_SITE_URL=
-11
View File
@@ -6,8 +6,6 @@ node_modules/
*.log
*.tsbuildinfo
.DS_Store
.idea/
.cursor/
apps/api/generated/
apps/api/dist/
apps/api/storage/uploads/*
@@ -17,12 +15,3 @@ apps/api/storage/uploads/*
# apps/api/release/app.js + apps/api/release/vendor/ (Prisma)
# apps/admin-panel/dist/
# apps/user-app/dist/
# Live legacy data dump (customers' personal data). Never commit.
# Only the structure-only oapi-struncture.sql belongs in the repo.
/oapi.sql
# Legacy Henza reference trees — keep locally, never push to Hedena Gold.
/henza_admin/
/henza_pwa/
/henza_server/
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/gold-bank.iml" filepath="$PROJECT_DIR$/.idea/gold-bank.iml" />
</modules>
</component>
</project>
Generated
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>
Generated
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
-427
View File
@@ -1,427 +0,0 @@
# DELIVERY — gold-banking-monorepo
Branch: **`release/delivery`** · Prepared 2026-09-23
Contains everything from `parity/security-hotfix` and `parity/phase-1-admin`.
Both merged with **no conflicts** (`phase-1-admin` already contained
`security-hotfix`, so the merge was a fast-forward). `main` was not touched.
---
## 0. Quick start — run these in order
Copy-paste, top to bottom. Nothing here needs a decision.
### Step 1 — fill in the environment
Create `.env` in the repo root. These **must** have a value:
```dotenv
# Database
POSTGRES_USER=postgres
POSTGRES_PASSWORD=<choose a strong password>
POSTGRES_DB=goldbank
DATABASE_URL=postgresql://postgres:<the same password>@db:5432/goldbank?schema=public
# Auth — two DIFFERENT random values. Generate each with:
# node -e "console.log(require('crypto').randomBytes(48).toString('base64url'))"
JWT_SECRET=<random value 1, 32+ characters>
JWT_CUSTOMER_SECRET=<random value 2, 32+ characters, NOT the same as above>
# Every origin the panel and the two apps are opened from, comma-separated.
CORS_ORIGINS=https://adm.example.ir,https://app.example.ir,https://app2.example.ir
```
The API **refuses to start** in production until all three are right, and
prints every problem at once (REL-01, REL-03, SEC-04 —
docs/SERVER-SETUP-CHECKLIST.md). A random value:
`node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"`.
Inside Docker the database host is `db`, not `localhost`.
Everything else is optional and may be left blank. Two you may want now:
```dotenv
# Delivery only — a fixed OTP that signs in ANY active customer.
# Exactly 4 digits, or it cannot be typed into the apps. REMOVE before the
# real release (see "Before the real release" below). The production image
# refuses to start with it unless the server is marked as a test server:
OTP_MASTER_CODE=1379
TEST_SERVER_ALLOW_MASTER_CODE=yes
# SMS goes through the "relay" gateway with nothing to set (D26).
# SMS_PROVIDER=none turns sending off; the server still starts either way.
SMS_PROVIDER=
```
### Step 2 — build
```bash
pnpm install
pnpm build:release
```
**Do not skip `pnpm build:release`, and never run `docker compose build`
without it first.** The API image copies a bundle built here on the machine; it
does not build inside the image. Skipping this step produces an image from
**old code** and still reports success. This is real — it happened during this
delivery's own test run, with a bundle four days stale. (DEPLOY-03)
### Step 3 — start
```bash
docker compose up -d --build
```
Database migrations and seeding run **automatically inside the API container**
at boot. There is no manual migration step. Never run `prisma migrate reset`.
### Step 4 — check it came up
```bash
docker compose ps # db healthy, api up
docker compose logs api | tail -40
```
In the log you should see `Nest application successfully started`, and
`Applied migrations: …` (first run) or `No pending migrations` (later runs).
An `OTP_MASTER_CODE IS ACTIVE` banner is **expected** while that variable is
set — it is a reminder, not an error, and it disappears once you remove the
variable before the real release.
A `JWT_CUSTOMER_SECRET IS NOT SET` banner means **Step 1 was not done**. That
one is a real security problem, not a warning to ignore — go back and set it.
### Step 5 — prove the whole flow works
```bash
# Publishes the API on host port 3100 so the test can reach it.
docker compose -f docker-compose.yml -f docker-compose.delivery-test.yml up -d api
node apps/api/scripts/test-compose-e2e.cjs http://localhost:3100/api <OTP_MASTER_CODE>
```
Expect **32 passed, 0 failed**. It signs a sales expert and an ordinary
customer into the app, has both place an order, confirms both orders appear in
the admin panel, and re-checks the token boundaries — all against the real
container.
`docker-compose.delivery-test.yml` exists **only** for that check. The normal
deployment uses `docker-compose.yml` alone, where the API has no host port and
Dokploy/Traefik routes to it by domain.
---
## ⚠ Before the real release
Tonight's build is a **delivery build**. These are the things that must change
before it is a production release. The first three are blocking.
### A. Configuration — blocking
| # | Do this | Why it matters |
|---|---|---|
| **1** | **Set `JWT_CUSTOMER_SECRET` to its own random value**, different from `JWT_SECRET`. | Not a cosmetic default. Unset, the API signs customer and sales-expert **app** tokens with `JWT_SECRET` — *the same key admin tokens use* — and the two token realms stop being cryptographically separate. The only remaining barrier is the `typ` check in `jwt.strategy.ts`, and a sales expert's app token carries their **AdminUser id** in `sub`. Two independent defences collapse into one. A loud boot banner fires while it is missing. **REL-03 (Critical).** |
| **2** | **Remove `OTP_MASTER_CODE` and `TEST_SERVER_ALLOW_MASTER_CODE` from the environment.** The API now refuses to start with the code set unless that flag says this is a test server. | While set, **any active customer signs in with that one fixed code**. Unset or empty disables it completely; the API prints a banner at boot for as long as it is armed. **REL-01 (Critical).** |
| **3** | **Set real `JWT_SECRET` and `CORS_ORIGINS`.** The API refuses to start without them. | Without the key anyone could forge an admin token; without the origin list any website could call the API from a visitor's browser. |
| **4** | **Confirm SMS is going out.** The default `relay` gateway needs no credentials (D26); check `docker compose logs api` for `SMS provider: relay` and send yourself a login code. | If the gateway refuses the `host` value (inside Docker it is the container's IP), set `SMS_RELAY_HOST`. A failed send is only a log line — the server still boots (D20). **REL-02.** |
| **5** | **Accounting integration (D27), only if it will be switched on:** set `ACCOUNTING_SECRET_KEY`; count products with no image (they disappear from the app — query in ISSUES ACC-05) and photograph them first; for Zarrin, run the trial on temporary documents first (ACC-01). | The integration itself is off until an admin turns it on in «اتصال حسابداری»; nothing changes before that except the no-image rule, which applies to every product. |
After changing any of these: `docker compose up -d --build` and confirm the
`OTP_MASTER_CODE` banner is **gone** from `docker compose logs api`.
### B. Credentials to rotate — the reference leaked them
These are committed in plaintext in `henza_server` and must be assumed
compromised by anyone who has ever had repo access. None were copied into this
repository, but they are **the same live accounts**.
- **SMS panel** username + password + sender line (`app/Models/Sms.php:73-77`) — **SEC-12 (Critical)**
- PayPing bearer token, Tahesab bearer token + IP allowlist, wallet hash pepper — **SEC-06**
### C. Parity work not finished
Phase 1 stopped after the Persons section when delivery mode began (D19). The
**Phase 1 gate still stands** as the next milestone.
**Admin — not yet audited field-by-field.** Each has had only the breakage scan
(`scan-admin-b2b.cjs`, 34 endpoints, 0 broken), not a parity audit:
- **Financial documents** (اسناد) — the money path; highest priority of the three
- **Users & permissions** (کاربران و دسترسی‌ها) — includes **PERM-01**, still
open: the database holds **178** distinct permission ids against the UI's
**174**. Unresolved because fixing it means **deleting rows**, which needs
your approval.
- **Reports** (گزارش‌ها)
- Also untouched: products/categories/variants, SMS templates, promotions,
cities, branches, workshops.
**Apps — known gaps.**
| ID | What |
|---|---|
| **MON-08** | `wagePercent`, `discountPercent` and `flatRialCharge` are taken from the **client** on order creation, so a caller can set their own making charge. `rashWeightGrams` is correctly read from the variant. The reference is *worse* (it trusts client `rash_weight` too), so this is already a partial improvement — but it is still a money path trusting client input. **Fix this first in Phase 2.** |
| **STUB-05** | Admin SMS recipients are hardcoded mobile numbers scattered across six places in the reference. Port as `SMS_ADMIN_RECIPIENTS` (comma-separated), the way `PROFILE_ADMIN_MOBILES` already works — not by copying the literals. Staff changes should not need a deploy. |
| **STUB-04** | Live gold price is **pull-on-mount only** — no socket, no polling. `quotedAt` renders as a Jalali **date**, so a stale rate looks current. |
| **STUB-03** | All list filtering and paging is **client-side** over one flat `GET /catalog/products`; no server `page`/`limit`. |
| **Analytics** | The reference PWA calls `useCustomAnalytics` on every route change (`henza_pwa/src/router/index.ts:249`); neither target app has any analytics. Deliberate — it needs a provider decision from you, not a port. |
**`docs/parity/ISSUES.md`** holds the full list with evidence and file
references — currently **40+ entries**, including everything above plus the
reference bugs deliberately not ported (SEC-14, SEC-16, APP-03, and the two
D21 items). Phase 3 is the pass over that file.
---
## 1. Environment variables
Names only — no values here, and none are committed. The full annotated list is
in `.env.example`; `docker-compose.yml` passes each one through to the API
container.
### Required
| Variable | Purpose |
|---|---|
| `DATABASE_URL` | PostgreSQL connection string. Inside Docker the host is `db`. |
| `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` | Credentials for the bundled `db` service. |
| `JWT_SECRET` | Signs **admin** tokens. Without it the API falls back to a well-known development string — set it. |
| `JWT_CUSTOMER_SECRET` | Signs **customer and sales-expert app** tokens. Must be its own value — see the warning at the top of this file. Unset, it falls back to `JWT_SECRET` and the two token realms merge. |
### Optional
| Variable | Default | Purpose |
|---|---|---|
| `PORT` / `API_PORT` | `3000` | API listen port. |
| `JWT_EXPIRES_IN` | `12h` | Admin token lifetime. |
| `JWT_CUSTOMER_EXPIRES_IN` | `30d` | Customer token lifetime. |
| `UPLOAD_DIR` | `<api>/storage/uploads` | Where uploaded files are written. |
| `ADMIN_PANEL_PORT`, `USER_APP_PORT`, `USER_APP_V2_PORT` | 5180–5182 | Dev servers only. |
| `RUN_MIGRATIONS` | `true` | Set `false` to skip the boot-time migration run. |
| `RUN_SEED` | `true` | Set `false` to skip seeding. |
| `ACCOUNTING_SECRET_KEY` | *(derived from `JWT_SECRET`)* | Encrypts the accounting-software token the admin enters (D27). Set its own long random value; changing it later means re-entering the token. |
| `ACCOUNTING_WORKER` | `on` | `off` keeps this instance out of the accounting background sync. |
| `PROFILE_ADMIN_MOBILES` | *(blank)* | Comma-separated admin mobiles told when a customer completes their profile. The reference hardcoded four numbers; blank means nobody is notified and the upload still succeeds. |
### SMS — all optional
`SMS_PROVIDER`: `relay` (default), `trez`, or `none` (D26).
- **relay**: `SMS_RELAY_URL`, `SMS_RELAY_SECRET` (both have built-in
defaults), `SMS_RELAY_HOST` (blank = the machine's first external IPv4).
No `platform` is sent; the gateway picks the line.
- **trez**: `SMS_TREZ_URL`, `SMS_TREZ_USERNAME`, `SMS_TREZ_PASSWORD`,
`SMS_TREZ_SENDER_LINE`.
The server **always starts**; a misconfigured provider is a warning, and a
failed or skipped SMS never blocks an operation, including customer creation.
### ⚠ `OTP_MASTER_CODE` — delivery only
A fixed OTP accepted for **any active customer**, alongside the real code.
- Read only from the environment; never hardcoded.
- Unset or empty → completely disabled.
- **Must be exactly 4 digits.** Both apps' login box is four single-character
inputs and only submits at four, so a longer code works over the API but
cannot be typed into either app.
- Every login through it is logged with the customer's mobile.
- The real OTP keeps working unchanged while it is armed.
---
## 2. Running it
### Development
```bash
pnpm install
pnpm dev # all apps via turbo
```
Ports come from the repo-root `.env` (single source of truth):
API 3000, admin-panel 5180, user-app 5181, user-app-v2 5182.
### Build
```bash
pnpm build # all workspaces
pnpm build:release # the production/Docker build
```
### Deploy (Docker)
```bash
pnpm build:release # ⚠ REQUIRED FIRST — see below
docker compose up -d --build
```
> **⚠ `pnpm build:release` must be run before `docker compose build`.**
> The API image is `COPY release/ ./` — it ships a bundle built on the
> developer machine, not one built inside the image. So `docker compose build`
> will happily produce an image from a **stale** `release/app.js` and report
> success. During this delivery's compose run that bundle was four days old and
> the container was missing every route added since. Tracked as **DEPLOY-03**.
Services: `db` (Postgres 15), `api` (port 3000), `admin-panel`, `user-app`,
`user-app-v2` (each nginx on port 80). The apps expose no host ports —
Dokploy/Traefik routes to them by domain. Uploads persist in the `api_uploads`
volume; without it every previously uploaded image 404s after a redeploy.
---
## 3. How migrations run
**Automatically, inside the API container, at boot.** No manual step.
`src/prisma/bootstrap.ts` calls `runMigrations()` before Nest starts. That
runner (`src/prisma/migrate.ts`) applies the `.sql` files from
`prisma/migrations` and records each one in its own tracking table,
**`_goldbank_migrations`**, so every file runs exactly once.
It exists because **the deploy image is copy-only and ships no Prisma CLI**, so
`prisma migrate deploy` is not available there. This is why the database has two
migration ledgers — `_prisma_migrations` (written by the Prisma CLI in
development) and `_goldbank_migrations` (written by the runtime runner). Both
are intentional. `_goldbank_migrations` is declared in `schema.prisma` with
`@@ignore` so drift checks do not propose dropping it.
**Never** run `prisma migrate reset`, and never edit either table by hand.
In development, migrations are created with `prisma migrate dev`; in production
the container runner applies them.
Seeding runs once after migrations, gated on a marker row, and is skipped when
no seed script is present.
---
## 4. What was verified on this branch
All green on `release/delivery`:
| Suite | Result |
|---|---|
| `pnpm build` (5 workspaces) | ✅ all pass |
| jest (api unit) | **54/54** |
| `smoke-admin.cjs` — admin flows | **43/43** |
| `smoke-apps.cjs` — customer flows, both apps | **43/43** |
| `scan-admin-b2b.cjs` — every B2B read endpoint | **34 ok, 0 broken** |
| `smoke-admin-b2b-writes.cjs` — order + document writes | **25/25** |
| `auth-smoke.sh` — permissions/401/403 | **32/32** |
| `test-sales-expert-roundtrip.cjs` | **33/33** |
| `test-sales-expert-app-login.cjs` — expert app login (D21) | **36/36** |
| `test-complete-profile.cjs` — «تکمیل پروفایل» | **38/38** |
| `test-report.cjs` — «گزارش» | **40/40** |
| `test-compose-e2e.cjs` — **the whole flow inside Docker Compose** | **32/32** |
| `test-name-trigger.cjs` | **9/9** |
| typecheck (api, admin-panel, user-app, user-app-v2) | ✅ clean |
### Verified inside Docker Compose, not just locally
The whole delivery flow was run against the **built container**, which is what
actually ships: `test-compose-e2e.cjs` signs a **sales expert** and an
**ordinary customer** into the app with the master code, has **both place an
order**, and confirms **both orders appear in the admin panel** and open there.
It also re-checks the three token boundaries in the deployed image. **32/32.**
The three page suites were re-run against the container too — complete-profile
38/38, report 40/40, expert login 36/36.
That run found two deployment problems that no local test could have caught:
- **DEPLOY-02 (fixed):** admin login returned **500 for every user in Docker**.
Compose passes optional variables as `${X:-}`, so an unconfigured one arrives
as `""`, not `undefined`; the code used `?? 'default'`, which only falls back
on `undefined`, so the empty string reached `jsonwebtoken` and it threw on
every sign. Fixed with `src/common/env.ts`, applied to every auth, port,
upload and SMS read, and pinned by a test that reproduces the original throw.
- **DEPLOY-03 (process):** the API image ships a **pre-built** bundle, so
`docker compose build` can silently produce an image from stale code. See the
warning in the deploy section above.
Flows walked end to end: admin login, customer creation, sales-expert
create/edit, product and variant create/edit, order list/detail/status change,
wholesale order placement from the admin panel, cash document creation +
confirm + un-confirm; and in **both** customer apps: OTP login with the master
code, product browsing, cart, order placement, order history.
Money paths were checked against the reference's own formulas, not just for a
200: making charge `rash × (1 + (wage − discount)/100)` reproduced exactly, and
the cash-document conversion `price / (price_per_gram / 4.331802)` moved the
customer's gold debt by the expected amount and restored it exactly on
un-confirm.
Every test script deletes only the rows it created; the write test also asserts
the customer's balances end exactly as they started.
---
## 5. Known issues — not done tonight
Nothing here blocks the delivery. Full detail in `docs/parity/ISSUES.md`.
### Missing app pages
**None.** Every page the reference PWA has now exists in both apps.
### Delivered tonight
| Feature | State |
|---|---|
| **Sales-expert app login (D21)** | ✅ Done. An expert signs into both apps with their AdminUser account and the ordinary OTP flow; the master code works for them too. They get the whole app — browsing, cart, ordering, order history — plus their own two screens. |
| **«سفارش مشتریان» (ChildOrder)** | ✅ Done in both apps. Their referred customers' orders at «تحویل مسئول فروش», searchable, infinite-scrolled. |
| **«مشتریان من» (customerChoice)** | ✅ Done in both apps. Their referred customers, searchable, with a read-only detail sheet. |
| **APP-02 — admin home slider in v2** | ✅ Fixed. v2 now prefers admin-configured slides and keeps its category-derived hero as the fallback. |
| **«تکمیل پروفایل» (complete_profile)** | ✅ Done in both apps. Three document uploads (جواز کسب، سردر مغازه، کارت ملی), then the profile is marked verified. Reached from the profile screen, and only while the documents are missing — exactly the reference's condition. |
| **«گزارش» (report + report/detail)** | ✅ Done in both apps. Two tabs («کلیات حساب» / «جزئیات حساب»), a running gold and rial balance per document, and a per-document detail sheet. Built on the **internal ledger** (owner's option الف) — no connection to the external accounting system the reference proxied. See D24. |
An expert's own order lands on their **own trading account**, with the making
charge from `AdminUser.wagePercent` and the credit limit from
`SalesExpertProfile.gramLevel` — the same two numbers the reference read off
the agent's own customer row. See D21.
**Two reference behaviours were deliberately not ported**, both money/session
risks and both recorded in D21:
1. `myChild` minted an `api_token` per listed customer so the agent could act
**as** them — a session-hijack primitive.
2. The child-order edit drawer (`PUT /order/child/{id}`) recalculated an
order's weights and prices from client-sent values. The card opens a
read-only summary instead.
### Behavioural gaps
| ID | What |
|---|---|
| **APP-02** | `user-app-v2` has no `getHomeSlider`, so slides configured in the admin panel appear in **user-app only**; v2 always derives its hero from categories. Not fixed because v2's home is its own design and rewiring it during a delivery risks the page being demonstrated. |
| **PERS-02** | The reference's cities are a province → city **tree** (provinces not selectable); the target flattened this to a `province` string and a flat select on all three Persons forms. The city itself is still captured correctly. |
| **PERS-03** | The reference collects a Jalali **birth date** on the customer forms; the target has no column for it. |
| **PERS-01** | The reference makes a customer's **mobile immutable on edit**; the target allows editing it. Owner ruled this NOT ported — the target keeps its current behaviour. |
| **PERF-01** | `listReagents()` runs one aggregate per row (N+1) for the per-agent sales total. Harmless at the current agent count. |
### Parity work not reached
Phase 1 was not completed: the field-by-field audit stopped after the Persons
section, and products/categories/variants, financial documents, users and
permissions, SMS templates, promotions, cities, branches, workshops and reports
have had only the breakage scan above, not a full parity audit. The
**Phase 1 gate was lifted for tonight only** (D19) and still stands as the next
milestone.
`PERM-01` (the database holds 178 distinct permission ids against the UI's 174)
is still awaiting a decision, because resolving it means deleting rows.
---
## 6. Decisions that shaped this build
Recorded in `docs/parity/DECISIONS.md`:
- **D13** — the retail storefront (`single*`, wallet, comments, `customer.php`,
`site.php`) is **preserved but out of scope**. Nothing there was changed.
- **D19** — delivery mode: implement the main gaps, do not stop to ask, choose
the lowest-risk option, change nothing protected.
- **D20** — SMS can never block a boot or an operation; `OTP_MASTER_CODE` for
the delivery.
- **D26** — SMS goes through the owner's `relay` gateway by default; Trez is
kept as a selectable driver.
- **D11 is cancelled** — superseded by D13.
+6 -11
View File
@@ -8,7 +8,7 @@ Dokploy only builds thin images that copy those files (no `pnpm install` on the
```bash
pnpm install
pnpm build:release
git add -f apps/api/release apps/admin-panel/dist apps/user-app/dist
git add -f apps/api/release apps/admin-panel/dist apps/user-app/dist apps/user-app-v2/dist
git commit -m "chore: refresh release artifacts"
git push
```
@@ -20,31 +20,26 @@ What this produces:
| API | `apps/api/release/app.js` (+ Prisma under `release/vendor`) | `docker.arvancloud.ir/node:20-bookworm` |
| Admin | `apps/admin-panel/dist/` | `docker.arvancloud.ir/nginx:latest` |
| User | `apps/user-app/dist/` | `docker.arvancloud.ir/nginx:latest` |
| User v2 | `apps/user-app-v2/dist/` | `docker.arvancloud.ir/nginx:latest` |
## Dokploy Environment
```env
POSTGRES_USER=hedenagold
POSTGRES_USER=goldbank
POSTGRES_PASSWORD=...
POSTGRES_DB=hedenagold
POSTGRES_DB=goldbank
EXTERNAL_DB_PORT=5433
DATABASE_URL=postgresql://hedenagold:...@db:5432/hedenagold?schema=public
DATABASE_URL=postgresql://goldbank:...@db:5432/goldbank?schema=public
PORT=3000
```
A second copy on the **same server** is a separate Dokploy application (its own
domains, passwords, and `JWT_SECRET`). Do not set `container_name` in this file:
those names are global in Docker, and a fixed `hedenagold_db` collides with the
first copy. Give the second app its own `EXTERNAL_DB_PORT` (for example `5434`
if `5433` is already taken). Volumes stay separate because Dokploy uses a
different Compose project name for each application.
## Domains (container ports)
| Service | Domain | Port |
|---|---|---|
| `admin-panel` | admin.example.com | **80** |
| `user-app` | app.example.com | **80** |
| `user-app-v2` | app-v2.example.com | **80** |
| `api` | (optional) | **3000** |
Frontends call `/api` on their own domain; nginx proxies to `api:3000`.
+10
View File
@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/apps.iml" filepath="$PROJECT_DIR$/.idea/apps.iml" />
</modules>
</component>
</project>
+19
View File
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>
+1 -1
View File
@@ -1,4 +1,4 @@
FROM nginx:latest
FROM docker.arvancloud.ir/nginx:latest
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY dist /usr/share/nginx/html
@@ -1 +0,0 @@
import{I as h}from"./Icon-B9FXOmlS.js";import{d as w,v as B,x as l,o as C,c as S,w as u,e as $,t as A,p as E,f as L,k as c,P as p,M as d,A as j,r as M}from"./index-QQcP5-AC.js";const V=w({__name:"AccountingSyncButton",props:{endpoint:{}},emits:["done"],setup(m,{emit:f}){const v=m,_=f,s=c(!1),r=c(null),a=c(!1),k={customers:"مشتریان",balances:"مانده‌ها",products:"کارساخته‌ها و موجودی"},y={scanned:"بررسی‌شده",inScope:"در گروه‌های انتخابی",refreshed:"به‌روز",linked:"لینک‌شده",imported:"وارد شده",issues:"مغایرت",updated:"به‌روز",items:"کارساخته",stockErrors:"خطای موجودی"},g=e=>e&&typeof e=="object"?Object.entries(e).map(([o,t])=>`${y[o]??o}: ${t}`).join("، "):"";B(async()=>{try{const{data:e}=await l.get("admin/accounting-status");s.value=!!(e!=null&&e.connected),r.value=(e==null?void 0:e.provider)??null}catch{s.value=!1}});async function x(){var e,o;a.value=!0;try{const{data:t}=await l.post(v.endpoint),b=Object.entries(t.results).map(([i,n])=>p("p",{style:n.ok?"":"color:#d4380d"},`${k[i]??i}: ${n.ok?g(n.summary)||"انجام شد":`خطا — ${n.error}`}`));(t.ok?d.success:d.warning)({title:t.ok?"سینک با حسابداری انجام شد":"سینک با حسابداری کامل انجام نشد",content:p("div",b),okText:"باشه"}),_("done")}catch(t){j.error(((o=(e=t==null?void 0:t.response)==null?void 0:e.data)==null?void 0:o.message)||"سینک با حسابداری انجام نشد")}finally{a.value=!1}}return(e,o)=>{const t=M("a-button");return s.value?(C(),S(t,{key:0,loading:a.value,onClick:x},{icon:u(()=>[E(h,{icon:"vuesax-linear:refresh-2",size:"16",class:"icon-spin"})]),default:u(()=>[$(" سینک با "+A(r.value||"حسابداری"),1)]),_:1},8,["loading"])):L("",!0)}}});export{V as _};
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./Add.vue_vue_type_script_setup_true_lang-vc2KlLTI.js";import"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-BTbAO94-.js";export{o as default};
-1
View File
@@ -1 +0,0 @@
import{_ as o}from"./Add.vue_vue_type_script_setup_true_lang-C2L5ywNo.js";import"./index-QQcP5-AC.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-gPDguZj2.js";import"./Icon-B9FXOmlS.js";export{o as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{_ as o}from"./AddBranches.vue_vue_type_script_setup_true_lang-Du71-Nbh.js";import"./index-QQcP5-AC.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";export{o as default};
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./AddBranches.vue_vue_type_script_setup_true_lang-BQagavGv.js";import"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";export{o as default};
@@ -0,0 +1 @@
import{d as q,v as G,x as c,a as v,w as l,h as R,j as H,l as S,y as j,r as i,o as p,n as C,c as n,b as O,e as $,F as z,f as g,A as I,z as M,k as J,t as N}from"./index-DuH3OOPw.js";import{_ as K}from"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";const P={class:"grid-2"},Q={class:"grid-2"},Y=q({__name:"AddBranches",props:{open:{type:Boolean},id:{}},emits:["update:open","success"],setup(h,{emit:V}){const y=h,w=V,f=y.open!==void 0,A=H(),E=J(),d=f?y.id:A.params.id,m=S(!1),_=S([]),t=j({name:"",phone:"",address:"",user_id:void 0}),r=j({});function k(s,e){var o,u;return String(((u=(o=e==null?void 0:e.children)==null?void 0:o[0])==null?void 0:u.children)??"").includes(s)}async function b(){var s,e;if(Object.keys(r).forEach(o=>delete r[o]),!t.name){r.name="لطفا مقدار را وارد کنید";return}try{m.value=!0,d?await c.put(`branch/${d}`,t):await c.post("branch",t),M.info("اطلاعات با موفقیت به ثبت رسید."),d||Object.assign(t,{name:"",phone:"",address:"",user_id:void 0}),f?(w("success"),w("update:open",!1)):E.push("/branch/list")}catch(o){M.error(((e=(s=o==null?void 0:o.response)==null?void 0:s.data)==null?void 0:e.message)??"خطا در ثبت اطلاعات.")}finally{m.value=!1}}return G(async()=>{try{const{data:s}=await c.get("user/list");_.value=s}catch(s){console.log(s)}if(d)try{const{data:s}=await c.get(`branch/${d}`);Object.assign(t,{name:s.name??"",phone:s.phone??"",address:s.address??"",user_id:s.user_id??void 0})}catch(s){console.log(s)}}),(s,e)=>{const o=i("a-input"),u=i("a-form-item"),U=i("a-select-option"),x=i("a-select"),B=i("a-textarea"),F=i("a-button"),D=i("a-card"),L=i("a-form"),T=i("a-spin");return f?(p(),v(K,{key:0,open:!!h.open,id:R(d),"add-title":"افزودن شعبه","edit-title":"ویرایش شعبه",loading:m.value,submitting:m.value,"onUpdate:open":e[4]||(e[4]=a=>s.$emit("update:open",a)),onSubmit:b},{default:l(()=>[C("div",P,[n(u,{label:"نام شعبه","validate-status":r.name?"error":"",help:r.name},{default:l(()=>[n(o,{value:t.name,"onUpdate:value":e[0]||(e[0]=a=>t.name=a)},null,8,["value"])]),_:1},8,["validate-status","help"]),n(u,{label:"تلفن شعبه"},{default:l(()=>[n(o,{value:t.phone,"onUpdate:value":e[1]||(e[1]=a=>t.phone=a)},null,8,["value"])]),_:1}),n(u,{label:"مدیر"},{default:l(()=>[n(x,{value:t.user_id,"onUpdate:value":e[2]||(e[2]=a=>t.user_id=a),"show-search":"","allow-clear":"",placeholder:"انتخاب کنید","filter-option":k},{default:l(()=>[(p(!0),O(z,null,$(_.value,a=>(p(),v(U,{key:a.id,value:a.id},{default:l(()=>[g(N(a.name),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1})]),n(u,{label:"آدرس"},{default:l(()=>[n(B,{value:t.address,"onUpdate:value":e[3]||(e[3]=a=>t.address=a),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1})]),_:1},8,["open","id","loading","submitting"])):(p(),v(T,{key:1,spinning:m.value},{default:l(()=>[n(L,{layout:"vertical",onSubmit:I(b,["prevent"])},{default:l(()=>[n(D,{title:R(d)?"ویرایش شعبه":"افزودن شعبه",class:"mb-base"},{default:l(()=>[C("div",Q,[n(u,{label:"نام شعبه","validate-status":r.name?"error":"",help:r.name},{default:l(()=>[n(o,{value:t.name,"onUpdate:value":e[5]||(e[5]=a=>t.name=a)},null,8,["value"])]),_:1},8,["validate-status","help"]),n(u,{label:"تلفن شعبه"},{default:l(()=>[n(o,{value:t.phone,"onUpdate:value":e[6]||(e[6]=a=>t.phone=a)},null,8,["value"])]),_:1}),n(u,{label:"مدیر"},{default:l(()=>[n(x,{value:t.user_id,"onUpdate:value":e[7]||(e[7]=a=>t.user_id=a),"show-search":"","allow-clear":"",placeholder:"انتخاب کنید","filter-option":k},{default:l(()=>[(p(!0),O(z,null,$(_.value,a=>(p(),v(U,{key:a.id,value:a.id},{default:l(()=>[g(N(a.name),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1})]),n(u,{label:"آدرس"},{default:l(()=>[n(B,{value:t.address,"onUpdate:value":e[8]||(e[8]=a=>t.address=a),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1}),n(F,{type:"primary",onClick:b},{default:l(()=>[...e[9]||(e[9]=[g("ثبت اطلاعات",-1)])]),_:1})]),_:1},8,["title"])]),_:1})]),_:1},8,["spinning"]))}}});export{Y as _};
@@ -1 +0,0 @@
import{d as T,v as G,x as c,c as v,w as l,q as R,i as H,k as S,y as j,r as i,o as p,m as C,p as n,a as O,b as $,e as g,t as M,F as N,B as I,A as V,j as J}from"./index-QQcP5-AC.js";import{_ as K}from"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";const P={class:"grid-2"},Q={class:"grid-2"},Y=T({__name:"AddBranches",props:{open:{type:Boolean},id:{}},emits:["update:open","success"],setup(y,{emit:z}){const h=y,w=z,f=h.open!==void 0,A=H(),E=J(),r=f?h.id:A.params.id,m=S(!1),_=S([]),t=j({name:"",phone:"",address:"",user_id:void 0}),d=j({});function k(s,e){var o,u;return String(((u=(o=e==null?void 0:e.children)==null?void 0:o[0])==null?void 0:u.children)??"").includes(s)}async function b(){var s,e;if(Object.keys(d).forEach(o=>delete d[o]),!t.name){d.name="لطفا مقدار را وارد کنید";return}try{m.value=!0,r?await c.put(`branch/${r}`,t):await c.post("branch",t),V.info("اطلاعات با موفقیت به ثبت رسید."),r||Object.assign(t,{name:"",phone:"",address:"",user_id:void 0}),f?(w("success"),w("update:open",!1)):E.push("/branch/list")}catch(o){V.error(((e=(s=o==null?void 0:o.response)==null?void 0:s.data)==null?void 0:e.message)??"خطا در ثبت اطلاعات.")}finally{m.value=!1}}return G(async()=>{try{const{data:s}=await c.get("user/list");_.value=s}catch(s){console.log(s)}if(r)try{const{data:s}=await c.get(`branch/${r}`);Object.assign(t,{name:s.name??"",phone:s.phone??"",address:s.address??"",user_id:s.user_id??void 0})}catch(s){console.log(s)}}),(s,e)=>{const o=i("a-input"),u=i("a-form-item"),U=i("a-select-option"),x=i("a-select"),B=i("a-textarea"),F=i("a-button"),q=i("a-card"),D=i("a-form"),L=i("a-spin");return f?(p(),v(K,{key:0,open:!!y.open,id:R(r),"add-title":"افزودن شعبه","edit-title":"ویرایش شعبه",loading:m.value,submitting:m.value,"onUpdate:open":e[4]||(e[4]=a=>s.$emit("update:open",a)),onSubmit:b},{default:l(()=>[C("div",P,[n(u,{label:"نام شعبه","validate-status":d.name?"error":"",help:d.name},{default:l(()=>[n(o,{value:t.name,"onUpdate:value":e[0]||(e[0]=a=>t.name=a)},null,8,["value"])]),_:1},8,["validate-status","help"]),n(u,{label:"تلفن شعبه"},{default:l(()=>[n(o,{value:t.phone,"onUpdate:value":e[1]||(e[1]=a=>t.phone=a)},null,8,["value"])]),_:1}),n(u,{label:"مدیر"},{default:l(()=>[n(x,{value:t.user_id,"onUpdate:value":e[2]||(e[2]=a=>t.user_id=a),"show-search":"","allow-clear":"",placeholder:"انتخاب کنید","filter-option":k},{default:l(()=>[(p(!0),O(N,null,$(_.value,a=>(p(),v(U,{key:a.id,value:a.id},{default:l(()=>[g(M(a.name),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1})]),n(u,{label:"آدرس"},{default:l(()=>[n(B,{value:t.address,"onUpdate:value":e[3]||(e[3]=a=>t.address=a),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1})]),_:1},8,["open","id","loading","submitting"])):(p(),v(L,{key:1,spinning:m.value},{default:l(()=>[n(D,{layout:"vertical",onSubmit:I(b,["prevent"])},{default:l(()=>[n(q,{title:R(r)?"ویرایش شعبه":"افزودن شعبه",class:"mb-base page-card"},{default:l(()=>[C("div",Q,[n(u,{label:"نام شعبه","validate-status":d.name?"error":"",help:d.name},{default:l(()=>[n(o,{value:t.name,"onUpdate:value":e[5]||(e[5]=a=>t.name=a)},null,8,["value"])]),_:1},8,["validate-status","help"]),n(u,{label:"تلفن شعبه"},{default:l(()=>[n(o,{value:t.phone,"onUpdate:value":e[6]||(e[6]=a=>t.phone=a)},null,8,["value"])]),_:1}),n(u,{label:"مدیر"},{default:l(()=>[n(x,{value:t.user_id,"onUpdate:value":e[7]||(e[7]=a=>t.user_id=a),"show-search":"","allow-clear":"",placeholder:"انتخاب کنید","filter-option":k},{default:l(()=>[(p(!0),O(N,null,$(_.value,a=>(p(),v(U,{key:a.id,value:a.id},{default:l(()=>[g(M(a.name),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1})]),n(u,{label:"آدرس"},{default:l(()=>[n(B,{value:t.address,"onUpdate:value":e[8]||(e[8]=a=>t.address=a),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1}),n(F,{type:"primary",class:"form-submit",onClick:b},{default:l(()=>[...e[9]||(e[9]=[g("ثبت اطلاعات",-1)])]),_:1})]),_:1},8,["title"])]),_:1})]),_:1},8,["spinning"]))}}});export{Y as _};
-1
View File
@@ -1 +0,0 @@
import{I as c}from"./Icon-B9FXOmlS.js";import{d as b,u as g,k as x,a as n,m as s,p as l,w,F as _,b as B,c as h,a1 as S,f as u,i as R,g as z,r as F,o,j as A,n as I,t as k,_ as N}from"./index-QQcP5-AC.js";import{_ as V}from"./bulkCustomer.vue_vue_type_script_setup_true_lang-UhBsQkgG.js";import{R as D}from"./ReagentCustomer-DkAyHy8l.js";import{S as P}from"./singleCustomer-XwT9-VyX.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-gPDguZj2.js";import"./CityTreeSelect.vue_vue_type_script_setup_true_lang-qW84W-31.js";import"./DateInput-COU3oLhL.js";import"./index-DT4Uze1z.js";const $={id:"page-user-view",class:"pf-page"},j={class:"pf-head"},q={class:"kind-picker",role:"tablist"},E=["aria-selected","onClick"],L={class:"kind-card__icon"},M={class:"kind-card__text"},O=b({__name:"AddCustomer",setup(T){const v=R(),d=A(),p=g(),t=x(v.query.tab||"omde"),r=a=>!!p.hasPermission(a),i=z(()=>{const a=[];return r("persons.create.client")&&a.push({key:"omde",title:"مشتری عمده",hint:"مغازه‌دار، بنکدار و خریدار عمده",icon:"vuesax-linear:shop"}),r("persons.create.seller")&&a.push({key:"reagent",title:"کارشناس فروش",hint:"کارشناس با کمیسیون و سقف اعتبار",icon:"vuesax-linear:personalcard"}),p.isSuperAdmin&&a.push({key:"tak",title:"مشتری تک",hint:"مشتری خرده‌فروشی حضوری",icon:"vuesax-linear:user"}),a}),f={omde:V,reagent:D,tak:P};function y(){window.history.length>1?d.back():d.push("/customer/omde-list")}return(a,m)=>{const C=F("a-button");return o(),n("div",$,[s("header",j,[l(C,{class:"pf-head__back",shape:"circle",onClick:y},{icon:w(()=>[l(c,{icon:"vuesax-linear:arrow-right",size:"16"})]),_:1}),m[0]||(m[0]=s("div",{class:"pf-head__text"},[s("h1",null,"افزودن شخص"),s("p",null,"نوع شخص را انتخاب و اطلاعات او را تکمیل کنید.")],-1))]),r("persons.create")&&i.value.length?(o(),n(_,{key:0},[s("div",q,[(o(!0),n(_,null,B(i.value,e=>(o(),n("button",{key:e.key,type:"button",role:"tab",class:I(["kind-card",{"is-on":t.value===e.key}]),"aria-selected":t.value===e.key,onClick:U=>t.value=e.key},[s("span",L,[l(c,{icon:e.icon,size:"22"},null,8,["icon"])]),s("span",M,[s("b",null,k(e.title),1),s("small",null,k(e.hint),1)]),t.value===e.key?(o(),h(c,{key:0,icon:"vuesax-linear:tick-circle",size:"16",class:"kind-card__check"})):u("",!0)],10,E))),128))]),i.value.some(e=>e.key===t.value)?(o(),h(S(f[t.value]),{key:t.value})):u("",!0)],64)):u("",!0)])}}}),se=N(O,[["__scopeId","data-v-84479c56"]]);export{se as default};
-1
View File
@@ -1 +0,0 @@
b[data-v-84479c56]{font-weight:500}.kind-picker[data-v-84479c56]{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-bottom:20px}.kind-card[data-v-84479c56]{position:relative;display:flex;align-items:center;gap:12px;padding:14px 16px;border:1.5px solid #eef0f5;border-radius:16px;background:#fff;text-align:start;cursor:pointer;font:inherit;transition:border-color .2s ease,box-shadow .2s ease,background .2s ease}.kind-card[data-v-84479c56]:hover{border-color:#c9d0dd}.kind-card.is-on[data-v-84479c56]{border-color:#1d4ed8;background:#1d4ed808;box-shadow:0 0 0 3px #1d4ed814}.kind-card__icon[data-v-84479c56]{width:42px;height:42px;flex-shrink:0;display:grid;place-items:center;border-radius:12px;font-size:16px;color:#8a94a6;background:#f2f4f7;transition:color .2s ease,background .2s ease}.kind-card.is-on .kind-card__icon[data-v-84479c56]{color:#fff;background:#1d4ed8}.kind-card__text[data-v-84479c56]{display:flex;flex-direction:column;gap:2px;min-width:0}.kind-card__text b[data-v-84479c56]{font-size:14px;color:#101828}.kind-card__text small[data-v-84479c56]{font-size:12px;color:#8a94a6}.kind-card__check[data-v-84479c56]{margin-inline-start:auto;font-size:16px;color:#1d4ed8}@media (max-width: 575px){.kind-picker[data-v-84479c56]{grid-template-columns:minmax(0,1fr);gap:8px;margin-bottom:12px}.kind-card[data-v-84479c56]{padding:10px 12px}.kind-card__icon[data-v-84479c56]{width:36px;height:36px;font-size:16px}}
+1
View File
@@ -0,0 +1 @@
import{_ as p}from"./bulkCustomer.vue_vue_type_script_setup_true_lang-DgWWO9pL.js";import{d as u,u as l,b as f,n as b,c as n,w as e,r as a,o as s,a as r,i as c}from"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";const h={id:"page-user-view"},k={id:"user-data"},N=u({__name:"AddCustomer",setup(B){const _=l(),t=o=>!!_.hasPermission(o);return(o,C)=>{const d=a("a-tab-pane"),m=a("a-tabs"),i=a("a-card");return s(),f("div",h,[b("div",k,[n(i,{class:"mb-base"},{default:e(()=>[t("persons.create")?(s(),r(m,{key:0},{default:e(()=>[t("persons.create.client")?(s(),r(d,{key:"omde",tab:"مشتری عمده"},{default:e(()=>[n(p)]),_:1})):c("",!0)]),_:1})):c("",!0)]),_:1})])])}}});export{N as default};
+1
View File
@@ -0,0 +1 @@
.mt-4[data-v-f195ac0c]{margin-top:1rem}.mt-5[data-v-f195ac0c]{margin-top:1.25rem}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.mt-4[data-v-93be6d1e]{margin-top:1rem}.mt-5[data-v-93be6d1e]{margin-top:1.25rem}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.grid-1[data-v-7f6fdb0f]{display:grid;grid-template-columns:1fr}.image-grid[data-v-7f6fdb0f]{display:flex;flex-wrap:wrap;gap:8px}.flex-1[data-v-7f6fdb0f]{flex:1}.mb-8[data-v-7f6fdb0f]{margin-bottom:2rem}.overflow-auto[data-v-7f6fdb0f]{overflow:auto}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./AddWorkshop.vue_vue_type_script_setup_true_lang-Ddx471SB.js";import"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";export{o as default};
-1
View File
@@ -1 +0,0 @@
import{_ as o}from"./AddWorkshop.vue_vue_type_script_setup_true_lang-C26658Z1.js";import"./index-QQcP5-AC.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";export{o as default};
@@ -1 +0,0 @@
import{d as O,v as S,x as v,c as b,w as n,q as g,i as V,k as z,y,r as p,o as U,m as x,p as o,e as A,B as q,A as c,j as E}from"./index-QQcP5-AC.js";import{_ as T}from"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";const W={class:"grid-3"},D={class:"grid-3"},H=O({__name:"AddWorkshop",props:{open:{type:Boolean},id:{}},emits:["update:open","success"],setup(f,{emit:B}){const h=f,w=B,_=h.open!==void 0,R=V(),j=E(),d=_?h.id:R.params.id,i=z(!1),e=y({workshop_name:"",workshop_phone:"",workshop_address:"",user_fname:"",user_lname:"",user_phone:""}),u=y({});async function m(){var t,a;if(Object.keys(u).forEach(l=>delete u[l]),e.workshop_name||(u.workshop_name="لطفا مقدار را وارد کنید"),(e.user_fname||e.user_lname)&&!e.user_phone&&(u.user_phone="موبایل سرپرست کارگاه الزامی است"),!Object.keys(u).length)try{i.value=!0,d?await v.put(`workshop/${d}`,e):await v.post("workshop",e),c.info("اطلاعات با موفقیت به ثبت رسید."),_?(w("success"),w("update:open",!1)):j.push("/workshop/list")}catch(l){c.error(((a=(t=l==null?void 0:l.response)==null?void 0:t.data)==null?void 0:a.message)??"خطا در ثبت اطلاعات.")}finally{i.value=!1}}return S(async()=>{if(d)try{const{data:t}=await v.get(`workshop/${d}`);Object.assign(e,{workshop_name:t.workshop_name??t.name??"",workshop_phone:t.workshop_phone??t.phone??"",workshop_address:t.workshop_address??t.address??"",user_fname:t.user_fname??"",user_lname:t.user_lname??"",user_phone:t.user_phone??""})}catch(t){console.log(t)}}),(t,a)=>{const l=p("a-input"),r=p("a-form-item"),k=p("a-textarea"),C=p("a-button"),$=p("a-card"),M=p("a-form"),N=p("a-spin");return _?(U(),b(T,{key:0,open:!!f.open,id:g(d),"add-title":"افزودن کارگاه","edit-title":"ویرایش کارگاه",loading:i.value,submitting:i.value,width:"lg","onUpdate:open":a[6]||(a[6]=s=>t.$emit("update:open",s)),onSubmit:m},{default:n(()=>[x("div",W,[o(r,{label:"نام کارگاه","validate-status":u.workshop_name?"error":"",help:u.workshop_name},{default:n(()=>[o(l,{value:e.workshop_name,"onUpdate:value":a[0]||(a[0]=s=>e.workshop_name=s)},null,8,["value"])]),_:1},8,["validate-status","help"]),o(r,{label:"تلفن کارگاه"},{default:n(()=>[o(l,{value:e.workshop_phone,"onUpdate:value":a[1]||(a[1]=s=>e.workshop_phone=s)},null,8,["value"])]),_:1}),o(r,{label:"نام سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_fname,"onUpdate:value":a[2]||(a[2]=s=>e.user_fname=s)},null,8,["value"])]),_:1}),o(r,{label:"نام خانوادگی سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_lname,"onUpdate:value":a[3]||(a[3]=s=>e.user_lname=s)},null,8,["value"])]),_:1}),o(r,{label:"موبایل سرپرست کارگاه","validate-status":u.user_phone?"error":"",help:u.user_phone},{default:n(()=>[o(l,{value:e.user_phone,"onUpdate:value":a[4]||(a[4]=s=>e.user_phone=s)},null,8,["value"])]),_:1},8,["validate-status","help"])]),o(r,{label:"آدرس"},{default:n(()=>[o(k,{value:e.workshop_address,"onUpdate:value":a[5]||(a[5]=s=>e.workshop_address=s),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1})]),_:1},8,["open","id","loading","submitting"])):(U(),b(N,{key:1,spinning:i.value},{default:n(()=>[o(M,{layout:"vertical",onSubmit:q(m,["prevent"])},{default:n(()=>[o($,{title:g(d)?"ویرایش کارگاه":"افزودن کارگاه",class:"mb-base page-card"},{default:n(()=>[x("div",D,[o(r,{label:"نام کارگاه","validate-status":u.workshop_name?"error":"",help:u.workshop_name},{default:n(()=>[o(l,{value:e.workshop_name,"onUpdate:value":a[7]||(a[7]=s=>e.workshop_name=s)},null,8,["value"])]),_:1},8,["validate-status","help"]),o(r,{label:"تلفن کارگاه"},{default:n(()=>[o(l,{value:e.workshop_phone,"onUpdate:value":a[8]||(a[8]=s=>e.workshop_phone=s)},null,8,["value"])]),_:1}),o(r,{label:"نام سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_fname,"onUpdate:value":a[9]||(a[9]=s=>e.user_fname=s)},null,8,["value"])]),_:1}),o(r,{label:"نام خانوادگی سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_lname,"onUpdate:value":a[10]||(a[10]=s=>e.user_lname=s)},null,8,["value"])]),_:1}),o(r,{label:"موبایل سرپرست کارگاه","validate-status":u.user_phone?"error":"",help:u.user_phone},{default:n(()=>[o(l,{value:e.user_phone,"onUpdate:value":a[11]||(a[11]=s=>e.user_phone=s)},null,8,["value"])]),_:1},8,["validate-status","help"])]),o(r,{label:"آدرس"},{default:n(()=>[o(k,{value:e.workshop_address,"onUpdate:value":a[12]||(a[12]=s=>e.workshop_address=s),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1}),o(C,{type:"primary",class:"form-submit",onClick:m},{default:n(()=>[...a[13]||(a[13]=[A("ثبت اطلاعات",-1)])]),_:1})]),_:1},8,["title"])]),_:1})]),_:1},8,["spinning"]))}}});export{H as _};
@@ -0,0 +1 @@
import{d as N,v as O,x as v,a as b,w as n,h as g,j as S,l as V,y,r as p,o as U,n as x,c as o,f as A,A as E,z as c,k as T}from"./index-DuH3OOPw.js";import{_ as W}from"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";const q={class:"grid-3"},D={class:"grid-3"},H=N({__name:"AddWorkshop",props:{open:{type:Boolean},id:{}},emits:["update:open","success"],setup(f,{emit:R}){const h=f,w=R,_=h.open!==void 0,B=S(),j=T(),d=_?h.id:B.params.id,i=V(!1),e=y({workshop_name:"",workshop_phone:"",workshop_address:"",user_fname:"",user_lname:"",user_phone:""}),u=y({});async function m(){var t,a;if(Object.keys(u).forEach(l=>delete u[l]),e.workshop_name||(u.workshop_name="لطفا مقدار را وارد کنید"),(e.user_fname||e.user_lname)&&!e.user_phone&&(u.user_phone="موبایل سرپرست کارگاه الزامی است"),!Object.keys(u).length)try{i.value=!0,d?await v.put(`workshop/${d}`,e):await v.post("workshop",e),c.info("اطلاعات با موفقیت به ثبت رسید."),_?(w("success"),w("update:open",!1)):j.push("/workshop/list")}catch(l){c.error(((a=(t=l==null?void 0:l.response)==null?void 0:t.data)==null?void 0:a.message)??"خطا در ثبت اطلاعات.")}finally{i.value=!1}}return O(async()=>{if(d)try{const{data:t}=await v.get(`workshop/${d}`);Object.assign(e,{workshop_name:t.workshop_name??t.name??"",workshop_phone:t.workshop_phone??t.phone??"",workshop_address:t.workshop_address??t.address??"",user_fname:t.user_fname??"",user_lname:t.user_lname??"",user_phone:t.user_phone??""})}catch(t){console.log(t)}}),(t,a)=>{const l=p("a-input"),r=p("a-form-item"),k=p("a-textarea"),C=p("a-button"),$=p("a-card"),z=p("a-form"),M=p("a-spin");return _?(U(),b(W,{key:0,open:!!f.open,id:g(d),"add-title":"افزودن کارگاه","edit-title":"ویرایش کارگاه",loading:i.value,submitting:i.value,width:"lg","onUpdate:open":a[6]||(a[6]=s=>t.$emit("update:open",s)),onSubmit:m},{default:n(()=>[x("div",q,[o(r,{label:"نام کارگاه","validate-status":u.workshop_name?"error":"",help:u.workshop_name},{default:n(()=>[o(l,{value:e.workshop_name,"onUpdate:value":a[0]||(a[0]=s=>e.workshop_name=s)},null,8,["value"])]),_:1},8,["validate-status","help"]),o(r,{label:"تلفن کارگاه"},{default:n(()=>[o(l,{value:e.workshop_phone,"onUpdate:value":a[1]||(a[1]=s=>e.workshop_phone=s)},null,8,["value"])]),_:1}),o(r,{label:"نام سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_fname,"onUpdate:value":a[2]||(a[2]=s=>e.user_fname=s)},null,8,["value"])]),_:1}),o(r,{label:"نام خانوادگی سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_lname,"onUpdate:value":a[3]||(a[3]=s=>e.user_lname=s)},null,8,["value"])]),_:1}),o(r,{label:"موبایل سرپرست کارگاه","validate-status":u.user_phone?"error":"",help:u.user_phone},{default:n(()=>[o(l,{value:e.user_phone,"onUpdate:value":a[4]||(a[4]=s=>e.user_phone=s)},null,8,["value"])]),_:1},8,["validate-status","help"])]),o(r,{label:"آدرس"},{default:n(()=>[o(k,{value:e.workshop_address,"onUpdate:value":a[5]||(a[5]=s=>e.workshop_address=s),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1})]),_:1},8,["open","id","loading","submitting"])):(U(),b(M,{key:1,spinning:i.value},{default:n(()=>[o(z,{layout:"vertical",onSubmit:E(m,["prevent"])},{default:n(()=>[o($,{title:g(d)?"ویرایش کارگاه":"افزودن کارگاه",class:"mb-base"},{default:n(()=>[x("div",D,[o(r,{label:"نام کارگاه","validate-status":u.workshop_name?"error":"",help:u.workshop_name},{default:n(()=>[o(l,{value:e.workshop_name,"onUpdate:value":a[7]||(a[7]=s=>e.workshop_name=s)},null,8,["value"])]),_:1},8,["validate-status","help"]),o(r,{label:"تلفن کارگاه"},{default:n(()=>[o(l,{value:e.workshop_phone,"onUpdate:value":a[8]||(a[8]=s=>e.workshop_phone=s)},null,8,["value"])]),_:1}),o(r,{label:"نام سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_fname,"onUpdate:value":a[9]||(a[9]=s=>e.user_fname=s)},null,8,["value"])]),_:1}),o(r,{label:"نام خانوادگی سرپرست کارگاه"},{default:n(()=>[o(l,{value:e.user_lname,"onUpdate:value":a[10]||(a[10]=s=>e.user_lname=s)},null,8,["value"])]),_:1}),o(r,{label:"موبایل سرپرست کارگاه","validate-status":u.user_phone?"error":"",help:u.user_phone},{default:n(()=>[o(l,{value:e.user_phone,"onUpdate:value":a[11]||(a[11]=s=>e.user_phone=s)},null,8,["value"])]),_:1},8,["validate-status","help"])]),o(r,{label:"آدرس"},{default:n(()=>[o(k,{value:e.workshop_address,"onUpdate:value":a[12]||(a[12]=s=>e.workshop_address=s),"auto-size":{minRows:2,maxRows:5}},null,8,["value"])]),_:1}),o(C,{type:"primary",onClick:m},{default:n(()=>[...a[13]||(a[13]=[A("ثبت اطلاعات",-1)])]),_:1})]),_:1},8,["title"])]),_:1})]),_:1},8,["spinning"]))}}});export{H as _};
-1
View File
@@ -1 +0,0 @@
.stats[data-v-eef9d79c]{display:flex;flex-wrap:wrap;gap:.5rem}.mt-4[data-v-eef9d79c]{margin-top:1rem}@media (max-width: 575px){.mt-4[data-v-eef9d79c]{margin-top:12px}.stats[data-v-eef9d79c] .ant-tag{margin:0}.detail-tabs[data-v-eef9d79c] .ant-tabs-nav{margin-bottom:8px}.detail-tabs[data-v-eef9d79c] .ant-table-content{overflow-x:auto}.detail-tabs[data-v-eef9d79c] .ant-table-content>table{min-width:max-content}}
-1
View File
@@ -1 +0,0 @@
import{d as D,v as S,x as A,c as b,w as a,i as F,k as y,r as d,o as i,p as l,e as n,t as s,m as q,a as p,F as f,q as I,f as k,_ as E}from"./index-QQcP5-AC.js";import{j}from"./filters-B9z4I-pb.js";import"./index-DT4Uze1z.js";const M={class:"stats mt-4"},R=D({__name:"AgentDetail",setup(T){const C=F().params.id,m=y(!1),o=y(void 0),h=[{title:"نام",dataIndex:"name"},{title:"موبایل",dataIndex:"mobile"},{title:"شهر",dataIndex:"city"},{title:"نوع",dataIndex:"type_str"},{title:"وضعیت",dataIndex:"active"},{title:"تاریخ ثبت",dataIndex:"caj"}],w=[{title:"شماره سفارش",dataIndex:"name"},{title:"مشتری",dataIndex:"customer"},{title:"وزن",dataIndex:"w"},{title:"وضعیت",dataIndex:"status"},{title:"تاریخ",dataIndex:"caj"}];return S(async()=>{try{m.value=!0;const{data:_}=await A.get(`admin/customers-omde/reagent/${C}`);o.value=_}catch(_){console.log(_)}finally{m.value=!1}}),(_,G)=>{const c=d("a-descriptions-item"),u=d("a-tag"),z=d("a-descriptions"),B=d("a-card"),v=d("a-table"),x=d("a-tab-pane"),N=d("a-tabs"),V=d("a-spin");return i(),b(V,{spinning:m.value},{default:a(()=>[l(B,{title:"مشاهده اطلاعات کارشناس فروش",class:"page-card"},{default:a(()=>[l(z,{bordered:"",size:"small",column:{xxl:3,xl:3,lg:3,md:2,sm:2,xs:1}},{default:a(()=>[l(c,{label:"نام"},{default:a(()=>{var e;return[n(s(((e=o.value)==null?void 0:e.name)??"-"),1)]}),_:1}),l(c,{label:"موبایل"},{default:a(()=>{var e;return[n(s(((e=o.value)==null?void 0:e.mobile)??"-"),1)]}),_:1}),l(c,{label:"شعبه"},{default:a(()=>{var e,t;return[n(s(((t=(e=o.value)==null?void 0:e.branch)==null?void 0:t.name)??"-"),1)]}),_:1}),l(c,{label:"درصد کمیسیون"},{default:a(()=>{var e;return[n(s(((e=o.value)==null?void 0:e.percent)??"-"),1)]}),_:1}),l(c,{label:"وضعیت"},{default:a(()=>{var e;return[l(u,{color:((e=o.value)==null?void 0:e.active)===1?"green":"red"},{default:a(()=>{var t;return[n(s(((t=o.value)==null?void 0:t.active)===1?"فعال":"غیر فعال"),1)]}),_:1},8,["color"])]}),_:1}),l(c,{label:"میزان فروش (گرم)"},{default:a(()=>{var e;return[n(s(((e=o.value)==null?void 0:e.grams_sell)??0),1)]}),_:1})]),_:1}),q("div",M,[l(u,{color:"blue"},{default:a(()=>{var e,t;return[n("مشتریان معرفی‌شده: "+s(((t=(e=o.value)==null?void 0:e.referred_customers)==null?void 0:t.length)??0),1)]}),_:1}),l(u,{color:"green"},{default:a(()=>{var e,t;return[n("سفارش‌های ثبت‌شده: "+s(((t=(e=o.value)==null?void 0:e.orders)==null?void 0:t.length)??0),1)]}),_:1})])]),_:1}),l(N,{class:"mt-4 detail-tabs","default-active-key":"referred"},{default:a(()=>[l(x,{key:"referred",tab:"مشتریان معرفی‌شده"},{default:a(()=>{var e;return[l(v,{size:"small","data-source":(e=o.value)==null?void 0:e.referred_customers,columns:h,pagination:{pageSize:10},"row-key":"id"},{bodyCell:a(({column:t,record:r})=>{var g;return[t.dataIndex==="active"?(i(),b(u,{key:0,color:r.active===1?"green":"red"},{default:a(()=>[n(s(r.active===1?"فعال":"غیر فعال"),1)]),_:2},1032,["color"])):t.dataIndex==="city"?(i(),p(f,{key:1},[n(s(((g=r.city)==null?void 0:g.title)??"-"),1)],64)):t.dataIndex==="caj"?(i(),p(f,{key:2},[n(s(I(j)(r.caj)),1)],64)):k("",!0)]}),_:1},8,["data-source"])]}),_:1}),l(x,{key:"orders",tab:"سفارش‌های مشتریان"},{default:a(()=>{var e;return[l(v,{size:"small","data-source":(e=o.value)==null?void 0:e.orders,columns:w,pagination:{pageSize:10},"row-key":"id"},{bodyCell:a(({column:t,record:r})=>[t.dataIndex==="caj"?(i(),p(f,{key:0},[n(s(I(j)(r.caj)),1)],64)):k("",!0)]),_:1},8,["data-source"])]}),_:1})]),_:1})]),_:1},8,["spinning"])}}}),L=E(R,[["__scopeId","data-v-eef9d79c"]]);export{L as default};
+1
View File
@@ -0,0 +1 @@
import{b as r,O as i,_ as m}from"./omdeListModel-B4MG3nAI.js";import{d as a,a as n,h as t,o as s}from"./index-DuH3OOPw.js";import"./model-DWeeFWcT.js";import"./SettingOutlined-MJAt5cqt.js";import"./_plugin-vue_export-helper-DlAUqK2U.js";import"./ListPageHeader.vue_vue_type_script_setup_true_lang-BaaZTuAN.js";import"./bulkCustomer.vue_vue_type_script_setup_true_lang-DgWWO9pL.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";import"./UnorderedListOutlined-7Y4Gu4Ut.js";const x=a({__name:"BankerList",setup(p){const e=i.banker.title,o=r("banker");return(l,_)=>(s(),n(m,{title:t(e),model:t(o)},null,8,["title","model"]))}});export{x as default};
-1
View File
@@ -1 +0,0 @@
import{b as r,O as m,a as i}from"./OmdeCustomerList-fAbvz4EO.js";import{d as p,c as s,q as t,o as a}from"./index-QQcP5-AC.js";import"./Icon-B9FXOmlS.js";import"./index-DT4Uze1z.js";import"./model-m8OsHE_l.js";import"./filters-B9z4I-pb.js";import"./ListPageHeader.vue_vue_type_script_setup_true_lang-BBMCN2Q8.js";import"./AccountingSyncButton.vue_vue_type_script_setup_true_lang-Ce4J8HI3.js";import"./bulkCustomer.vue_vue_type_script_setup_true_lang-UhBsQkgG.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-gPDguZj2.js";import"./CityTreeSelect.vue_vue_type_script_setup_true_lang-qW84W-31.js";import"./DateInput-COU3oLhL.js";const S=p({__name:"BankerList",setup(n){const o=m.banker.title,e=r("banker");return(l,c)=>(a(),s(i,{title:t(o),model:t(e)},null,8,["title","model"]))}});export{S as default};
@@ -1 +0,0 @@
@media (max-width: 575px){.base-form-modal .ant-modal{width:100vw!important;max-width:100vw;top:0;margin:0;padding-bottom:0}.base-form-modal .ant-modal-content{display:flex;flex-direction:column;min-height:calc(var(--vh, 1vh) * 100);border-radius:0;padding:14px 16px 0}.base-form-modal .ant-modal-header{margin-bottom:12px}.base-form-modal .ant-modal-body{flex:1}.base-form-modal .ant-modal-footer{position:sticky;bottom:0;display:flex;gap:8px;margin:0 -16px;padding:10px 16px;background:#fff;border-top:1px solid #f0f0f0}.base-form-modal .ant-modal-footer>.ant-btn{flex:1;margin:0!important}}
@@ -0,0 +1 @@
import{d as r,a as g,w as o,r as a,o as k,c as l,V as x}from"./index-DuH3OOPw.js";const B=r({__name:"BaseFormModal",props:{open:{type:Boolean},id:{default:void 0},addTitle:{},editTitle:{},loading:{type:Boolean,default:!1},submitting:{type:Boolean,default:!1},width:{default:"md"},okText:{default:"ثبت اطلاعات"}},emits:["update:open","submit","cancel"],setup(e,{emit:d}){const n=d,s={sm:520,md:720,lg:900,xl:1140};function m(t){n("update:open",t)}function c(){n("update:open",!1),n("cancel")}return(t,i)=>{const f=a("a-form"),u=a("a-spin"),p=a("a-modal");return k(),g(p,{open:e.open,title:e.id?e.editTitle:e.addTitle,width:s[e.width],"confirm-loading":e.submitting,"ok-text":e.okText,"cancel-text":"انصراف","mask-closable":!1,"onUpdate:open":m,onOk:i[0]||(i[0]=T=>t.$emit("submit")),onCancel:c},{default:o(()=>[l(u,{spinning:e.loading},{default:o(()=>[l(f,{layout:"vertical"},{default:o(()=>[x(t.$slots,"default")]),_:3})]),_:3},8,["spinning"])]),_:3},8,["open","title","width","confirm-loading","ok-text"])}}});export{B as _};
@@ -1 +0,0 @@
import{d as r,c as g,w as o,r as a,o as k,p as i,O as w}from"./index-QQcP5-AC.js";const b=r({__name:"BaseFormModal",props:{open:{type:Boolean},id:{default:void 0},addTitle:{},editTitle:{},loading:{type:Boolean,default:!1},submitting:{type:Boolean,default:!1},width:{default:"md"},okText:{default:"ثبت اطلاعات"}},emits:["update:open","submit","cancel"],setup(e,{emit:d}){const n=d,s={sm:520,md:720,lg:900,xl:1140};function m(t){n("update:open",t)}function c(){n("update:open",!1),n("cancel")}return(t,l)=>{const f=a("a-form"),u=a("a-spin"),p=a("a-modal");return k(),g(p,{open:e.open,title:e.id?e.editTitle:e.addTitle,width:s[e.width],"confirm-loading":e.submitting,"ok-text":e.okText,"cancel-text":"انصراف","mask-closable":!1,"wrap-class-name":"base-form-modal","onUpdate:open":m,onOk:l[0]||(l[0]=x=>t.$emit("submit")),onCancel:c},{default:o(()=>[i(u,{spinning:e.loading},{default:o(()=>[i(f,{layout:"vertical"},{default:o(()=>[w(t.$slots,"default")]),_:3})]),_:3},8,["spinning"])]),_:3},8,["open","title","width","confirm-loading","ok-text"])}}});export{b as _};
@@ -1 +0,0 @@
import{d as P,v as S,x as u,a as v,p as n,w as l,k as _,r as s,y as T,o as d,m as $,F as w,b as C,e as g,A,M as k,c as x,t as B}from"./index-QQcP5-AC.js";const D={id:"page-user-list"},E={class:"grid-3"},z=P({__name:"ChangePercent",setup(j){const y=_([]),b=_([]),p=_("-"),f=_(!1),a=T({category_id:void 0,brand_id:void 0,percent:void 0});function h(t,e){var r,c;return String(((c=(r=e==null?void 0:e.children)==null?void 0:r[0])==null?void 0:c.children)??"").toLowerCase().indexOf(t.toLowerCase())>=0}async function m(){try{const{data:t}=await u.post("change/percent/count",{category_id:a.category_id,brand_id:a.brand_id});p.value=t.count}catch(t){console.log("e ==> ",t)}}async function L(){if(a.percent===void 0||a.percent===null){A.error("لطفا اجرت درصدی را وارد کنید");return}try{f.value=!0;const{data:t}=await u.post("change/all",{percent:a.percent,category_id:a.category_id,brand_id:a.brand_id});k.success({title:"تغییرات با موفقیت اعمال شد",okText:"بستن"}),p.value=t.updated}catch(t){k.success({title:"خطا در اعمال تغییرات"}),console.log(t)}finally{f.value=!1}}return S(async()=>{try{const[t,e]=await Promise.all([u.get("brand"),u.post("category")]);y.value=t.data,b.value=(e.data.rows??e.data).map(r=>({id:r.id,title:r.type_str?`${r.title} (${r.type_str}) `:r.title}))}catch(t){console.log(t)}m()}),(t,e)=>{const r=s("a-select-option"),c=s("a-select"),i=s("a-form-item"),M=s("a-input"),N=s("a-input-number"),U=s("a-button"),V=s("a-popconfirm"),F=s("a-form"),O=s("a-card");return d(),v("div",D,[n(O,{title:"تغییر درصد محصولات",class:"page-card"},{default:l(()=>[n(F,{layout:"vertical"},{default:l(()=>[$("div",E,[n(i,{label:"دسته بندی"},{default:l(()=>[n(c,{value:a.category_id,"onUpdate:value":e[0]||(e[0]=o=>a.category_id=o),"allow-clear":"","show-search":"",placeholder:"انتخاب کنید","filter-option":h,onChange:m},{default:l(()=>[(d(!0),v(w,null,C(b.value,o=>(d(),x(r,{key:o.id,value:o.id},{default:l(()=>[g(B(o.title),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1}),n(i,{label:"برند"},{default:l(()=>[n(c,{value:a.brand_id,"onUpdate:value":e[1]||(e[1]=o=>a.brand_id=o),"allow-clear":"","show-search":"",placeholder:"انتخاب کنید","filter-option":h,onChange:m},{default:l(()=>[(d(!0),v(w,null,C(y.value,o=>(d(),x(r,{key:o.id,value:o.id},{default:l(()=>[g(B(o.title),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1}),n(i,{label:"تعداد یافت شده"},{default:l(()=>[n(M,{value:p.value,disabled:""},null,8,["value"])]),_:1}),n(i,{label:"اجرت درصدی"},{default:l(()=>[n(N,{value:a.percent,"onUpdate:value":e[2]||(e[2]=o=>a.percent=o),class:"w-full",step:.1},null,8,["value"])]),_:1})]),n(V,{title:"آیا از اعمال تغییرات مطمئن هستید؟","ok-text":"بله","cancel-text":"خیر",onConfirm:L},{default:l(()=>[n(U,{type:"primary",class:"form-submit",loading:f.value},{default:l(()=>[...e[3]||(e[3]=[g("اعمال تغییرات",-1)])]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1})])}}});export{z as default};
@@ -0,0 +1 @@
import{d as P,v as S,x as u,b as m,c as o,w as l,l as _,r as c,y as T,o as d,n as $,F as w,e as C,f as g,z,M as k,a as x,t as B}from"./index-DuH3OOPw.js";const D={id:"page-user-list"},E={class:"grid-3"},A=P({__name:"ChangePercent",setup(j){const y=_([]),b=_([]),p=_("-"),f=_(!1),a=T({category_id:void 0,brand_id:void 0,percent:void 0});function h(t,e){var r,s;return String(((s=(r=e==null?void 0:e.children)==null?void 0:r[0])==null?void 0:s.children)??"").toLowerCase().indexOf(t.toLowerCase())>=0}async function v(){try{const{data:t}=await u.post("change/percent/count",{category_id:a.category_id,brand_id:a.brand_id});p.value=t.count}catch(t){console.log("e ==> ",t)}}async function L(){if(a.percent===void 0||a.percent===null){z.error("لطفا اجرت درصدی را وارد کنید");return}try{f.value=!0;const{data:t}=await u.post("change/all",{percent:a.percent,category_id:a.category_id,brand_id:a.brand_id});k.success({title:"تغییرات با موفقیت اعمال شد",okText:"بستن"}),p.value=t.updated}catch(t){k.success({title:"خطا در اعمال تغییرات"}),console.log(t)}finally{f.value=!1}}return S(async()=>{try{const[t,e]=await Promise.all([u.get("brand"),u.post("category")]);y.value=t.data,b.value=(e.data.rows??e.data).map(r=>({id:r.id,title:r.type_str?`${r.title} (${r.type_str}) `:r.title}))}catch(t){console.log(t)}v()}),(t,e)=>{const r=c("a-select-option"),s=c("a-select"),i=c("a-form-item"),M=c("a-input"),N=c("a-input-number"),U=c("a-button"),V=c("a-popconfirm"),F=c("a-form"),O=c("a-card");return d(),m("div",D,[o(O,{title:"تغییر درصد محصولات"},{default:l(()=>[o(F,{layout:"vertical"},{default:l(()=>[$("div",E,[o(i,{label:"دسته بندی"},{default:l(()=>[o(s,{value:a.category_id,"onUpdate:value":e[0]||(e[0]=n=>a.category_id=n),"allow-clear":"","show-search":"",placeholder:"انتخاب کنید","filter-option":h,onChange:v},{default:l(()=>[(d(!0),m(w,null,C(b.value,n=>(d(),x(r,{key:n.id,value:n.id},{default:l(()=>[g(B(n.title),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1}),o(i,{label:"برند"},{default:l(()=>[o(s,{value:a.brand_id,"onUpdate:value":e[1]||(e[1]=n=>a.brand_id=n),"allow-clear":"","show-search":"",placeholder:"انتخاب کنید","filter-option":h,onChange:v},{default:l(()=>[(d(!0),m(w,null,C(y.value,n=>(d(),x(r,{key:n.id,value:n.id},{default:l(()=>[g(B(n.title),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1}),o(i,{label:"تعداد یافت شده"},{default:l(()=>[o(M,{value:p.value,disabled:""},null,8,["value"])]),_:1}),o(i,{label:"اجرت درصدی"},{default:l(()=>[o(N,{value:a.percent,"onUpdate:value":e[2]||(e[2]=n=>a.percent=n),class:"w-full",step:.1},null,8,["value"])]),_:1})]),o(V,{title:"آیا از اعمال تغییرات مطمئن هستید؟","ok-text":"بله","cancel-text":"خیر",onConfirm:L},{default:l(()=>[o(U,{type:"primary",loading:f.value},{default:l(()=>[...e[3]||(e[3]=[g("اعمال تغییرات",-1)])]),_:1},8,["loading"])]),_:1})]),_:1})]),_:1})])}}});export{A as default};
@@ -1 +0,0 @@
import{d as m,o as v,c as g,g as w,r as k}from"./index-QQcP5-AC.js";const _=m({__name:"CityTreeSelect",props:{value:{},cities:{},parentPicker:{type:Boolean},excludeId:{},placeholder:{}},emits:["update:value","change"],setup(i,{emit:u}){const n=i,d=u,h=w(()=>{const l=new Set(n.cities.map(e=>e.id)),o=new Map;for(const e of n.cities){const a=e.parent&&l.has(e.parent)&&e.parent!==e.id?e.parent:null;o.has(a)||o.set(a,[]),o.get(a).push(e)}const r=new Set,p=(e,a)=>(o.get(e)??[]).filter(t=>!r.has(t.id)&&!(n.parentPicker&&t.id===n.excludeId)).map(t=>{r.add(t.id);const c=p(t.id,a+1),s={value:t.id,key:t.id,title:t.title};return c.length&&(s.children=c),!n.parentPicker&&a===0&&c.length&&(s.disabled=!0),s});return p(null,0)});function f(l){d("update:value",l??void 0),d("change",l??void 0)}return(l,o)=>{const r=k("a-tree-select");return v(),g(r,{value:i.value??void 0,"tree-data":h.value,"show-search":"","allow-clear":"","tree-default-expand-all":"","tree-node-filter-prop":"title","dropdown-style":{maxHeight:"400px",overflow:"auto"},placeholder:i.placeholder??"شهر را انتخاب کنید",class:"w-full","onUpdate:value":f},null,8,["value","tree-data","placeholder"])}}});export{_};
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.mr-3[data-v-6332a204]{margin-left:.75rem}.comments-list ul[data-v-6332a204]{list-style:none;padding-right:0}.comment-item[data-v-6332a204]{display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1rem}.comment-item .img[data-v-6332a204]{width:65px;height:65px;display:flex;align-items:center;justify-content:center}.comment-body[data-v-6332a204]{flex:1;min-width:240px}.comment-body .time[data-v-6332a204]{font-size:12px;color:#888}.border[data-v-6332a204]{border:1px solid grey;border-radius:50%}
-1
View File
@@ -1 +0,0 @@
.mr-3[data-v-2efe4af0]{margin-left:.75rem}.comments-list ul[data-v-2efe4af0]{list-style:none;padding-right:0}.comment-item[data-v-2efe4af0]{display:flex;gap:1rem;flex-wrap:wrap;margin-bottom:1rem}.comment-item .img[data-v-2efe4af0]{width:65px;height:65px;display:flex;align-items:center;justify-content:center}.comment-body[data-v-2efe4af0]{flex:1;min-width:240px}.comment-body .time[data-v-2efe4af0]{font-size:12px;color:#888}.border[data-v-2efe4af0]{border:1px solid grey;border-radius:50%}@media (max-width: 575px){.comment-item[data-v-2efe4af0]{gap:10px;flex-wrap:nowrap;margin-bottom:12px}.comment-item .img[data-v-2efe4af0]{width:40px;height:40px;flex-shrink:0}.comment-body[data-v-2efe4af0]{min-width:0}}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.row[data-v-787cb7a2]{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}.tags[data-v-787cb7a2]{display:flex;gap:.5rem;flex-wrap:wrap}.slider-toolbar[data-v-787cb7a2]{display:flex;justify-content:space-between;align-items:center;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}.slider-count[data-v-787cb7a2]{color:#00000073}.slider-grid[data-v-787cb7a2]{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,220px),1fr));gap:16px}.slide-card[data-v-787cb7a2]{display:flex;flex-direction:column;gap:10px;min-width:0;padding:12px;border:1px solid #f0f0f0;border-radius:8px;background:#fff}.slide-card__head[data-v-787cb7a2]{display:flex;justify-content:space-between;align-items:center}.slide-card__title[data-v-787cb7a2]{font-weight:600}.slide-card__media[data-v-787cb7a2]{height:140px}.slide-card__media[data-v-787cb7a2] .image-upload,.slide-card__media[data-v-787cb7a2] .ant-upload-wrapper,.slide-card__media[data-v-787cb7a2] .ant-upload-list,.slide-card__media[data-v-787cb7a2] .ant-upload-list-item-container,.slide-card__media[data-v-787cb7a2] .ant-upload.ant-upload-select{width:100%!important;height:100%!important;margin:0!important}.slide-card__media[data-v-787cb7a2] .ant-upload-wrapper{display:block}.slide-card__media[data-v-787cb7a2] .ant-upload-list-item-thumbnail img{object-fit:cover}.mt-4[data-v-787cb7a2]{margin-top:1rem}@media (max-width: 575px){.row>.ant-input[data-v-787cb7a2]{flex:1 1 140px;max-width:none!important}.mt-4[data-v-787cb7a2]{margin-top:12px}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.row[data-v-60fd2863]{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap}.tags[data-v-60fd2863]{display:flex;gap:.5rem;flex-wrap:wrap}.slide[data-v-60fd2863]{border:1px solid #f0f0f0;border-radius:6px;padding:.75rem;margin-bottom:.75rem}.mobile-c[data-v-60fd2863]{margin:0 0 .5rem}.mt-4[data-v-60fd2863]{margin-top:1rem}
@@ -0,0 +1 @@
import{c as u,I as l}from"./index-DuH3OOPw.js";var d={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-792 72h752v120H136V232zm752 560H136V440h752v352zm-237-64h165c4.4 0 8-3.6 8-8v-72c0-4.4-3.6-8-8-8H651c-4.4 0-8 3.6-8 8v72c0 4.4 3.6 8 8 8z"}}]},name:"credit-card",theme:"outlined"};function c(r){for(var t=1;t<arguments.length;t++){var e=arguments[t]!=null?Object(arguments[t]):{},n=Object.keys(e);typeof Object.getOwnPropertySymbols=="function"&&(n=n.concat(Object.getOwnPropertySymbols(e).filter(function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),n.forEach(function(a){o(r,a,e[a])})}return r}function o(r,t,e){return t in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}var i=function(t,e){var n=c({},t,e.attrs);return u(l,c({},n,{icon:d}),null)};i.displayName="CreditCardOutlined";i.inheritAttrs=!1;export{i as C};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
.stats[data-v-9dee124b]{display:flex;gap:.5rem}.mt-4[data-v-9dee124b]{margin-top:1rem}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
import{j as y}from"./filters-CWowq3Uw.js";import{d as b,v as k,x as C,a as l,w as t,l as I,y as j,r as e,o,c as d,f as i,t as _,b as v,h as w,F as h,i as B}from"./index-DuH3OOPw.js";import"./index-BHIiPvAE.js";const F=b({__name:"DashboardAnalytics",setup(N){const n=I(!1),r=j({lastOrder:[]}),m=[{title:"شماره سفارش",dataIndex:"name"},{title:"مشتری",dataIndex:"customer"},{title:"وزن",dataIndex:"w"},{title:"وضعیت",dataIndex:"sts_str"},{title:"تاریخ",dataIndex:"caj"}];function p(a){return{success:"green",warning:"orange",danger:"red",primary:"blue"}[a]??""}return k(async()=>{try{n.value=!0;const{data:a}=await C.get("get-analytics-tak");Object.assign(r,a)}catch(a){console.error(a)}finally{n.value=!1}}),(a,O)=>{const u=e("a-tag"),g=e("a-table"),f=e("a-card"),x=e("a-spin");return o(),l(x,{spinning:n.value},{default:t(()=>[d(f,{title:"سفارشات هفته جاری"},{default:t(()=>[d(g,{size:"small","data-source":r.lastOrder,columns:m,pagination:{pageSize:15},"row-key":"id"},{bodyCell:t(({column:c,record:s})=>[c.dataIndex==="sts_str"?(o(),l(u,{key:0,color:p(s.statusColor)},{default:t(()=>[i(_(s.sts_str),1)]),_:2},1032,["color"])):c.dataIndex==="caj"?(o(),v(h,{key:1},[i(_(w(y)(s.caj)),1)],64)):B("",!0)]),_:1},8,["data-source"])]),_:1})]),_:1},8,["spinning"])}}});export{F as default};
@@ -0,0 +1 @@
import{d as y,o as d,a as _,w as e,n,s as N,g as D,t as c,p as k,r as o,v as $,x as j,l as L,y as z,c as a,f as u,b as T,h as V,F as E,i as M}from"./index-DuH3OOPw.js";import{B as v,D as F,S as U,T as q}from"./TeamOutlined-CEYpODLG.js";import{_ as O}from"./_plugin-vue_export-helper-DlAUqK2U.js";import{j as A}from"./filters-CWowq3Uw.js";import"./index-BHIiPvAE.js";const G={class:"stat-row"},H={class:"stat-value"},J={class:"stat-title"},K=y({__name:"StatisticsCardLine",props:{icon:{},statistic:{},statisticTitle:{},color:{default:"primary"},hideChart:{type:Boolean,default:!0}},setup(i){const l=i,t={UsersIcon:q,ShoppingBagIcon:U,DollarSignIcon:F,BarChartIcon:v},p=k(()=>t[l.icon??""]??v);return(C,I)=>{const s=o("a-card");return d(),_(s,{class:"stat-card"},{default:e(()=>[n("div",G,[n("span",{class:N(["stat-icon",`icon-${i.color}`])},[(d(),_(D(p.value)))],2),n("div",null,[n("h2",H,c(i.statistic),1),n("span",J,c(i.statisticTitle),1)])])]),_:1})}}}),h=O(K,[["__scopeId","data-v-ebefdc7b"]]),P={class:"grid-3"},Q={class:"grid-2 mt-4"},R=y({__name:"DashboardECommerce",setup(i){const l=L(!1),t=z({customerCount:0,allgram:0,ordercount:0,reagent:[],lastOrder:[],sumLastOrder:0,sumLastOrderMonth:0}),p=[{title:"کارشناس فروش",dataIndex:"name"},{title:"میزان فروش (گرم)",dataIndex:"grams_sell"},{title:"تعداد سفارش",dataIndex:"order_count"}],C=[{title:"شماره سفارش",dataIndex:"name"},{title:"مشتری",dataIndex:"customer"},{title:"وزن",dataIndex:"w"},{title:"وضعیت",dataIndex:"sts_str"},{title:"تاریخ",dataIndex:"caj"}];function I(s){return{success:"green",warning:"orange",danger:"red",primary:"blue"}[s]??""}return $(async()=>{try{l.value=!0;const{data:s}=await j.get("get-analytics");Object.assign(t,s),t.reagent.sort((m,r)=>Number(r.grams_sell)-Number(m.grams_sell)),t.lastOrder.sort((m,r)=>Number(r.w)-Number(m.w))}catch(s){console.error(s)}finally{l.value=!1}}),(s,m)=>{const r=o("a-table"),g=o("a-card"),b=o("a-descriptions-item"),w=o("a-descriptions"),B=o("a-tag"),S=o("a-spin");return d(),_(S,{spinning:l.value},{default:e(()=>[n("div",P,[a(h,{icon:"UsersIcon",color:"warning",statistic:`${t.customerCount} نفر `,"statistic-title":"تعداد مشتریان ثبت نامی در 30 روز گذشته"},null,8,["statistic"]),a(h,{icon:"ShoppingBagIcon",statistic:`${t.allgram} گرم `,"statistic-title":"طلای فروش رفته در 30 روز گذشته"},null,8,["statistic"]),a(h,{icon:"ShoppingBagIcon",color:"warning",statistic:`${t.ordercount} سفارش `,"statistic-title":"تعداد سفارشات 30 روز گذشته"},null,8,["statistic"])]),n("div",Q,[a(g,{title:"عملکرد کارشناسان فروش"},{default:e(()=>[a(r,{size:"small","data-source":t.reagent,columns:p,pagination:!1,"row-key":"id"},null,8,["data-source"])]),_:1}),a(g,{title:"میزان فروش"},{default:e(()=>[a(w,{bordered:"",size:"small",column:1},{default:e(()=>[a(b,{label:"تعداد سفارش (۳۰ روز)"},{default:e(()=>[u(c(t.sumLastOrder),1)]),_:1}),a(b,{label:"مجموع وزن (۳۰ روز)"},{default:e(()=>[u(c(t.sumLastOrderMonth)+" گرم ",1)]),_:1})]),_:1})]),_:1})]),a(g,{title:"سفارشات هفته جاری",class:"mt-4"},{default:e(()=>[a(r,{size:"small","data-source":t.lastOrder,columns:C,pagination:{pageSize:10},"row-key":"id"},{bodyCell:e(({column:x,record:f})=>[x.dataIndex==="sts_str"?(d(),_(B,{key:0,color:I(f.statusColor)},{default:e(()=>[u(c(f.sts_str),1)]),_:2},1032,["color"])):x.dataIndex==="caj"?(d(),T(E,{key:1},[u(c(V(A)(f.caj)),1)],64)):M("",!0)]),_:1},8,["data-source"])]),_:1})]),_:1},8,["spinning"])}}}),at=O(R,[["__scopeId","data-v-a6b0d1e2"]]);export{at as default};
@@ -0,0 +1 @@
.stat-card[data-v-ebefdc7b]{height:100%}.stat-row[data-v-ebefdc7b]{display:flex;align-items:center;gap:1rem}.stat-icon[data-v-ebefdc7b]{display:inline-flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:50%;font-size:22px;flex-shrink:0}.icon-primary[data-v-ebefdc7b]{background:#1c2d561a;color:#1c2d56}.icon-warning[data-v-ebefdc7b]{background:#ff9f431f;color:#ff9f43}.icon-success[data-v-ebefdc7b]{background:#28c76f1f;color:#28c76f}.icon-danger[data-v-ebefdc7b]{background:#ea54551f;color:#ea5455}.stat-value[data-v-ebefdc7b]{margin:0;font-size:20px;color:#1c2d56}.stat-title[data-v-ebefdc7b]{font-size:12px;color:#888}.mt-4[data-v-a6b0d1e2]{margin-top:1rem}
-1
View File
@@ -1 +0,0 @@
.hint[data-v-3863c3bc]{color:#ea5455;font-size:11px}
-1
View File
@@ -1 +0,0 @@
import{m}from"./index-DT4Uze1z.js";import{d as _,l as V,o as r,c as Y,w as h,p as D,a as j,t as M,f as b,k as d,g as k,r as i,_ as x}from"./index-QQcP5-AC.js";const y={key:0,class:"hint"},g=_({__name:"DateInput",props:{label:{},modelValue:{},placeholder:{default:"مثال: ۱۴۰۳/۰۵/۱۲"}},emits:["update:modelValue"],setup(o,{emit:p}){const n=o,c=p,t=d(void 0),a=d(""),f=k(()=>{if(t.value!==void 0)return t.value;if(!n.modelValue)return"";const e=m(n.modelValue);return e.isValid()?e.format("jYYYY/jMM/jDD"):""});V(()=>n.modelValue,()=>{t.value=void 0});function v(e){if(t.value=e,!e){a.value="",c("update:modelValue",void 0);return}const u=e.replace(/[۰-۹]/g,s=>String("۰۱۲۳۴۵۶۷۸۹".indexOf(s))),l=m(u,"jYYYY/jMM/jDD");l.isValid()?(a.value="",c("update:modelValue",l.format("YYYY-MM-DD"))):a.value="تاریخ معتبر نیست"}return(e,u)=>{const l=i("a-input"),s=i("a-form-item");return r(),Y(s,{label:o.label},{default:h(()=>[D(l,{value:f.value,placeholder:o.placeholder,"allow-clear":"","onUpdate:value":v},null,8,["value","placeholder"]),a.value?(r(),j("small",y,M(a.value),1)):b("",!0)]),_:1},8,["label"])}}}),I=x(g,[["__scopeId","data-v-3863c3bc"]]);export{I as D};
+1
View File
@@ -0,0 +1 @@
.hint[data-v-42b77d70]{color:#ea5455;font-size:11px}
+1
View File
@@ -0,0 +1 @@
import{m}from"./index-BHIiPvAE.js";import{d as _,m as V,o as r,a as Y,w as h,c as D,b,t as j,i as M,l as i,p as x,r as d}from"./index-DuH3OOPw.js";import{_ as y}from"./_plugin-vue_export-helper-DlAUqK2U.js";const k={key:0,class:"hint"},C=_({__name:"DateInput",props:{label:{},modelValue:{},placeholder:{default:"مثال: ۱۴۰۳/۰۵/۱۲"}},emits:["update:modelValue"],setup(o,{emit:p}){const n=o,u=p,t=i(void 0),a=i(""),f=x(()=>{if(t.value!==void 0)return t.value;if(!n.modelValue)return"";const e=m(n.modelValue);return e.isValid()?e.format("jYYYY/jMM/jDD"):""});V(()=>n.modelValue,()=>{t.value=void 0});function v(e){if(t.value=e,!e){a.value="",u("update:modelValue",void 0);return}const c=e.replace(/[۰-۹]/g,s=>String("۰۱۲۳۴۵۶۷۸۹".indexOf(s))),l=m(c,"jYYYY/jMM/jDD");l.isValid()?(a.value="",u("update:modelValue",l.format("YYYY-MM-DD"))):a.value="تاریخ معتبر نیست"}return(e,c)=>{const l=d("a-input"),s=d("a-form-item");return r(),Y(s,{label:o.label},{default:h(()=>[D(l,{value:f.value,placeholder:o.placeholder,"allow-clear":"","onUpdate:value":v},null,8,["value","placeholder"]),a.value?(r(),b("small",k,j(a.value),1)):M("",!0)]),_:1},8,["label"])}}}),w=y(C,[["__scopeId","data-v-42b77d70"]]);export{w as D};
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./Add.vue_vue_type_script_setup_true_lang-vc2KlLTI.js";import{d as t,a as e,o as r}from"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-BTbAO94-.js";const i=t({__name:"Edit",setup(a){return(_,m)=>(r(),e(o))}});export{i as default};
-1
View File
@@ -1 +0,0 @@
import{_ as o}from"./Add.vue_vue_type_script_setup_true_lang-C2L5ywNo.js";import{d as t,c as r,o as e}from"./index-QQcP5-AC.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-gPDguZj2.js";import"./Icon-B9FXOmlS.js";const f=t({__name:"Edit",setup(a){return(m,p)=>(e(),r(o))}});export{f as default};
-1
View File
@@ -1 +0,0 @@
import{_ as e}from"./AddBranches.vue_vue_type_script_setup_true_lang-Du71-Nbh.js";import{d as o,c as r,o as t}from"./index-QQcP5-AC.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";const p=o({__name:"EditBranches",setup(a){return(c,n)=>(t(),r(e))}});export{p as default};
+1
View File
@@ -0,0 +1 @@
import{_ as e}from"./AddBranches.vue_vue_type_script_setup_true_lang-BQagavGv.js";import{d as o,a,o as r}from"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";const p=o({__name:"EditBranches",setup(t){return(n,s)=>(r(),a(e))}});export{p as default};
@@ -0,0 +1 @@
import{_ as o}from"./bulkCustomer.vue_vue_type_script_setup_true_lang-DgWWO9pL.js";import{d as e,a as t,o as r}from"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";const p=e({__name:"EditBulkCustomer",setup(a){return(s,_)=>(r(),t(o))}});export{p as default};
@@ -1 +0,0 @@
import{_ as o}from"./bulkCustomer.vue_vue_type_script_setup_true_lang-UhBsQkgG.js";import{d as r,c as t,o as e}from"./index-QQcP5-AC.js";import"./Icon-B9FXOmlS.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-gPDguZj2.js";import"./CityTreeSelect.vue_vue_type_script_setup_true_lang-qW84W-31.js";import"./DateInput-COU3oLhL.js";import"./index-DT4Uze1z.js";const l=r({__name:"EditBulkCustomer",setup(m){return(p,a)=>(e(),t(o,{class:"page-card"}))}});export{l as default};
+1
View File
@@ -0,0 +1 @@
import o from"./AddInvoice-CkxeyCUi.js";import{d as t,a as e,o as r}from"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";import"./model-DWeeFWcT.js";import"./SettingOutlined-MJAt5cqt.js";import"./_plugin-vue_export-helper-DlAUqK2U.js";const f=t({__name:"EditInvoice",setup(p){return(i,m)=>(r(),e(o))}});export{f as default};
-1
View File
@@ -1 +0,0 @@
import o from"./AddInvoice-C5JsFepN.js";import{d as t,c as r,o as e}from"./index-QQcP5-AC.js";import"./Icon-B9FXOmlS.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";import"./model-m8OsHE_l.js";import"./filters-B9z4I-pb.js";import"./index-DT4Uze1z.js";const u=t({__name:"EditInvoice",setup(p){return(i,m)=>(e(),r(o))}});export{u as default};
-1
View File
@@ -1 +0,0 @@
import e from"./AddProduct-CjWrD0FL.js";import{d as o,a as t,p as r,o as a}from"./index-QQcP5-AC.js";import"./Icon-B9FXOmlS.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-gPDguZj2.js";const _=o({__name:"EditProduct",setup(c){return(n,p)=>(a(),t("div",null,[r(e)]))}});export{_ as default};
+1
View File
@@ -0,0 +1 @@
import e from"./AddProduct-Dx2YzUG6.js";import{d as o,b as t,c as r,o as c}from"./index-DuH3OOPw.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-BTbAO94-.js";import"./_plugin-vue_export-helper-DlAUqK2U.js";const _=o({__name:"EditProduct",setup(a){return(n,d)=>(c(),t("div",null,[r(e)]))}});export{_ as default};
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{R as e}from"./ReagentCustomer-DkAyHy8l.js";import{d as t,c as o,o as r}from"./index-QQcP5-AC.js";import"./Icon-B9FXOmlS.js";import"./CityTreeSelect.vue_vue_type_script_setup_true_lang-qW84W-31.js";import"./ImageUploadInput.vue_vue_type_script_setup_true_lang-gPDguZj2.js";const d=t({__name:"EditReagent",setup(a){return(p,s)=>(r(),o(e,{class:"page-card"}))}});export{d as default};
+1
View File
@@ -0,0 +1 @@
.avatar-cell[data-v-03ed8085]{grid-row:span 2;display:flex;justify-content:center}
@@ -0,0 +1 @@
import{d as C,j as N,k as V,v as D,x as m,r,o as p,a as _,w as i,c as s,A as F,h as M,n as z,b as A,F as I,e as L,f as b,t as Q,l as w,y as k,z as x}from"./index-DuH3OOPw.js";const T={class:"grid-3"},f="لطفا مقدار را وارد کنید",q=C({__name:"singleCustomer",setup(S){const v=N(),h=V(),c=v.params.id,d=w(!1),y=w([]),a=k({fname:"",lname:"",phone:"",city_id:void 0}),l=k({});function j(t,e){var n,u;return String(((u=(n=e==null?void 0:e.children)==null?void 0:n[0])==null?void 0:u.children)??"").includes(t)}async function g(){var t,e;if(Object.keys(l).forEach(n=>delete l[n]),a.fname||(l.fname=f),a.lname||(l.lname=f),a.phone||(l.phone=f),!Object.keys(l).length)try{d.value=!0;const n={...a,name:`${a.fname} ${a.lname}`.trim(),mobile:a.phone};c?await m.put(`single/${c}`,n):await m.post("single",n),x.info("اطلاعات با موفقیت به ثبت رسید."),h.go(-1)}catch(n){x.error(((e=(t=n==null?void 0:n.response)==null?void 0:t.data)==null?void 0:e.message)??"خطا در ثبت اطلاعات.")}finally{d.value=!1}}return D(async()=>{try{const{data:t}=await m.post("cities/list");y.value=t.rows??[]}catch(t){console.log(t)}if(c)try{const{data:t}=await m.get(`customer/${c}`),e=String(t.name??"").trim().split(" ");Object.assign(a,{fname:t.fname||e[0]||"",lname:t.lname||e.slice(1).join(" "),phone:t.mobile??"",city_id:t.city_id??void 0})}catch(t){console.log(t)}}),(t,e)=>{const n=r("a-input"),u=r("a-form-item"),E=r("a-select-option"),U=r("a-select"),$=r("a-button"),B=r("a-card"),O=r("a-form"),R=r("a-spin");return p(),_(R,{spinning:d.value},{default:i(()=>[s(O,{layout:"vertical",onSubmit:F(g,["prevent"])},{default:i(()=>[s(B,{title:M(c)?"ویرایش مشتری":"افزودن مشتری",class:"mb-base"},{default:i(()=>[z("div",T,[s(u,{label:"نام ","validate-status":l.fname?"error":"",help:l.fname},{default:i(()=>[s(n,{value:a.fname,"onUpdate:value":e[0]||(e[0]=o=>a.fname=o)},null,8,["value"])]),_:1},8,["validate-status","help"]),s(u,{label:"نام خانوادگی","validate-status":l.lname?"error":"",help:l.lname},{default:i(()=>[s(n,{value:a.lname,"onUpdate:value":e[1]||(e[1]=o=>a.lname=o)},null,8,["value"])]),_:1},8,["validate-status","help"]),s(u,{label:"شماره تلفن همراه","validate-status":l.phone?"error":"",help:l.phone},{default:i(()=>[s(n,{value:a.phone,"onUpdate:value":e[2]||(e[2]=o=>a.phone=o)},null,8,["value"])]),_:1},8,["validate-status","help"]),s(u,{label:"شهر"},{default:i(()=>[s(U,{value:a.city_id,"onUpdate:value":e[3]||(e[3]=o=>a.city_id=o),"show-search":"","allow-clear":"",placeholder:"انتخاب کنید","filter-option":j},{default:i(()=>[(p(!0),A(I,null,L(y.value,o=>(p(),_(E,{key:o.id,value:o.id},{default:i(()=>[b(Q(o.title),1)]),_:2},1032,["value"]))),128))]),_:1},8,["value"])]),_:1})]),s($,{type:"primary",onClick:g},{default:i(()=>[...e[4]||(e[4]=[b("ثبت اطلاعات",-1)])]),_:1})]),_:1},8,["title"])]),_:1})]),_:1},8,["spinning"])}}}),H=C({__name:"EditSingleCustomer",setup(S){return(v,h)=>(p(),_(q))}});export{H as default};
@@ -1 +0,0 @@
import{S as o}from"./singleCustomer-XwT9-VyX.js";import{d as r,c as e,o as t}from"./index-QQcP5-AC.js";import"./Icon-B9FXOmlS.js";import"./CityTreeSelect.vue_vue_type_script_setup_true_lang-qW84W-31.js";import"./DateInput-COU3oLhL.js";import"./index-DT4Uze1z.js";const u=r({__name:"EditSingleCustomer",setup(m){return(a,p)=>(t(),e(o,{class:"page-card"}))}});export{u as default};
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./AddWorkshop.vue_vue_type_script_setup_true_lang-Ddx471SB.js";import{d as e,a as r,o as t}from"./index-DuH3OOPw.js";import"./BaseFormModal.vue_vue_type_script_setup_true_lang-zAo7Oyp5.js";const m=e({__name:"EditWorkshop",setup(a){return(s,_)=>(t(),r(o))}});export{m as default};
-1
View File
@@ -1 +0,0 @@
import{_ as o}from"./AddWorkshop.vue_vue_type_script_setup_true_lang-C26658Z1.js";import{d as e,c as r,o as t}from"./index-QQcP5-AC.js";import"./BaseFormModal.vue_vue_type_style_index_0_lang-BOlb0dJf.js";const m=e({__name:"EditWorkshop",setup(a){return(s,_)=>(t(),r(o))}});export{m as default};
+1
View File
@@ -0,0 +1 @@
.full-page-msg[data-v-4851d61e]{min-height:100vh;display:flex;align-items:center;justify-content:center}
-1
View File
@@ -1 +0,0 @@
.full-page-msg[data-v-82a95474]{min-height:100vh;display:flex;align-items:center;justify-content:center}
+1
View File
@@ -0,0 +1 @@
import{_}from"./_plugin-vue_export-helper-DlAUqK2U.js";import{b as c,c as e,w as o,f as l,r as s,o as p}from"./index-DuH3OOPw.js";const u={},d={class:"full-page-msg"};function i(r,t){const n=s("a-button"),a=s("a-result");return p(),c("div",d,[e(a,{status:"404",title:"صفحه مورد نظر یافت نشد","sub-title":"آدرسی که وارد کرده‌اید وجود ندارد."},{extra:o(()=>[e(n,{type:"primary",onClick:t[0]||(t[0]=m=>r.$router.push("/"))},{default:o(()=>[...t[1]||(t[1]=[l("بازگشت به خانه",-1)])]),_:1})]),_:1})])}const b=_(u,[["render",i],["__scopeId","data-v-4851d61e"]]);export{b as default};
-1
View File
@@ -1 +0,0 @@
import{_,a as l,p as e,w as o,e as c,r as s,o as p}from"./index-QQcP5-AC.js";const u={},d={class:"full-page-msg"};function i(r,t){const a=s("a-button"),n=s("a-result");return p(),l("div",d,[e(n,{status:"404",title:"صفحه مورد نظر یافت نشد","sub-title":"آدرسی که وارد کرده‌اید وجود ندارد."},{extra:o(()=>[e(a,{type:"primary",onClick:t[0]||(t[0]=m=>r.$router.push("/"))},{default:o(()=>[...t[1]||(t[1]=[c("بازگشت به خانه",-1)])]),_:1})]),_:1})])}const x=_(u,[["render",i],["__scopeId","data-v-82a95474"]]);export{x as default};
+1
View File
@@ -0,0 +1 @@
import{_}from"./_plugin-vue_export-helper-DlAUqK2U.js";import{b as c,c as e,w as o,f as l,r as s,o as p}from"./index-DuH3OOPw.js";const u={},i={class:"full-page-msg"};function m(r,t){const a=s("a-button"),n=s("a-result");return p(),c("div",i,[e(n,{status:"500",title:"خطای سرور","sub-title":"مشکلی در سرور رخ داده است. لطفاً بعداً تلاش کنید."},{extra:o(()=>[e(a,{type:"primary",onClick:t[0]||(t[0]=d=>r.$router.push("/"))},{default:o(()=>[...t[1]||(t[1]=[l("بازگشت به خانه",-1)])]),_:1})]),_:1})])}const x=_(u,[["render",m],["__scopeId","data-v-abbac884"]]);export{x as default};
+1
View File
@@ -0,0 +1 @@
.full-page-msg[data-v-abbac884]{min-height:100vh;display:flex;align-items:center;justify-content:center}
-1
View File
@@ -1 +0,0 @@
.full-page-msg[data-v-1080291a]{min-height:100vh;display:flex;align-items:center;justify-content:center}
-1
View File
@@ -1 +0,0 @@
import{_,a as l,p as e,w as o,e as c,r as s,o as p}from"./index-QQcP5-AC.js";const u={},d={class:"full-page-msg"};function i(r,t){const a=s("a-button"),n=s("a-result");return p(),l("div",d,[e(n,{status:"500",title:"خطای سرور","sub-title":"مشکلی در سرور رخ داده است. لطفاً بعداً تلاش کنید."},{extra:o(()=>[e(a,{type:"primary",onClick:t[0]||(t[0]=m=>r.$router.push("/"))},{default:o(()=>[...t[1]||(t[1]=[c("بازگشت به خانه",-1)])]),_:1})]),_:1})])}const x=_(u,[["render",i],["__scopeId","data-v-1080291a"]]);export{x as default};
@@ -0,0 +1 @@
import{d as E,a as i,w as l,r as C,o as s,c as d,W as P,n as w,h as r,E as h,K as B,J as L,V as S,l as p,x as V,z as y,k as A}from"./index-DuH3OOPw.js";import{X as I}from"./model-DWeeFWcT.js";import{_ as N}from"./ListPageHeader.vue_vue_type_script_setup_true_lang-BaaZTuAN.js";const X={class:"flex gap-2"},K=E({__name:"FacilityList",props:{title:{},model:{},editPath:{},activePath:{},addRid:{},addLabel:{}},setup(a){const k=a,$=A(),f=p(null),c=p(!1),m=p(void 0);function _(){m.value=void 0,c.value=!0}function b(e){m.value=e,c.value=!0}function g(e){c.value=e}function O(){var e;(e=f.value)==null||e.fetch()}function R(e){$.push(`${k.editPath}/${e}`)}async function x(e){var o,n,u;try{await V.get(`${k.activePath}/${e}`),y.success("تغییرات ثبت شد"),(o=f.value)==null||o.fetch()}catch(t){y.error(((u=(n=t==null?void 0:t.response)==null?void 0:n.data)==null?void 0:u.message)??"خطا در تغییر وضعیت")}}return(e,o)=>{const n=C("a-tooltip"),u=C("a-card");return s(),i(u,{title:a.title},{default:l(()=>[d(I,{ref_key:"tbl",ref:f,model:a.model},P({actions:l(({record:t})=>[w("div",X,[a.addRid?(s(),i(n,{key:0,title:"ویرایش"},{default:l(()=>[d(r(h),{class:"cursor-pointer mx-1 text-primary",onClick:v=>b(t.id)},null,8,["onClick"])]),_:2},1024)):(s(),i(n,{key:1,title:"ویرایش"},{default:l(()=>[d(r(h),{class:"cursor-pointer mx-1 text-primary",onClick:v=>R(t.id)},null,8,["onClick"])]),_:2},1024)),d(n,{title:"وضعیت"},{default:l(()=>[t.is_active?(s(),i(r(B),{key:0,class:"cursor-pointer mx-1 text-success",onClick:v=>x(t.id)},null,8,["onClick"])):(s(),i(r(L),{key:1,class:"cursor-pointer mx-1 text-danger",onClick:v=>x(t.id)},null,8,["onClick"]))]),_:2},1024)])]),_:2},[a.addRid?{name:"add",fn:l(()=>[d(N,{rid:a.addRid,"add-label":a.addLabel,onAdd:o[0]||(o[0]=t=>_())},null,8,["rid","add-label"])]),key:"0"}:void 0]),1032,["model"]),S(e.$slots,"form",{open:c.value,editingId:m.value,onUpdateOpen:g,onSuccess:O})]),_:3},8,["title"])}}});export{K as _};
@@ -1 +0,0 @@
import{I as u}from"./Icon-B9FXOmlS.js";import{d as z,c as l,w as n,r as y,o as s,p as c,a0 as I,m as O,O as P,k as p,x as w,A as C,j as A}from"./index-QQcP5-AC.js";import{X as B}from"./model-m8OsHE_l.js";import{_ as L}from"./ListPageHeader.vue_vue_type_script_setup_true_lang-BBMCN2Q8.js";const S={class:"flex gap-2"},j=z({__name:"FacilityList",props:{title:{},model:{},editPath:{},activePath:{},addRid:{},addLabel:{}},setup(a){const k=a,$=A(),m=p(null),d=p(!1),f=p(void 0);function _(){f.value=void 0,d.value=!0}function h(e){f.value=e,d.value=!0}function b(e){d.value=e}function g(){var e;(e=m.value)==null||e.fetch()}function R(e){$.push(`${k.editPath}/${e}`)}async function x(e){var o,i,r;try{await w.get(`${k.activePath}/${e}`),C.success("تغییرات ثبت شد"),(o=m.value)==null||o.fetch()}catch(t){C.error(((r=(i=t==null?void 0:t.response)==null?void 0:i.data)==null?void 0:r.message)??"خطا در تغییر وضعیت")}}return(e,o)=>{const i=y("a-tooltip"),r=y("a-card");return s(),l(r,{title:a.title,class:"page-card"},{default:n(()=>[c(B,{ref_key:"tbl",ref:m,model:a.model,"mobile-cards":""},I({actions:n(({record:t})=>[O("div",S,[a.addRid?(s(),l(i,{key:0,title:"ویرایش"},{default:n(()=>[c(u,{icon:"vuesax-linear:edit-2",size:"16",class:"cursor-pointer mx-1 text-primary",onClick:v=>h(t.id)},null,8,["onClick"])]),_:2},1024)):(s(),l(i,{key:1,title:"ویرایش"},{default:n(()=>[c(u,{icon:"vuesax-linear:edit-2",size:"16",class:"cursor-pointer mx-1 text-primary",onClick:v=>R(t.id)},null,8,["onClick"])]),_:2},1024)),c(i,{title:"وضعیت"},{default:n(()=>[t.is_active?(s(),l(u,{key:0,icon:"vuesax-linear:tick-circle",size:"16",class:"cursor-pointer mx-1 text-success",onClick:v=>x(t.id)},null,8,["onClick"])):(s(),l(u,{key:1,icon:"vuesax-linear:close-circle",size:"16",class:"cursor-pointer mx-1 text-danger",onClick:v=>x(t.id)},null,8,["onClick"]))]),_:2},1024)])]),_:2},[a.addRid?{name:"add",fn:n(()=>[c(L,{rid:a.addRid,"add-label":a.addLabel,onAdd:o[0]||(o[0]=t=>_())},null,8,["rid","add-label"])]),key:"0"}:void 0]),1032,["model"]),P(e.$slots,"form",{open:d.value,editingId:f.value,onUpdateOpen:b,onSuccess:g})]),_:3},8,["title"])}}});export{j as _};
+1
View File
@@ -0,0 +1 @@
import{_ as o}from"./_plugin-vue_export-helper-DlAUqK2U.js";import{b as t,c as r,r as c,o as s}from"./index-DuH3OOPw.js";const n={},a={class:"layout--full-page"};function _(l,f){const e=c("router-view");return s(),t("div",a,[r(e)])}const p=o(n,[["render",_]]);export{p as default};
-1
View File
@@ -1 +0,0 @@
import{_ as o,a as t,p as r,r as c,o as s}from"./index-QQcP5-AC.js";const a={},n={class:"layout--full-page"};function _(l,p){const e=c("router-view");return s(),t("div",n,[r(e)])}const f=o(a,[["render",_]]);export{f as default};

Some files were not shown because too many files have changed in this diff Show More