This commit is contained in:
2026-09-30 09:53:30 +03:30
parent 4d27da98fc
commit ebfbeb7a88
64 changed files with 1275 additions and 626 deletions
@@ -1 +1 @@
import{$ as e,g as t,k as n,l as r,st as i}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a}from"./http-BjLkjdQu.js";import{B as o}from"./index-pv0Ktkd1.js";import{t as s}from"./BaseSwitch-pJwnFBVo.js";function c(e=480){return new Promise(t=>{window.setTimeout(t,e)})}function l(t){let n=e(null),r=e(null),i=a();async function o(e,a){if(n.value)return a;let o=!a;if(!o&&t.confirmOff&&!await t.confirmOff())return a;n.value=e,r.value=null;let s=a;try{return await t.onPersist(o),i.success(t.successMessage?.(o)??(o?`وضعیت فعال شد.`:`وضعیت غیرفعال شد.`)),o}catch{return r.value=e,i.error(t.errorMessage??`به‌روزرسانی وضعیت انجام نشد.`),s}finally{n.value=null}}function s(e){return n.value===e}function c(e){return r.value===e}return{toggle:o,isLoading:s,hasError:c}}var u=t({__name:`ActiveSwitchCell`,props:{id:{},modelValue:{type:Boolean},entityName:{},entityType:{},persist:{type:Function}},emits:[`update:modelValue`],setup(e,{emit:t}){let a=e,u=t,{toggle:d,isLoading:f,hasError:p}=l({onPersist:async e=>{a.persist?await a.persist(a.id,e):await c()},successMessage:e=>e?`${a.entityType??`مورد`} «${a.entityName}» فعال شد.`:`${a.entityType??`مورد`} «${a.entityName}» غیرفعال شد.`});async function m(e){if(e===a.modelValue)return;let t=a.entityType??`مورد`;if(!await o({title:e?`فعال‌سازی ${t}`:`غیرفعال‌سازی ${t}`,message:e?`آیا از فعال کردن «${a.entityName}» مطمئن هستید؟`:`آیا از غیرفعال کردن «${a.entityName}» مطمئن هستید؟`,tone:e?`primary`:`danger`,confirmLabel:e?`فعال کردن`:`غیرفعال کردن`,cancelLabel:`انصراف`}))return;let n=await d(a.id,a.modelValue);u(`update:modelValue`,n)}return(t,a)=>(n(),r(s,{"model-value":e.modelValue,loading:i(f)(e.id),error:i(p)(e.id),"onUpdate:modelValue":m},null,8,[`model-value`,`loading`,`error`]))}});export{u as t};
import{$ as e,g as t,k as n,l as r,st as i}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a}from"./http-BjLkjdQu.js";import{V as o}from"./index-BzcByePp.js";import{t as s}from"./BaseSwitch-pJwnFBVo.js";function c(e=480){return new Promise(t=>{window.setTimeout(t,e)})}function l(t){let n=e(null),r=e(null),i=a();async function o(e,a){if(n.value)return a;let o=!a;if(!o&&t.confirmOff&&!await t.confirmOff())return a;n.value=e,r.value=null;let s=a;try{return await t.onPersist(o),i.success(t.successMessage?.(o)??(o?`وضعیت فعال شد.`:`وضعیت غیرفعال شد.`)),o}catch{return r.value=e,i.error(t.errorMessage??`به‌روزرسانی وضعیت انجام نشد.`),s}finally{n.value=null}}function s(e){return n.value===e}function c(e){return r.value===e}return{toggle:o,isLoading:s,hasError:c}}var u=t({__name:`ActiveSwitchCell`,props:{id:{},modelValue:{type:Boolean},entityName:{},entityType:{},persist:{type:Function}},emits:[`update:modelValue`],setup(e,{emit:t}){let a=e,u=t,{toggle:d,isLoading:f,hasError:p}=l({onPersist:async e=>{a.persist?await a.persist(a.id,e):await c()},successMessage:e=>e?`${a.entityType??`مورد`} «${a.entityName}» فعال شد.`:`${a.entityType??`مورد`} «${a.entityName}» غیرفعال شد.`});async function m(e){if(e===a.modelValue)return;let t=a.entityType??`مورد`;if(!await o({title:e?`فعال‌سازی ${t}`:`غیرفعال‌سازی ${t}`,message:e?`آیا از فعال کردن «${a.entityName}» مطمئن هستید؟`:`آیا از غیرفعال کردن «${a.entityName}» مطمئن هستید؟`,tone:e?`primary`:`danger`,confirmLabel:e?`فعال کردن`:`غیرفعال کردن`,cancelLabel:`انصراف`}))return;let n=await d(a.id,a.modelValue);u(`update:modelValue`,n)}return(t,a)=>(n(),r(s,{"model-value":e.modelValue,loading:i(f)(e.id),error:i(p)(e.id),"onUpdate:modelValue":m},null,8,[`model-value`,`loading`,`error`]))}});export{u as t};
@@ -1 +1 @@
import{Dt as e,d as t,g as n,k as r,s as i,t as a}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{c as o,r as s}from"./index-pv0Ktkd1.js";var c=[`id`,`value`,`placeholder`,`disabled`],l=a(n({__name:`AdminCurrencyInput`,props:{modelValue:{default:null},placeholder:{default:``},disabled:{type:Boolean,default:!1},id:{},inputClass:{default:`admin-input`},allowEmpty:{type:Boolean,default:!0}},emits:[`update:modelValue`],setup(n,{emit:a}){let l=n,u=a,d=i(()=>l.modelValue==null||!Number.isFinite(l.modelValue)?``:o(l.modelValue));function f(e){let t=e.target,n=s(t.value);if(!n){u(`update:modelValue`,l.allowEmpty?null:0),t.value=``;return}let r=Number(n);u(`update:modelValue`,r),t.value=o(r)}return(i,a)=>(r(),t(`input`,{id:n.id,type:`text`,inputmode:`numeric`,dir:`ltr`,class:e([`admin-currency-input`,n.inputClass]),value:d.value,placeholder:n.placeholder,disabled:n.disabled,onInput:f},null,42,c))}}),[[`__scopeId`,`data-v-56435fc8`]]);export{l as t};
import{Dt as e,d as t,g as n,k as r,s as i,t as a}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{c as o,r as s}from"./index-BzcByePp.js";var c=[`id`,`value`,`placeholder`,`disabled`],l=a(n({__name:`AdminCurrencyInput`,props:{modelValue:{default:null},placeholder:{default:``},disabled:{type:Boolean,default:!1},id:{},inputClass:{default:`admin-input`},allowEmpty:{type:Boolean,default:!0}},emits:[`update:modelValue`],setup(n,{emit:a}){let l=n,u=a,d=i(()=>l.modelValue==null||!Number.isFinite(l.modelValue)?``:o(l.modelValue));function f(e){let t=e.target,n=s(t.value);if(!n){u(`update:modelValue`,l.allowEmpty?null:0),t.value=``;return}let r=Number(n);u(`update:modelValue`,r),t.value=o(r)}return(i,a)=>(r(),t(`input`,{id:n.id,type:`text`,inputmode:`numeric`,dir:`ltr`,class:e([`admin-currency-input`,n.inputClass]),value:d.value,placeholder:n.placeholder,disabled:n.disabled,onInput:f},null,42,c))}}),[[`__scopeId`,`data-v-56435fc8`]]);export{l as t};
@@ -1 +1 @@
import{R as e,S as t,a as n,c as r,d as i,g as a,k as o,l as s,t as c,u as l}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{m as u}from"./http-BjLkjdQu.js";var d=[`src`,`alt`],f=c(a({__name:`AdminImageLightbox`,props:t({src:{},alt:{}},{modelValue:{type:Boolean,required:!0},modelModifiers:{}}),emits:[`update:modelValue`],setup(t){let a=e(t,`modelValue`);return(e,c)=>(o(),s(n,{to:`body`},[a.value?(o(),i(`div`,{key:0,class:`admin-image-lightbox`,onClick:c[1]||=u(e=>a.value=!1,[`self`])},[r(`button`,{type:`button`,class:`admin-image-lightbox__close`,onClick:c[0]||=e=>a.value=!1},` بستن `),r(`img`,{src:t.src,alt:t.alt??`پیش‌نمایش تصویر`},null,8,d)])):l(``,!0)]))}}),[[`__scopeId`,`data-v-65676449`]]);export{f as t};
import{R as e,S as t,a as n,c as r,d as i,g as a,k as o,l as s,t as c,u as l}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{m as u}from"./http-BjLkjdQu.js";var d=[`src`,`alt`],f=c(a({__name:`AdminImageLightbox`,props:t({src:{},alt:{}},{modelValue:{type:Boolean,required:!0},modelModifiers:{}}),emits:[`update:modelValue`],setup(t){let a=e(t,`modelValue`);return(e,c)=>(o(),s(n,{to:`body`},[a.value?(o(),i(`div`,{key:0,class:`admin-image-lightbox`,onClick:c[1]||=u(e=>a.value=!1,[`self`])},[r(`button`,{type:`button`,class:`admin-image-lightbox__close`,onClick:c[0]||=e=>a.value=!1},` بستن `),r(`img`,{src:t.src,alt:t.alt??`پیش‌نمایش تصویر`},null,8,d)])):l(``,!0)]))}}),[[`__scopeId`,`data-v-d52b23fb`]]);export{f as t};
@@ -1 +1 @@
.admin-image-lightbox[data-v-65676449]{z-index:300000;background:#000000d6;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.admin-image-lightbox img[data-v-65676449]{object-fit:contain;border-radius:8px;max-width:min(900px,100%);max-height:88vh}.admin-image-lightbox__close[data-v-65676449]{top:14px;cursor:pointer;background:var(--color-surface);color:var(--color-text);font:inherit;border:0;border-radius:8px;padding:8px 12px;font-size:.875rem;font-weight:600;position:absolute;inset-inline-end:14px}
.admin-image-lightbox[data-v-d52b23fb]{z-index:300000;background:#000000d6;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.admin-image-lightbox img[data-v-d52b23fb]{object-fit:contain;border-radius:8px;max-width:min(900px,100%);max-height:88vh}.admin-image-lightbox__close[data-v-d52b23fb]{top:14px;cursor:pointer;background:var(--color-surface);color:var(--color-text);font:inherit;border:0;border-radius:8px;padding:8px 12px;font-size:.875rem;font-weight:600;position:absolute;inset-inline-end:14px}
-1
View File
@@ -1 +0,0 @@
.image-picker__label[data-v-05db5be7]{margin:0 0 8px;font-size:.8125rem;font-weight:600}.image-picker__row[data-v-05db5be7]{align-items:center;gap:12px;display:flex}.image-picker__preview[data-v-05db5be7]{border:1px solid var(--color-border);background:#e8eceb 50%/cover no-repeat;border-radius:10px;flex-shrink:0;width:88px;height:72px}.image-picker__preview--empty[data-v-05db5be7]{background:repeating-linear-gradient(45deg,#eef1f0,#eef1f0 8px,#e4e8e6 8px 16px)}.image-picker__actions[data-v-05db5be7]{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.image-picker__file[data-v-05db5be7]{display:none}.image-picker__error[data-v-05db5be7]{width:100%;color:var(--color-danger);margin:0;font-size:.75rem}.image-picker__ok[data-v-05db5be7]{width:100%;color:var(--color-success-dark);margin:0;font-size:.75rem;font-weight:600}
@@ -1 +1 @@
import{$ as e,At as t,Dt as n,c as r,d as i,g as a,k as o,kt as s,st as c,t as l,u}from"./_plugin-vue_export-helper-QrIvvHC8.js";var d=960,f=.82;function p(e){let t=e.trim();return t?`url("${t.replace(/"/g,`\\"`)}")`:``}async function m(e){if(!e.type.startsWith(`image/`))throw Error(`فقط فایل تصویری مجاز است.`);if(e.size>8388608)throw Error(`حجم تصویر نباید بیشتر از ۸ مگابایت باشد.`);return g(await h(e))}function h(e){return new Promise((t,n)=>{let r=new FileReader;r.onload=()=>t(String(r.result??``)),r.onerror=()=>n(Error(`خواندن تصویر ناموفق بود.`)),r.readAsDataURL(e)})}function g(e){return new Promise((t,n)=>{let r=new Image;r.onload=()=>{let e=Math.min(1,d/Math.max(r.width,r.height)),i=Math.max(1,Math.round(r.width*e)),a=Math.max(1,Math.round(r.height*e)),o=document.createElement(`canvas`);o.width=i,o.height=a;let s=o.getContext(`2d`);if(!s){n(Error(`پردازش تصویر ناموفق بود.`));return}s.drawImage(r,0,0,i,a),t(o.toDataURL(`image/jpeg`,f))},r.onerror=()=>n(Error(`تصویر نامعتبر است.`)),r.src=e})}var _={class:`image-picker`},v={key:0,class:`image-picker__label`},y={class:`image-picker__row`},b={class:`image-picker__actions`},x=[`disabled`],S=[`disabled`],C={key:1,class:`image-picker__error`},w={key:2,class:`image-picker__ok`},T=l(a({__name:`AdminImagePicker`,props:{modelValue:{},label:{}},emits:[`update:modelValue`],setup(a,{emit:l}){let d=a,f=l,h=e(null),g=e(!1),T=e(``),E=e(!1);function D(){h.value?.click()}async function O(e){let t=e.target,n=t.files?.[0];if(t.value=``,n){g.value=!0,T.value=``,E.value=!1;try{f(`update:modelValue`,await m(n)),E.value=!0}catch(e){T.value=e instanceof Error?e.message:`بارگذاری تصویر ناموفق بود.`}finally{g.value=!1}}}function k(){f(`update:modelValue`,null)}return(e,l)=>(o(),i(`div`,_,[a.label?(o(),i(`p`,v,t(a.label),1)):u(``,!0),r(`div`,y,[r(`div`,{class:n([`image-picker__preview`,{"image-picker__preview--empty":!d.modelValue}]),style:s(d.modelValue?{backgroundImage:c(p)(d.modelValue)}:void 0)},null,6),r(`div`,b,[r(`button`,{type:`button`,class:`admin-btn admin-btn--outline admin-btn--sm`,disabled:g.value,onClick:D},t(d.modelValue?`تغییر تصویر`:`افزودن تصویر`),9,x),d.modelValue?(o(),i(`button`,{key:0,type:`button`,class:`admin-btn admin-btn--outline admin-btn--sm`,disabled:g.value,onClick:k},` حذف `,8,S)):u(``,!0),T.value?(o(),i(`p`,C,t(T.value),1)):E.value?(o(),i(`p`,w,`تصویر انتخاب شد — برای ثبت نهایی «ذخیره» را بزنید.`)):u(``,!0)])]),r(`input`,{ref_key:`inputRef`,ref:h,type:`file`,accept:`image/*`,class:`image-picker__file`,onChange:O},null,544)]))}}),[[`__scopeId`,`data-v-05db5be7`]]);export{m as n,p as r,T as t};
import{$ as e,At as t,Dt as n,c as r,d as i,g as a,k as o,kt as s,st as c,t as l,u}from"./_plugin-vue_export-helper-QrIvvHC8.js";var d=960,f=.82;function p(e){let t=e.trim();return t?`url("${t.replace(/"/g,`\\"`)}")`:``}async function m(e){if(!e.type.startsWith(`image/`))throw Error(`فقط فایل تصویری مجاز است.`);if(e.size>8388608)throw Error(`حجم تصویر نباید بیشتر از ۸ مگابایت باشد.`);return g(await h(e))}function h(e){return new Promise((t,n)=>{let r=new FileReader;r.onload=()=>t(String(r.result??``)),r.onerror=()=>n(Error(`خواندن تصویر ناموفق بود.`)),r.readAsDataURL(e)})}function g(e){return new Promise((t,n)=>{let r=new Image;r.onload=()=>{let e=Math.min(1,d/Math.max(r.width,r.height)),i=Math.max(1,Math.round(r.width*e)),a=Math.max(1,Math.round(r.height*e)),o=document.createElement(`canvas`);o.width=i,o.height=a;let s=o.getContext(`2d`);if(!s){n(Error(`پردازش تصویر ناموفق بود.`));return}s.drawImage(r,0,0,i,a),t(o.toDataURL(`image/jpeg`,f))},r.onerror=()=>n(Error(`تصویر نامعتبر است.`)),r.src=e})}var _={class:`image-picker`},v={key:0,class:`image-picker__label`},y={class:`image-picker__row`},b={class:`image-picker__actions`},x=[`disabled`],S=[`disabled`],C={key:1,class:`image-picker__error`},w={key:2,class:`image-picker__ok`},T=l(a({__name:`AdminImagePicker`,props:{modelValue:{},label:{}},emits:[`update:modelValue`],setup(a,{emit:l}){let d=a,f=l,h=e(null),g=e(!1),T=e(``),E=e(!1);function D(){h.value?.click()}async function O(e){let t=e.target,n=t.files?.[0];if(t.value=``,n){g.value=!0,T.value=``,E.value=!1;try{f(`update:modelValue`,await m(n)),E.value=!0}catch(e){T.value=e instanceof Error?e.message:`بارگذاری تصویر ناموفق بود.`}finally{g.value=!1}}}function k(){f(`update:modelValue`,null)}return(e,l)=>(o(),i(`div`,_,[a.label?(o(),i(`p`,v,t(a.label),1)):u(``,!0),r(`div`,y,[r(`div`,{class:n([`image-picker__preview`,{"image-picker__preview--empty":!d.modelValue}]),style:s(d.modelValue?{backgroundImage:c(p)(d.modelValue)}:void 0)},null,6),r(`div`,b,[r(`button`,{type:`button`,class:`admin-btn admin-btn--outline admin-btn--sm`,disabled:g.value,onClick:D},t(d.modelValue?`تغییر تصویر`:`افزودن تصویر`),9,x),d.modelValue?(o(),i(`button`,{key:0,type:`button`,class:`admin-btn admin-btn--outline admin-btn--sm`,disabled:g.value,onClick:k},` حذف `,8,S)):u(``,!0),T.value?(o(),i(`p`,C,t(T.value),1)):E.value?(o(),i(`p`,w,`تصویر انتخاب شد — برای ثبت نهایی «ثبت» را بزنید.`)):u(``,!0)])]),r(`input`,{ref_key:`inputRef`,ref:h,type:`file`,accept:`image/*`,class:`image-picker__file`,onChange:O},null,544)]))}}),[[`__scopeId`,`data-v-ab0add26`]]);export{p as n,T as t};
+1
View File
@@ -0,0 +1 @@
.image-picker__label[data-v-ab0add26]{margin:0 0 8px;font-size:.8125rem;font-weight:600}.image-picker__row[data-v-ab0add26]{align-items:center;gap:12px;display:flex}.image-picker__preview[data-v-ab0add26]{border:1px solid var(--color-border);background:#e8eceb 50%/cover no-repeat;border-radius:10px;flex-shrink:0;width:88px;height:72px}.image-picker__preview--empty[data-v-ab0add26]{background:repeating-linear-gradient(45deg,#eef1f0,#eef1f0 8px,#e4e8e6 8px 16px)}.image-picker__actions[data-v-ab0add26]{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.image-picker__file[data-v-ab0add26]{display:none}.image-picker__error[data-v-ab0add26]{width:100%;color:var(--color-danger);margin:0;font-size:.75rem}.image-picker__ok[data-v-ab0add26]{width:100%;color:var(--color-success-dark);margin:0;font-size:.75rem;font-weight:600}
@@ -1 +1 @@
import{$ as e,At as t,B as n,D as r,Dt as i,M as a,N as o,U as s,a as c,c as l,d as u,g as d,h as f,k as p,kt as m,l as h,r as g,s as _,t as v,u as y,w as b}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{f as x,m as S,p as C}from"./http-BjLkjdQu.js";import{L as w,M as T}from"./index-pv0Ktkd1.js";var E=[`id`],D=[`aria-label`,`aria-expanded`],O={class:`admin-select__trigger-content`},k={class:`admin-select__value`},A={class:`admin-select__chevron`,"aria-hidden":`true`},j=[`aria-label`],M={key:0,class:`admin-select__search`,role:`presentation`},N=[`placeholder`],P={key:1,class:`admin-select__empty`,role:`presentation`},F=[`aria-selected`,`onClick`],I={class:`admin-select__option-label`},L=v(d({__name:`AdminSelect`,props:{modelValue:{},options:{},placeholder:{default:`انتخاب کنید`},ariaLabel:{default:`انتخاب`},block:{type:Boolean,default:!1},searchable:{type:Boolean,default:!1},searchPlaceholder:{default:`جستجو…`},id:{default:void 0}},emits:[`update:modelValue`],setup(d,{emit:v}){let L=d,R=v,z=e(!1),B=e(``),V=e(null),H=e(null),U=e(null),W=e({});T(V,()=>{z.value=!1,B.value=``},{ignore:[H]});let G=_(()=>L.options.find(e=>e.value===L.modelValue)),K=_(()=>G.value?.label??L.placeholder),q=_(()=>{let e=B.value.trim().toLowerCase();return e?L.options.filter(t=>t.label.toLowerCase().includes(e)):L.options});function J(){let e=V.value;if(!e)return;let t=e.getBoundingClientRect(),n=Math.min(320,window.innerHeight*.5),r=window.innerHeight-t.bottom-6,i=t.top-6,a=r<Math.min(160,n)&&i>r,o=Math.max(120,Math.min(n,a?i:r));W.value={position:`fixed`,left:`${Math.max(8,Math.min(t.left,window.innerWidth-Math.max(t.width,220)-8))}px`,width:`${Math.max(t.width,220)}px`,zIndex:`1100`,maxHeight:`${o}px`,...a?{top:`auto`,bottom:`${window.innerHeight-t.top+6}px`}:{top:`${t.bottom+6}px`,bottom:`auto`}}}function Y(e){e?(window.addEventListener(`resize`,J),window.addEventListener(`scroll`,J,!0)):(window.removeEventListener(`resize`,J),window.removeEventListener(`scroll`,J,!0))}async function X(){z.value=!z.value,z.value&&(await b(),J(),L.searchable&&(B.value=``,await b(),U.value?.focus()))}function Z(e){R(`update:modelValue`,e),z.value=!1,B.value=``}return n(z,e=>{Y(e),e&&b(()=>J())}),r(()=>{Y(!1)}),(e,n)=>(p(),u(`div`,{id:d.id,ref_key:`rootRef`,ref:V,class:i([`admin-select`,{"admin-select--open":z.value,"admin-select--block":d.block}])},[l(`button`,{type:`button`,class:`admin-select__trigger`,"aria-label":d.ariaLabel,"aria-expanded":z.value,"aria-haspopup":`listbox`,onClick:X},[l(`span`,O,[o(e.$slots,`option-leading`,{option:G.value},void 0,!0),l(`span`,k,t(K.value),1)]),l(`span`,A,[f(w,{name:`chevron-down`,size:16})])],8,D),(p(),h(c,{to:`body`},[z.value?(p(),u(`ul`,{key:0,ref_key:`menuRef`,ref:H,class:`admin-select__menu`,role:`listbox`,"aria-label":d.ariaLabel,style:m(W.value)},[d.searchable?(p(),u(`li`,M,[s(l(`input`,{ref_key:`searchInputRef`,ref:U,"onUpdate:modelValue":n[0]||=e=>B.value=e,type:`search`,class:`admin-select__search-input`,placeholder:d.searchPlaceholder,onClick:n[1]||=S(()=>{},[`stop`]),onKeydown:n[2]||=C(S(e=>z.value=!1,[`stop`]),[`esc`])},null,40,N),[[x,B.value]])])):y(``,!0),q.value.length?y(``,!0):(p(),u(`li`,P,` موردی یافت نشد `)),(p(!0),u(g,null,a(q.value,n=>(p(),u(`li`,{key:n.value,role:`presentation`},[l(`button`,{type:`button`,class:i([`admin-select__option`,{"admin-select__option--active":n.value===d.modelValue}]),role:`option`,"aria-selected":n.value===d.modelValue,onClick:e=>Z(n.value)},[l(`span`,I,[o(e.$slots,`option-leading`,{option:n},void 0,!0),l(`span`,null,t(n.label),1)]),n.value===d.modelValue?(p(),h(w,{key:0,name:`check`,size:16,class:`admin-select__check`})):y(``,!0)],10,F)]))),128))],12,j)):y(``,!0)]))],10,E))}}),[[`__scopeId`,`data-v-4623e322`]]);export{L as t};
import{$ as e,At as t,B as n,D as r,Dt as i,M as a,N as o,U as s,a as c,c as l,d as u,g as d,h as f,k as p,kt as m,l as h,r as g,s as _,t as v,u as y,w as b}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{f as x,m as S,p as C}from"./http-BjLkjdQu.js";import{N as w,R as T}from"./index-BzcByePp.js";var E=[`id`],D=[`aria-label`,`aria-expanded`],O={class:`admin-select__trigger-content`},k={class:`admin-select__value`},A={class:`admin-select__chevron`,"aria-hidden":`true`},j=[`aria-label`],M={key:0,class:`admin-select__search`,role:`presentation`},N=[`placeholder`],P={key:1,class:`admin-select__empty`,role:`presentation`},F=[`aria-selected`,`onClick`],I={class:`admin-select__option-label`},L=v(d({__name:`AdminSelect`,props:{modelValue:{},options:{},placeholder:{default:`انتخاب کنید`},ariaLabel:{default:`انتخاب`},block:{type:Boolean,default:!1},searchable:{type:Boolean,default:!1},searchPlaceholder:{default:`جستجو…`},id:{default:void 0}},emits:[`update:modelValue`],setup(d,{emit:v}){let L=d,R=v,z=e(!1),B=e(``),V=e(null),H=e(null),U=e(null),W=e({});w(V,()=>{z.value=!1,B.value=``},{ignore:[H]});let G=_(()=>L.options.find(e=>e.value===L.modelValue)),K=_(()=>G.value?.label??L.placeholder),q=_(()=>{let e=B.value.trim().toLowerCase();return e?L.options.filter(t=>t.label.toLowerCase().includes(e)):L.options});function J(){let e=V.value;if(!e)return;let t=e.getBoundingClientRect(),n=Math.min(320,window.innerHeight*.5),r=window.innerHeight-t.bottom-6,i=t.top-6,a=r<Math.min(160,n)&&i>r,o=Math.max(120,Math.min(n,a?i:r));W.value={position:`fixed`,left:`${Math.max(8,Math.min(t.left,window.innerWidth-Math.max(t.width,220)-8))}px`,width:`${Math.max(t.width,220)}px`,zIndex:`1100`,maxHeight:`${o}px`,...a?{top:`auto`,bottom:`${window.innerHeight-t.top+6}px`}:{top:`${t.bottom+6}px`,bottom:`auto`}}}function Y(e){e?(window.addEventListener(`resize`,J),window.addEventListener(`scroll`,J,!0)):(window.removeEventListener(`resize`,J),window.removeEventListener(`scroll`,J,!0))}async function X(){z.value=!z.value,z.value&&(await b(),J(),L.searchable&&(B.value=``,await b(),U.value?.focus()))}function Z(e){R(`update:modelValue`,e),z.value=!1,B.value=``}return n(z,e=>{Y(e),e&&b(()=>J())}),r(()=>{Y(!1)}),(e,n)=>(p(),u(`div`,{id:d.id,ref_key:`rootRef`,ref:V,class:i([`admin-select`,{"admin-select--open":z.value,"admin-select--block":d.block}])},[l(`button`,{type:`button`,class:`admin-select__trigger`,"aria-label":d.ariaLabel,"aria-expanded":z.value,"aria-haspopup":`listbox`,onClick:X},[l(`span`,O,[o(e.$slots,`option-leading`,{option:G.value},void 0,!0),l(`span`,k,t(K.value),1)]),l(`span`,A,[f(T,{name:`chevron-down`,size:16})])],8,D),(p(),h(c,{to:`body`},[z.value?(p(),u(`ul`,{key:0,ref_key:`menuRef`,ref:H,class:`admin-select__menu`,role:`listbox`,"aria-label":d.ariaLabel,style:m(W.value)},[d.searchable?(p(),u(`li`,M,[s(l(`input`,{ref_key:`searchInputRef`,ref:U,"onUpdate:modelValue":n[0]||=e=>B.value=e,type:`search`,class:`admin-select__search-input`,placeholder:d.searchPlaceholder,onClick:n[1]||=S(()=>{},[`stop`]),onKeydown:n[2]||=C(S(e=>z.value=!1,[`stop`]),[`esc`])},null,40,N),[[x,B.value]])])):y(``,!0),q.value.length?y(``,!0):(p(),u(`li`,P,` موردی یافت نشد `)),(p(!0),u(g,null,a(q.value,n=>(p(),u(`li`,{key:n.value,role:`presentation`},[l(`button`,{type:`button`,class:i([`admin-select__option`,{"admin-select__option--active":n.value===d.modelValue}]),role:`option`,"aria-selected":n.value===d.modelValue,onClick:e=>Z(n.value)},[l(`span`,I,[o(e.$slots,`option-leading`,{option:n},void 0,!0),l(`span`,null,t(n.label),1)]),n.value===d.modelValue?(p(),h(T,{key:0,name:`check`,size:16,class:`admin-select__check`})):y(``,!0)],10,F)]))),128))],12,j)):y(``,!0)]))],10,E))}}),[[`__scopeId`,`data-v-4623e322`]]);export{L as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{At as e,Dt as t,N as n,c as r,d as i,g as a,h as o,k as s,t as c,u as l}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{L as u}from"./index-pv0Ktkd1.js";var d={class:`base-empty__icon`,"aria-hidden":`true`},f={class:`base-empty__title`},p={key:0,class:`base-empty__description`},m={key:1,class:`base-empty__actions`},h=c(a({__name:`BaseEmptyState`,props:{icon:{default:`file`},title:{},description:{},compact:{type:Boolean}},emits:[`action`,`secondary`],setup(a){return(c,h)=>(s(),i(`div`,{class:t([`base-empty`,{"base-empty--compact":a.compact}])},[r(`div`,d,[o(u,{name:a.icon,size:a.compact?28:36},null,8,[`name`,`size`])]),r(`h3`,f,e(a.title),1),a.description?(s(),i(`p`,p,e(a.description),1)):l(``,!0),c.$slots.default||c.$slots.secondary?(s(),i(`div`,m,[n(c.$slots,`default`,{},void 0,!0),n(c.$slots,`secondary`,{},void 0,!0)])):l(``,!0)],2))}}),[[`__scopeId`,`data-v-47ea81e4`]]);export{h as t};
import{At as e,Dt as t,N as n,c as r,d as i,g as a,h as o,k as s,t as c,u as l}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{R as u}from"./index-BzcByePp.js";var d={class:`base-empty__icon`,"aria-hidden":`true`},f={class:`base-empty__title`},p={key:0,class:`base-empty__description`},m={key:1,class:`base-empty__actions`},h=c(a({__name:`BaseEmptyState`,props:{icon:{default:`file`},title:{},description:{},compact:{type:Boolean}},emits:[`action`,`secondary`],setup(a){return(c,h)=>(s(),i(`div`,{class:t([`base-empty`,{"base-empty--compact":a.compact}])},[r(`div`,d,[o(u,{name:a.icon,size:a.compact?28:36},null,8,[`name`,`size`])]),r(`h3`,f,e(a.title),1),a.description?(s(),i(`p`,p,e(a.description),1)):l(``,!0),c.$slots.default||c.$slots.secondary?(s(),i(`div`,m,[n(c.$slots,`default`,{},void 0,!0),n(c.$slots,`secondary`,{},void 0,!0)])):l(``,!0)],2))}}),[[`__scopeId`,`data-v-47ea81e4`]]);export{h as t};
-1
View File
@@ -1 +0,0 @@
.admin-page__head[data-v-88043806]{justify-content:space-between;margin-bottom:20px;display:flex}.admin-page__head h1[data-v-88043806]{margin:0 0 4px;font-size:1.25rem}.admin-page__head p[data-v-88043806]{color:var(--color-text-muted);margin:0;font-size:.875rem}.admin-table[data-v-88043806]{border-collapse:collapse;width:100%}.admin-table th[data-v-88043806],.admin-table td[data-v-88043806]{text-align:right;border-bottom:1px solid var(--color-border);padding:10px 8px}.field[data-v-88043806]{flex-direction:column;gap:6px;margin-bottom:12px;font-size:.875rem;display:flex}.link[data-v-88043806]{color:var(--color-primary);cursor:pointer;background:0 0;border:none;font-family:inherit}
@@ -1 +1 @@
import{$ as e,At as t,E as n,H as r,M as i,U as a,Z as o,c as s,d as c,g as l,h as u,k as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as _,f as v,t as y}from"./http-BjLkjdQu.js";import{V as b,p as x}from"./index-pv0Ktkd1.js";import{t as S}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as C}from"./BaseSwitch-pJwnFBVo.js";import{t as w}from"./BaseEmptyState-y-wpauXF.js";var T={class:`admin-page`},E={key:2,class:`admin-table`},D={dir:`ltr`},O=[`onClick`],k={class:`field`},A={class:`field`},j={class:`field`},M={class:`field`},N=[`disabled`],P=g(l({__name:`BrandsView`,setup(l){let g=e([]),P=e(!0),F=e(!1),I=e(null),L=_(),R=o({name:``,slug:``,description:``,displayOrder:0,isActive:!0}),z=h(()=>I.value?`ویرایش برند`:`افزودن برند`);async function B(){P.value=!0;try{g.value=await x.list()}catch(e){L.error(e instanceof y?e.message:`بارگذاری برندها ناموفق بود`)}finally{P.value=!1}}function V(){I.value=null,Object.assign(R,{name:``,slug:``,description:``,displayOrder:g.value.length+1,isActive:!0}),F.value=!0}function H(e){I.value=e.id,Object.assign(R,{name:e.name,slug:e.slug??``,description:e.description??``,displayOrder:e.displayOrder,isActive:e.isActive}),F.value=!0}async function U(){try{let e={name:R.name.trim(),slug:R.slug.trim()||void 0,description:R.description.trim()||void 0,displayOrder:R.displayOrder,isActive:R.isActive};I.value?await x.update(I.value,e):await x.create(e),F.value=!1,L.success(`ذخیره شد`),await B()}catch(e){L.error(e instanceof y?e.message:`ذخیره ناموفق بود`)}}return n(()=>{B()}),(e,n)=>(d(),c(`div`,T,[s(`header`,{class:`admin-page__head`},[n[6]||=s(`div`,null,[s(`h1`,null,`برندها`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:V},`افزودن برند`)]),P.value?(d(),f(S,{key:0,variant:`table`})):g.value.length?(d(),c(`table`,E,[n[7]||=s(`thead`,null,[s(`tr`,null,[s(`th`,null,`نام`),s(`th`,null,`شناسه لاتین`),s(`th`,null,`مشتریان`),s(`th`,null,`فعال`),s(`th`)])],-1),s(`tbody`,null,[(d(!0),c(m,null,i(g.value,e=>(d(),c(`tr`,{key:e.id},[s(`td`,null,t(e.name),1),s(`td`,D,t(e.slug||`—`),1),s(`td`,null,t(e.customersCount??0),1),s(`td`,null,t(e.isActive?`بله`:`خیر`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>H(e)},`ویرایش`,8,O)])]))),128))])])):(d(),f(w,{key:1,title:`برندی ثبت نشده`})),u(b,{open:F.value,title:z.value,onClose:n[5]||=e=>F.value=!1},{footer:r(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!R.name.trim(),onClick:U},`ذخیره`,8,N)]),default:r(()=>[s(`label`,k,[n[8]||=p(`نام`,-1),a(s(`input`,{"onUpdate:modelValue":n[0]||=e=>R.name=e,class:`admin-input`},null,512),[[v,R.name]])]),s(`label`,A,[n[9]||=p(`شناسه لاتین`,-1),a(s(`input`,{"onUpdate:modelValue":n[1]||=e=>R.slug=e,class:`admin-input`,dir:`ltr`,placeholder:`مثلاً nademi-silk`},null,512),[[v,R.slug]])]),s(`label`,j,[n[10]||=p(`توضیح`,-1),a(s(`textarea`,{"onUpdate:modelValue":n[2]||=e=>R.description=e,class:`admin-input`,rows:`3`},null,512),[[v,R.description]])]),s(`label`,M,[n[11]||=p(`ترتیب`,-1),a(s(`input`,{"onUpdate:modelValue":n[3]||=e=>R.displayOrder=e,type:`number`,class:`admin-input`},null,512),[[v,R.displayOrder,void 0,{number:!0}]])]),u(C,{modelValue:R.isActive,"onUpdate:modelValue":n[4]||=e=>R.isActive=e,label:`فعال`},null,8,[`modelValue`])]),_:1},8,[`open`,`title`])]))}}),[[`__scopeId`,`data-v-88043806`]]);export{P as default};
import{$ as e,At as t,E as n,H as r,M as i,U as a,Z as o,c as s,d as c,g as l,h as u,k as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as _,f as v,t as y}from"./http-BjLkjdQu.js";import{H as b,p as x}from"./index-BzcByePp.js";import{t as S}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as C}from"./BaseSwitch-pJwnFBVo.js";import{t as w}from"./BaseEmptyState-B7aEMvHg.js";var T={class:`admin-page`},E={key:2,class:`admin-table`},D={dir:`ltr`},O=[`onClick`],k={class:`field`},A={class:`field`},j={class:`field`},M={class:`field`},N=[`disabled`],P=g(l({__name:`BrandsView`,setup(l){let g=e([]),P=e(!0),F=e(!1),I=e(null),L=_(),R=o({name:``,slug:``,description:``,displayOrder:0,isActive:!0}),z=h(()=>I.value?`ویرایش برند`:`افزودن برند`);async function B(){P.value=!0;try{g.value=await x.list()}catch(e){L.error(e instanceof y?e.message:`بارگذاری برندها ناموفق بود`)}finally{P.value=!1}}function V(){I.value=null,Object.assign(R,{name:``,slug:``,description:``,displayOrder:g.value.length+1,isActive:!0}),F.value=!0}function H(e){I.value=e.id,Object.assign(R,{name:e.name,slug:e.slug??``,description:e.description??``,displayOrder:e.displayOrder,isActive:e.isActive}),F.value=!0}async function U(){try{let e={name:R.name.trim(),slug:R.slug.trim()||void 0,description:R.description.trim()||void 0,displayOrder:R.displayOrder,isActive:R.isActive};I.value?await x.update(I.value,e):await x.create(e),F.value=!1,L.success(`ذخیره شد`),await B()}catch(e){L.error(e instanceof y?e.message:`ذخیره ناموفق بود`)}}return n(()=>{B()}),(e,n)=>(d(),c(`div`,T,[s(`header`,{class:`admin-page__head`},[n[6]||=s(`div`,null,[s(`h1`,null,`برندها`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:V},`افزودن برند`)]),P.value?(d(),f(S,{key:0,variant:`table`})):g.value.length?(d(),c(`table`,E,[n[7]||=s(`thead`,null,[s(`tr`,null,[s(`th`,null,`نام`),s(`th`,null,`شناسه لاتین`),s(`th`,null,`محصولات`),s(`th`,null,`فعال`),s(`th`)])],-1),s(`tbody`,null,[(d(!0),c(m,null,i(g.value,e=>(d(),c(`tr`,{key:e.id},[s(`td`,null,t(e.name),1),s(`td`,D,t(e.slug||`—`),1),s(`td`,null,t(e.productsCount??0),1),s(`td`,null,t(e.isActive?`بله`:`خیر`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>H(e)},`ویرایش`,8,O)])]))),128))])])):(d(),f(w,{key:1,title:`برندی ثبت نشده`})),u(b,{open:F.value,title:z.value,onClose:n[5]||=e=>F.value=!1},{footer:r(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!R.name.trim(),onClick:U},`ذخیره`,8,N)]),default:r(()=>[s(`label`,k,[n[8]||=p(`نام`,-1),a(s(`input`,{"onUpdate:modelValue":n[0]||=e=>R.name=e,class:`admin-input`},null,512),[[v,R.name]])]),s(`label`,A,[n[9]||=p(`شناسه لاتین`,-1),a(s(`input`,{"onUpdate:modelValue":n[1]||=e=>R.slug=e,class:`admin-input`,dir:`ltr`,placeholder:`مثلاً nademi-silk`},null,512),[[v,R.slug]])]),s(`label`,j,[n[10]||=p(`توضیح`,-1),a(s(`textarea`,{"onUpdate:modelValue":n[2]||=e=>R.description=e,class:`admin-input`,rows:`3`},null,512),[[v,R.description]])]),s(`label`,M,[n[11]||=p(`ترتیب`,-1),a(s(`input`,{"onUpdate:modelValue":n[3]||=e=>R.displayOrder=e,type:`number`,class:`admin-input`},null,512),[[v,R.displayOrder,void 0,{number:!0}]])]),u(C,{modelValue:R.isActive,"onUpdate:modelValue":n[4]||=e=>R.isActive=e,label:`فعال`},null,8,[`modelValue`])]),_:1},8,[`open`,`title`])]))}}),[[`__scopeId`,`data-v-a6f53b6f`]]);export{P as default};
+1
View File
@@ -0,0 +1 @@
.admin-page__head[data-v-a6f53b6f]{justify-content:space-between;margin-bottom:20px;display:flex}.admin-page__head h1[data-v-a6f53b6f]{margin:0 0 4px;font-size:1.25rem}.admin-page__head p[data-v-a6f53b6f]{color:var(--color-text-muted);margin:0;font-size:.875rem}.admin-table[data-v-a6f53b6f]{border-collapse:collapse;width:100%}.admin-table th[data-v-a6f53b6f],.admin-table td[data-v-a6f53b6f]{text-align:right;border-bottom:1px solid var(--color-border);padding:10px 8px}.field[data-v-a6f53b6f]{flex-direction:column;gap:6px;margin-bottom:12px;font-size:.875rem;display:flex}.link[data-v-a6f53b6f]{color:var(--color-primary);cursor:pointer;background:0 0;border:none;font-family:inherit}
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{$ as e,At as t,E as n,M as r,c as i,d as a,g as o,h as s,k as c,l,m as u,r as d,st as f,t as p,u as m}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as h,t as g}from"./http-BjLkjdQu.js";import{h as _,o as v}from"./index-pv0Ktkd1.js";import{t as y}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as b}from"./BaseEmptyState-y-wpauXF.js";import{t as x}from"./AdminSelect-DFH4VaRE.js";var S={class:`admin-page`},C={class:`admin-page__head`},w={class:`request-card__top`},T={class:`muted`,dir:`ltr`},E={class:`badge`},D={key:0,class:`muted`},O={class:`admin-table`},k={dir:`ltr`},A={key:0,class:`movements`},j={class:`muted`},M=p(o({__name:`CatalogRequestsView`,setup(o){let p=[{value:``,label:`همه وضعیت‌ها`},{value:`new`,label:`درخواست جدید`},{value:`approved`,label:`تأیید شده`},{value:`ready_to_ship`,label:`آماده ارسال`},{value:`delivered`,label:`تحویل به مشتری`},{value:`returned`,label:`بازگردانده شده`},{value:`cancelled`,label:`لغو شده`}],M=[{value:`warehouse`,label:`انبار`},{value:`customer`,label:`مشتری`},{value:`agent`,label:`واسطه`}],N=Object.fromEntries(p.filter(e=>e.value).map(e=>[e.value,e.label])),P=Object.fromEntries(M.map(e=>[e.value,e.label])),F=e([]),I=e(``),L=e(!0),R=e(null),z=h();async function B(){L.value=!0;try{F.value=await _.list(I.value||void 0)}catch(e){z.error(e instanceof g?e.message:`بارگذاری انجام نشد`)}finally{L.value=!1}}async function V(e,t){try{await _.updateStatus(e,t),z.success(`وضعیت به‌روز شد`),await B()}catch(e){z.error(e instanceof g?e.message:`به‌روزرسانی ناموفق بود`)}}async function H(e,t){if(!R.value){R.value=e;try{await _.updateHolder(e,{holderType:t}),z.success(`محل نگهداری به‌روز شد`),await B()}catch(e){z.error(e instanceof g?e.message:`به‌روزرسانی محل ناموفق بود`)}finally{R.value=null}}}function U(e){return P[e]??e}return n(()=>{B()}),(e,n)=>(c(),a(`div`,S,[i(`header`,C,[n[1]||=i(`div`,null,[i(`h1`,null,`درخواست کالیته فیزیکی`),i(`p`,null,`بررسی، گردش وضعیت و محل نگهداری اقلام`)],-1),s(x,{modelValue:I.value,"onUpdate:modelValue":[n[0]||=e=>I.value=e,B],options:p},null,8,[`modelValue`])]),L.value?(c(),l(y,{key:0,variant:`cards`})):F.value.length?(c(!0),a(d,{key:2},r(F.value,e=>(c(),a(`div`,{key:e.id,class:`request-card`},[i(`div`,w,[i(`div`,null,[i(`strong`,null,t(e.customerName),1),i(`span`,T,t(e.customerPhone),1),i(`span`,E,t(f(N)[e.status]??e.status),1)]),s(x,{"model-value":e.status,options:p.filter(e=>e.value),"onUpdate:modelValue":t=>V(e.id,String(t))},null,8,[`model-value`,`options`,`onUpdate:modelValue`])]),e.note?(c(),a(`p`,D,t(e.note),1)):m(``,!0),i(`table`,O,[n[2]||=i(`thead`,null,[i(`tr`,null,[i(`th`,null,`محصول`),i(`th`,null,`کد`),i(`th`,null,`تعداد`),i(`th`,null,`محل`)])],-1),i(`tbody`,null,[(c(!0),a(d,null,r(e.items,e=>(c(),a(`tr`,{key:e.id},[i(`td`,null,t(e.productName),1),i(`td`,k,t(e.productCode),1),i(`td`,null,t(e.quantity),1),i(`td`,null,[s(x,{"model-value":e.holderType,options:M,"onUpdate:modelValue":t=>H(e.id,String(t))},null,8,[`model-value`,`onUpdate:modelValue`]),e.movements?.length?(c(),a(`ul`,A,[(c(!0),a(d,null,r(e.movements,e=>(c(),a(`li`,{key:e.id},[u(t(U(e.fromHolder))+` → `+t(U(e.toHolder))+` `,1),i(`span`,j,t(f(v)(e.movedAt)),1)]))),128))])):m(``,!0)])]))),128))])])]))),128)):(c(),l(b,{key:1,title:`درخواستی نیست`}))]))}}),[[`__scopeId`,`data-v-747d8f88`]]);export{M as default};
import{$ as e,At as t,E as n,M as r,c as i,d as a,g as o,h as s,k as c,l,m as u,r as d,st as f,t as p,u as m}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as h,t as g}from"./http-BjLkjdQu.js";import{h as _,o as v}from"./index-BzcByePp.js";import{t as y}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as b}from"./BaseEmptyState-B7aEMvHg.js";import{t as x}from"./AdminSelect-8XDtPu3U.js";var S={class:`admin-page`},C={class:`admin-page__head`},w={class:`request-card__top`},T={class:`muted`,dir:`ltr`},E={class:`badge`},D={key:0,class:`muted`},O={class:`admin-table`},k={dir:`ltr`},A={key:0,class:`movements`},j={class:`muted`},M=p(o({__name:`CatalogRequestsView`,setup(o){let p=[{value:``,label:`همه وضعیت‌ها`},{value:`new`,label:`درخواست جدید`},{value:`approved`,label:`تأیید شده`},{value:`ready_to_ship`,label:`آماده ارسال`},{value:`delivered`,label:`تحویل به مشتری`},{value:`returned`,label:`بازگردانده شده`},{value:`cancelled`,label:`لغو شده`}],M=[{value:`warehouse`,label:`انبار`},{value:`customer`,label:`مشتری`},{value:`agent`,label:`واسطه`}],N=Object.fromEntries(p.filter(e=>e.value).map(e=>[e.value,e.label])),P=Object.fromEntries(M.map(e=>[e.value,e.label])),F=e([]),I=e(``),L=e(!0),R=e(null),z=h();async function B(){L.value=!0;try{F.value=await _.list(I.value||void 0)}catch(e){z.error(e instanceof g?e.message:`بارگذاری انجام نشد`)}finally{L.value=!1}}async function V(e,t){try{await _.updateStatus(e,t),z.success(`وضعیت به‌روز شد`),await B()}catch(e){z.error(e instanceof g?e.message:`به‌روزرسانی ناموفق بود`)}}async function H(e,t){if(!R.value){R.value=e;try{await _.updateHolder(e,{holderType:t}),z.success(`محل نگهداری به‌روز شد`),await B()}catch(e){z.error(e instanceof g?e.message:`به‌روزرسانی محل ناموفق بود`)}finally{R.value=null}}}function U(e){return P[e]??e}return n(()=>{B()}),(e,n)=>(c(),a(`div`,S,[i(`header`,C,[n[1]||=i(`div`,null,[i(`h1`,null,`درخواست کالیته فیزیکی`),i(`p`,null,`بررسی، گردش وضعیت و محل نگهداری اقلام`)],-1),s(x,{modelValue:I.value,"onUpdate:modelValue":[n[0]||=e=>I.value=e,B],options:p},null,8,[`modelValue`])]),L.value?(c(),l(y,{key:0,variant:`cards`})):F.value.length?(c(!0),a(d,{key:2},r(F.value,e=>(c(),a(`div`,{key:e.id,class:`request-card`},[i(`div`,w,[i(`div`,null,[i(`strong`,null,t(e.customerName),1),i(`span`,T,t(e.customerPhone),1),i(`span`,E,t(f(N)[e.status]??e.status),1)]),s(x,{"model-value":e.status,options:p.filter(e=>e.value),"onUpdate:modelValue":t=>V(e.id,String(t))},null,8,[`model-value`,`options`,`onUpdate:modelValue`])]),e.note?(c(),a(`p`,D,t(e.note),1)):m(``,!0),i(`table`,O,[n[2]||=i(`thead`,null,[i(`tr`,null,[i(`th`,null,`محصول`),i(`th`,null,`کد`),i(`th`,null,`تعداد`),i(`th`,null,`محل`)])],-1),i(`tbody`,null,[(c(!0),a(d,null,r(e.items,e=>(c(),a(`tr`,{key:e.id},[i(`td`,null,t(e.productName),1),i(`td`,k,t(e.productCode),1),i(`td`,null,t(e.quantity),1),i(`td`,null,[s(x,{"model-value":e.holderType,options:M,"onUpdate:modelValue":t=>H(e.id,String(t))},null,8,[`model-value`,`onUpdate:modelValue`]),e.movements?.length?(c(),a(`ul`,A,[(c(!0),a(d,null,r(e.movements,e=>(c(),a(`li`,{key:e.id},[u(t(U(e.fromHolder))+` → `+t(U(e.toHolder))+` `,1),i(`span`,j,t(f(v)(e.movedAt)),1)]))),128))])):m(``,!0)])]))),128))])])]))),128)):(c(),l(b,{key:1,title:`درخواستی نیست`}))]))}}),[[`__scopeId`,`data-v-747d8f88`]]);export{M as default};
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
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
.suggestions-panel[data-v-4182e847]{border-top:1px solid var(--color-border);margin-top:20px;padding-top:20px}.suggestions-panel__head[data-v-4182e847]{margin-bottom:12px}.suggestions-panel__title[data-v-4182e847]{margin:0 0 4px;font-size:.95rem}.suggestions-panel__hint[data-v-4182e847]{color:var(--color-text-muted);margin:0;font-size:.78rem;line-height:1.5}.suggestions-panel__loading[data-v-4182e847],.suggestions-panel__empty[data-v-4182e847]{color:var(--color-text-muted);font-size:.85rem}.suggestions-panel__add[data-v-4182e847]{flex-direction:column;gap:10px;display:flex}.field[data-v-4182e847]{flex-direction:column;gap:6px;font-size:.85rem;display:flex}.suggestions-panel__picked[data-v-4182e847]{flex-wrap:wrap;gap:6px;display:flex}.suggestions-panel__chip[data-v-4182e847]{background:var(--color-primary-soft);color:var(--color-primary);border-radius:999px;align-items:center;gap:6px;padding:4px 8px;font-size:.75rem;font-weight:700;display:inline-flex}.suggestions-panel__chip button[data-v-4182e847]{color:inherit;cursor:pointer;background:0 0;border:none;font-size:1rem;line-height:1}.suggestions-panel__sub[data-v-4182e847]{margin:16px 0 8px;font-size:.85rem}.suggestions-panel__list ul[data-v-4182e847]{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.suggestions-panel__row[data-v-4182e847]{background:var(--color-bg);border:1px solid var(--color-border);border-radius:10px;justify-content:space-between;align-items:flex-start;gap:12px;padding:10px 12px;display:flex}.suggestions-panel__actions[data-v-4182e847]{flex-direction:column;flex-shrink:0;align-items:flex-end;gap:4px;display:flex}.muted[data-v-4182e847]{color:var(--color-text-muted);margin-inline-start:4px;font-size:.75rem}.note[data-v-4182e847]{color:var(--color-text-muted);margin:4px 0 0;font-size:.75rem}.on[data-v-4182e847]{color:var(--color-success);font-size:.72rem;font-weight:700}.off[data-v-4182e847]{color:var(--color-text-muted);font-size:.72rem}.link[data-v-4182e847]{color:var(--color-primary);cursor:pointer;background:0 0;border:none;font-family:inherit;font-size:.78rem}.customers-page__tabs[data-v-db05c8d1]{gap:8px;margin-bottom:16px;display:flex}.customers-page__tab[data-v-db05c8d1]{border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);font:inherit;cursor:pointer;border-radius:999px;padding:8px 16px;font-size:.875rem;font-weight:600}.customers-page__tab.is-active[data-v-db05c8d1]{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary)}.customers-page__search[data-v-db05c8d1]{color:var(--color-text-muted);align-items:center;gap:10px;margin-bottom:16px;padding:10px 14px;display:flex}.customers-page__search-input[data-v-db05c8d1]{background:0 0;border:none;outline:none;flex:1;font-size:.875rem}.customers-page__table[data-v-db05c8d1]{overflow:hidden}.customers-page__brands[data-v-db05c8d1]{flex-wrap:wrap;gap:10px 16px;display:flex}.customers-page__brand[data-v-db05c8d1]{cursor:pointer;align-items:center;gap:8px;font-size:.875rem;display:flex}.customers-page__brand input[data-v-db05c8d1]{width:16px;height:16px;accent-color:var(--color-primary)}
.suggestions-panel[data-v-4182e847]{border-top:1px solid var(--color-border);margin-top:20px;padding-top:20px}.suggestions-panel__head[data-v-4182e847]{margin-bottom:12px}.suggestions-panel__title[data-v-4182e847]{margin:0 0 4px;font-size:.95rem}.suggestions-panel__hint[data-v-4182e847]{color:var(--color-text-muted);margin:0;font-size:.78rem;line-height:1.5}.suggestions-panel__loading[data-v-4182e847],.suggestions-panel__empty[data-v-4182e847]{color:var(--color-text-muted);font-size:.85rem}.suggestions-panel__add[data-v-4182e847]{flex-direction:column;gap:10px;display:flex}.field[data-v-4182e847]{flex-direction:column;gap:6px;font-size:.85rem;display:flex}.suggestions-panel__picked[data-v-4182e847]{flex-wrap:wrap;gap:6px;display:flex}.suggestions-panel__chip[data-v-4182e847]{background:var(--color-primary-soft);color:var(--color-primary);border-radius:999px;align-items:center;gap:6px;padding:4px 8px;font-size:.75rem;font-weight:700;display:inline-flex}.suggestions-panel__chip button[data-v-4182e847]{color:inherit;cursor:pointer;background:0 0;border:none;font-size:1rem;line-height:1}.suggestions-panel__sub[data-v-4182e847]{margin:16px 0 8px;font-size:.85rem}.suggestions-panel__list ul[data-v-4182e847]{flex-direction:column;gap:8px;margin:0;padding:0;list-style:none;display:flex}.suggestions-panel__row[data-v-4182e847]{background:var(--color-bg);border:1px solid var(--color-border);border-radius:10px;justify-content:space-between;align-items:flex-start;gap:12px;padding:10px 12px;display:flex}.suggestions-panel__actions[data-v-4182e847]{flex-direction:column;flex-shrink:0;align-items:flex-end;gap:4px;display:flex}.muted[data-v-4182e847]{color:var(--color-text-muted);margin-inline-start:4px;font-size:.75rem}.note[data-v-4182e847]{color:var(--color-text-muted);margin:4px 0 0;font-size:.75rem}.on[data-v-4182e847]{color:var(--color-success);font-size:.72rem;font-weight:700}.off[data-v-4182e847]{color:var(--color-text-muted);font-size:.72rem}.link[data-v-4182e847]{color:var(--color-primary);cursor:pointer;background:0 0;border:none;font-family:inherit;font-size:.78rem}.customers-page__tabs[data-v-2e37be8d]{gap:8px;margin-bottom:16px;display:flex}.customers-page__tab[data-v-2e37be8d]{border:1px solid var(--color-border);background:var(--color-surface);color:var(--color-text-muted);font:inherit;cursor:pointer;border-radius:999px;padding:8px 16px;font-size:.875rem;font-weight:600}.customers-page__tab.is-active[data-v-2e37be8d]{border-color:var(--color-primary);background:var(--color-primary-soft);color:var(--color-primary)}.customers-page__search[data-v-2e37be8d]{color:var(--color-text-muted);align-items:center;gap:10px;margin-bottom:16px;padding:10px 14px;display:flex}.customers-page__search-input[data-v-2e37be8d]{background:0 0;border:none;outline:none;flex:1;font-size:.875rem}.customers-page__table[data-v-2e37be8d]{overflow:hidden}
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 @@
.admin-page-header__sub[data-v-413b6ea4]{max-width:52rem;color:var(--color-text-muted);margin:6px 0 0;font-size:.8125rem;line-height:1.6}.preview-cell[data-v-413b6ea4]{white-space:nowrap;text-overflow:ellipsis;max-width:280px;overflow:hidden}.link-cell[data-v-413b6ea4]{text-overflow:ellipsis;white-space:nowrap;max-width:160px;overflow:hidden}.field[data-v-413b6ea4]{flex-direction:column;gap:6px;margin-bottom:12px;font-size:.875rem;font-weight:600;display:flex}
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
import{$ as e,At as t,E as n,H as r,M as i,U as a,Z as o,c as s,d as c,g as l,h as u,k as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as _,f as v,t as y}from"./http-BjLkjdQu.js";import{V as b,x}from"./index-pv0Ktkd1.js";import{t as S}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as C}from"./BaseEmptyState-y-wpauXF.js";var w={class:`admin-page`},T={key:2,class:`admin-table`},E={dir:`ltr`},D=[`onClick`],O={class:`field`},k={class:`field`},A={class:`field`},j={class:`field`},M=[`disabled`],N=g(l({__name:`LevelsView`,setup(l){let g=e([]),N=e(!0),P=e(!1),F=e(null),I=_(),L=o({code:``,label:``,sortOrder:0,benefitsText:``}),R=h(()=>F.value?`ویرایش سطح`:`سطح جدید`);function z(e){return e.split(`
import{$ as e,At as t,E as n,H as r,M as i,U as a,Z as o,c as s,d as c,g as l,h as u,k as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as _,f as v,t as y}from"./http-BjLkjdQu.js";import{H as b,S as x}from"./index-BzcByePp.js";import{t as S}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as C}from"./BaseEmptyState-B7aEMvHg.js";var w={class:`admin-page`},T={key:2,class:`admin-table`},E={dir:`ltr`},D=[`onClick`],O={class:`field`},k={class:`field`},A={class:`field`},j={class:`field`},M=[`disabled`],N=g(l({__name:`LevelsView`,setup(l){let g=e([]),N=e(!0),P=e(!1),F=e(null),I=_(),L=o({code:``,label:``,sortOrder:0,benefitsText:``}),R=h(()=>F.value?`ویرایش سطح`:`سطح جدید`);function z(e){return e.split(`
`).map(e=>e.trim()).filter(Boolean).map(e=>({title:e}))}async function B(){N.value=!0;try{g.value=await x.list()}catch(e){I.error(e instanceof y?e.message:`بارگذاری سطوح ناموفق بود`)}finally{N.value=!1}}function V(){F.value=null,Object.assign(L,{code:``,label:``,sortOrder:g.value.length+1,benefitsText:``}),P.value=!0}function H(e){F.value=e.id,Object.assign(L,{code:e.code,label:e.label,sortOrder:e.sortOrder,benefitsText:e.benefits.map(e=>e.title).join(`
`)}),P.value=!0}async function U(){try{let e={code:L.code.trim(),label:L.label.trim(),sortOrder:L.sortOrder,benefits:z(L.benefitsText)};F.value?await x.update(F.value,e):await x.create(e),P.value=!1,I.success(`ذخیره شد`),await B()}catch(e){I.error(e instanceof y?e.message:`ذخیره ناموفق بود`)}}return n(()=>{B()}),(e,n)=>(d(),c(`div`,w,[s(`header`,{class:`admin-page__head`},[n[5]||=s(`div`,null,[s(`h1`,null,`سطوح و مزایای مشتری`),s(`p`,null,`تعریف Level داخلی CRM و مزایای هر سطح (نمایش فقط در ادمین)`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:V},`سطح جدید`)]),N.value?(d(),f(S,{key:0,variant:`table`})):g.value.length?(d(),c(`table`,T,[n[6]||=s(`thead`,null,[s(`tr`,null,[s(`th`,null,`کد`),s(`th`,null,`عنوان`),s(`th`,null,`مزایا`),s(`th`,null,`مشتریان`),s(`th`)])],-1),s(`tbody`,null,[(d(!0),c(m,null,i(g.value,e=>(d(),c(`tr`,{key:e.id},[s(`td`,E,t(e.code),1),s(`td`,null,t(e.label),1),s(`td`,null,t(e.benefits.map(e=>e.title).join(`، `)||`—`),1),s(`td`,null,t(e.customersCount),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>H(e)},`ویرایش`,8,D)])]))),128))])])):(d(),f(C,{key:1,title:`سطحی تعریف نشده`})),u(b,{open:P.value,title:R.value,onClose:n[4]||=e=>P.value=!1},{footer:r(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!L.code.trim()||!L.label.trim(),onClick:U},` ذخیره `,8,M)]),default:r(()=>[s(`label`,O,[n[7]||=p(`کد`,-1),a(s(`input`,{"onUpdate:modelValue":n[0]||=e=>L.code=e,class:`admin-input`,dir:`ltr`},null,512),[[v,L.code]])]),s(`label`,k,[n[8]||=p(`عنوان`,-1),a(s(`input`,{"onUpdate:modelValue":n[1]||=e=>L.label=e,class:`admin-input`},null,512),[[v,L.label]])]),s(`label`,A,[n[9]||=p(`ترتیب`,-1),a(s(`input`,{"onUpdate:modelValue":n[2]||=e=>L.sortOrder=e,type:`number`,class:`admin-input`},null,512),[[v,L.sortOrder,void 0,{number:!0}]])]),s(`label`,j,[n[10]||=p(` مزایا (هر خط یک مورد) `,-1),a(s(`textarea`,{"onUpdate:modelValue":n[3]||=e=>L.benefitsText=e,class:`admin-input`,rows:`4`},null,512),[[v,L.benefitsText]])])]),_:1},8,[`open`,`title`])]))}}),[[`__scopeId`,`data-v-44c7f808`]]);export{N as default};
@@ -1 +1 @@
import{$ as e,At as t,U as n,c as r,d as i,g as a,h as o,k as s,m as c,s as l,st as u,t as d,u as f}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as p,f as m,l as h,m as g,u as _}from"./http-BjLkjdQu.js";import{F as v,I as y,L as b,P as x,R as S,z as C}from"./index-pv0Ktkd1.js";var w={class:`login-page`},T={class:`login-page__form-wrap`},E={class:`login-page__card admin-card`},D={class:`login-page__header`},O={key:0,class:`login-page__mock-hint`},k={dir:`ltr`},A={dir:`ltr`},j={key:0,class:`admin-form__error`,role:`alert`},M={class:`admin-form__field`},N={class:`login-page__input-wrap`},P={class:`login-page__field-icon`,"aria-hidden":`true`},F={class:`admin-form__field`},I={class:`login-page__input-wrap login-page__input-wrap--password`},L={class:`login-page__field-icon`,"aria-hidden":`true`},R=[`type`],z=[`aria-label`],B={class:`login-page__remember`},V=[`disabled`],H={key:0,class:`admin-btn__spinner`,"aria-hidden":`true`},U=d(a({__name:`LoginView`,setup(a){let d=C(),U=S(),W=x(),G=p(),K=e(y()?v.phone:``),q=e(y()?v.password:``),J=e(!0),Y=e(!1),X=y(),Z=l(()=>K.value.trim().length>0&&q.value.length>0);function Q(e){return e.replace(/[۰-۹]/g,e=>String(`۰۱۲۳۴۵۶۷۸۹`.indexOf(e))).replace(/[٠-٩]/g,e=>String(`٠١٢٣٤٥٦٧٨٩`.indexOf(e)))}async function $(){if(!Z.value){W.error=`لطفاً شماره موبایل و رمز عبور را وارد کنید.`;return}if(!await W.login(Q(K.value.trim()),q.value,J.value))return;G.success(`خوش آمدید، ${W.fullName}`);let e=typeof U.query.redirect==`string`?U.query.redirect:`/`,t=`/admin/`.replace(/\/$/,``),n=e.startsWith(t)?e.slice(t.length)||`/`:e;await d.replace(n)}return(e,a)=>(s(),i(`div`,w,[r(`div`,T,[r(`div`,E,[r(`div`,D,[a[8]||=r(`h2`,{class:`login-page__title`},`ورود به پنل مدیریت`,-1),a[9]||=r(`p`,{class:`login-page__subtitle`},`برای ادامه، اطلاعات حساب کاربری خود را وارد کنید.`,-1),u(X)?(s(),i(`p`,O,[a[6]||=c(` حالت موک فعال است — `,-1),r(`span`,k,t(u(v).phone),1),a[7]||=c(` / `,-1),r(`span`,A,t(u(v).password),1)])):f(``,!0)]),r(`form`,{class:`admin-form login-page__form`,onSubmit:g($,[`prevent`])},[u(W).error?(s(),i(`p`,j,t(u(W).error),1)):f(``,!0),r(`div`,M,[a[10]||=r(`label`,{for:`login-phone`},`شماره موبایل`,-1),r(`div`,N,[r(`span`,P,[o(b,{name:`phone`,size:18})]),n(r(`input`,{id:`login-phone`,"onUpdate:modelValue":a[0]||=e=>K.value=e,type:`tel`,class:`admin-input login-page__input`,placeholder:`۰۹۱۲۱۱۱۱۲۲۲۲`,autocomplete:`username`,inputmode:`tel`,dir:`ltr`,onInput:a[1]||=e=>u(W).clearError()},null,544),[[m,K.value]])])]),r(`div`,F,[a[11]||=r(`label`,{for:`login-password`},`رمز عبور`,-1),r(`div`,I,[r(`span`,L,[o(b,{name:`lock`,size:18})]),n(r(`input`,{id:`login-password`,"onUpdate:modelValue":a[2]||=e=>q.value=e,type:Y.value?`text`:`password`,class:`admin-input login-page__input login-page__input--with-toggle`,placeholder:`رمز عبور`,autocomplete:`current-password`,onInput:a[3]||=e=>u(W).clearError()},null,40,R),[[_,q.value]]),r(`button`,{type:`button`,class:`login-page__toggle-password`,"aria-label":Y.value?`پنهان کردن رمز`:`نمایش رمز`,onClick:a[4]||=e=>Y.value=!Y.value},[o(b,{name:Y.value?`eye-off`:`eye`,size:18},null,8,[`name`])],8,z)])]),r(`label`,B,[n(r(`input`,{"onUpdate:modelValue":a[5]||=e=>J.value=e,type:`checkbox`},null,512),[[h,J.value]]),a[12]||=r(`span`,null,`مرا به خاطر بسپار`,-1)]),r(`button`,{type:`submit`,class:`admin-btn admin-btn--primary login-page__submit`,disabled:!Z.value||u(W).loading},[u(W).loading?(s(),i(`span`,H)):f(``,!0),a[13]||=c(` ورود به پنل `,-1)],8,V)],32)])]),r(`div`,{class:`login-page__brand`},[r(`div`,{class:`login-page__brand-inner`},[r(`img`,{src:`/admin/brand-logo.png`,alt:`نادمی تکس`,class:`login-page__brand-logo`}),a[14]||=r(`p`,{class:`login-page__brand-badge`},`پارچه`,-1),a[15]||=r(`h1`,{class:`login-page__brand-title`},`پنل مدیریت سفارش‌های عمده`,-1),a[16]||=r(`p`,{class:`login-page__brand-text`},` مدیریت سفارش‌ها، مشتریان، قیمت‌گذاری، چک‌ها و گزارش‌های فروش در یک محیط یکپارچه. `,-1)])])]))}}),[[`__scopeId`,`data-v-df8d196a`]]);export{U as default};
import{$ as e,At as t,U as n,c as r,d as i,g as a,h as o,k as s,m as c,s as l,st as u,t as d,u as f}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as p,f as m,l as h,m as g,u as _}from"./http-BjLkjdQu.js";import{B as v,F as y,I as b,L as x,R as S,z as C}from"./index-BzcByePp.js";var w={class:`login-page`},T={class:`login-page__form-wrap`},E={class:`login-page__card admin-card`},D={class:`login-page__header`},O={key:0,class:`login-page__mock-hint`},k={dir:`ltr`},A={dir:`ltr`},j={key:0,class:`admin-form__error`,role:`alert`},M={class:`admin-form__field`},N={class:`login-page__input-wrap`},P={class:`login-page__field-icon`,"aria-hidden":`true`},F={class:`admin-form__field`},I={class:`login-page__input-wrap login-page__input-wrap--password`},L={class:`login-page__field-icon`,"aria-hidden":`true`},R=[`type`],z=[`aria-label`],B={class:`login-page__remember`},V=[`disabled`],H={key:0,class:`admin-btn__spinner`,"aria-hidden":`true`},U=d(a({__name:`LoginView`,setup(a){let d=v(),U=C(),W=y(),G=p(),K=e(x()?b.phone:``),q=e(x()?b.password:``),J=e(!0),Y=e(!1),X=x(),Z=l(()=>K.value.trim().length>0&&q.value.length>0);function Q(e){return e.replace(/[۰-۹]/g,e=>String(`۰۱۲۳۴۵۶۷۸۹`.indexOf(e))).replace(/[٠-٩]/g,e=>String(`٠١٢٣٤٥٦٧٨٩`.indexOf(e)))}async function $(){if(!Z.value){W.error=`لطفاً شماره موبایل و رمز عبور را وارد کنید.`;return}if(!await W.login(Q(K.value.trim()),q.value,J.value))return;G.success(`خوش آمدید، ${W.fullName}`);let e=typeof U.query.redirect==`string`?U.query.redirect:`/`,t=`/admin/`.replace(/\/$/,``),n=e.startsWith(t)?e.slice(t.length)||`/`:e;await d.replace(n)}return(e,a)=>(s(),i(`div`,w,[r(`div`,T,[r(`div`,E,[r(`div`,D,[a[8]||=r(`h2`,{class:`login-page__title`},`ورود به پنل مدیریت`,-1),a[9]||=r(`p`,{class:`login-page__subtitle`},`برای ادامه، اطلاعات حساب کاربری خود را وارد کنید.`,-1),u(X)?(s(),i(`p`,O,[a[6]||=c(` حالت موک فعال است — `,-1),r(`span`,k,t(u(b).phone),1),a[7]||=c(` / `,-1),r(`span`,A,t(u(b).password),1)])):f(``,!0)]),r(`form`,{class:`admin-form login-page__form`,onSubmit:g($,[`prevent`])},[u(W).error?(s(),i(`p`,j,t(u(W).error),1)):f(``,!0),r(`div`,M,[a[10]||=r(`label`,{for:`login-phone`},`شماره موبایل`,-1),r(`div`,N,[r(`span`,P,[o(S,{name:`phone`,size:18})]),n(r(`input`,{id:`login-phone`,"onUpdate:modelValue":a[0]||=e=>K.value=e,type:`tel`,class:`admin-input login-page__input`,placeholder:`۰۹۱۲۱۱۱۱۲۲۲۲`,autocomplete:`username`,inputmode:`tel`,dir:`ltr`,onInput:a[1]||=e=>u(W).clearError()},null,544),[[m,K.value]])])]),r(`div`,F,[a[11]||=r(`label`,{for:`login-password`},`رمز عبور`,-1),r(`div`,I,[r(`span`,L,[o(S,{name:`lock`,size:18})]),n(r(`input`,{id:`login-password`,"onUpdate:modelValue":a[2]||=e=>q.value=e,type:Y.value?`text`:`password`,class:`admin-input login-page__input login-page__input--with-toggle`,placeholder:`رمز عبور`,autocomplete:`current-password`,onInput:a[3]||=e=>u(W).clearError()},null,40,R),[[_,q.value]]),r(`button`,{type:`button`,class:`login-page__toggle-password`,"aria-label":Y.value?`پنهان کردن رمز`:`نمایش رمز`,onClick:a[4]||=e=>Y.value=!Y.value},[o(S,{name:Y.value?`eye-off`:`eye`,size:18},null,8,[`name`])],8,z)])]),r(`label`,B,[n(r(`input`,{"onUpdate:modelValue":a[5]||=e=>J.value=e,type:`checkbox`},null,512),[[h,J.value]]),a[12]||=r(`span`,null,`مرا به خاطر بسپار`,-1)]),r(`button`,{type:`submit`,class:`admin-btn admin-btn--primary login-page__submit`,disabled:!Z.value||u(W).loading},[u(W).loading?(s(),i(`span`,H)):f(``,!0),a[13]||=c(` ورود به پنل `,-1)],8,V)],32)])]),r(`div`,{class:`login-page__brand`},[r(`div`,{class:`login-page__brand-inner`},[r(`img`,{src:`/admin/brand-logo.png`,alt:`نادمی تکس`,class:`login-page__brand-logo`}),a[14]||=r(`p`,{class:`login-page__brand-badge`},`پارچه`,-1),a[15]||=r(`h1`,{class:`login-page__brand-title`},`پنل مدیریت سفارش‌های عمده`,-1),a[16]||=r(`p`,{class:`login-page__brand-text`},` مدیریت سفارش‌ها، مشتریان، قیمت‌گذاری، چک‌ها و گزارش‌های فروش در یک محیط یکپارچه. `,-1)])])]))}}),[[`__scopeId`,`data-v-df8d196a`]]);export{U as default};
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
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 @@
.products-page__filters[data-v-e062996d]{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:16px;padding:12px 14px;display:flex}.products-page__search[data-v-e062996d]{min-width:200px;color:var(--color-text-muted);flex:1;align-items:center;gap:10px;display:flex}.products-page__search-input[data-v-e062996d]{background:0 0;border:none;outline:none;flex:1;font-size:.875rem}.products-page__catalog-filter[data-v-e062996d]{width:min(260px,100%)}.products-page__table[data-v-e062996d]{overflow:hidden}.product-thumb[data-v-e062996d]{background:#e8eceb 50%/cover no-repeat;border-radius:8px;width:44px;height:36px;display:block}.product-thumb--empty[data-v-e062996d]{background:#eef1f0}.color-variants[data-v-e062996d]{flex-direction:column;gap:14px;display:flex}.color-variants__hint[data-v-e062996d]{color:var(--color-text-muted);margin:0 0 10px;font-size:.75rem;line-height:1.6}.color-variant[data-v-e062996d]{border:1px solid var(--color-border);background:var(--color-bg,#f7f8f7);border-radius:12px;flex-wrap:wrap;align-items:center;gap:12px;padding:10px;display:flex}.color-variant__fields[data-v-e062996d]{flex-direction:column;flex:1;align-items:flex-start;gap:8px;min-width:180px;display:flex}
-1
View File
@@ -1 +0,0 @@
.products-page__filters[data-v-761ea8ca]{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:16px;padding:12px 14px;display:flex}.products-page__search[data-v-761ea8ca]{min-width:200px;color:var(--color-text-muted);flex:1;align-items:center;gap:10px;display:flex}.products-page__search-input[data-v-761ea8ca]{background:0 0;border:none;outline:none;flex:1;font-size:.875rem}.products-page__catalog-filter[data-v-761ea8ca]{width:min(260px,100%)}.products-page__table[data-v-761ea8ca]{overflow:hidden}.product-thumb[data-v-761ea8ca]{background:#e8eceb 50%/cover no-repeat;border-radius:8px;width:44px;height:36px;display:block}.product-thumb--empty[data-v-761ea8ca]{background:#eef1f0}.color-variants[data-v-761ea8ca]{flex-direction:column;gap:14px;display:flex}.color-variants__hint[data-v-761ea8ca]{color:var(--color-text-muted);margin:0 0 10px;font-size:.75rem;line-height:1.6}.color-variant[data-v-761ea8ca]{border:1px solid var(--color-border);background:var(--color-bg,#f7f8f7);border-radius:12px;flex-wrap:wrap;align-items:center;gap:12px;padding:10px;display:flex}.color-variant__fields[data-v-761ea8ca]{flex-direction:column;flex:1;align-items:flex-start;gap:8px;min-width:180px;display:flex}.product-images[data-v-761ea8ca]{flex-wrap:wrap;gap:10px;display:flex}.product-images__item[data-v-761ea8ca]{flex-direction:column;gap:6px;width:92px;display:flex}.product-images__thumb[data-v-761ea8ca]{border:1px solid var(--color-border);background:#e8eceb 50%/cover no-repeat;border-radius:10px;width:92px;height:72px;display:block}.product-images__remove[data-v-761ea8ca],.product-images__add[data-v-761ea8ca]{border:1px dashed var(--color-border);cursor:pointer;text-align:center;background:0 0;border-radius:8px;padding:6px 8px;font-size:.75rem}.product-images__add[data-v-761ea8ca]{min-width:92px;min-height:72px;color:var(--color-text-muted);place-items:center;display:grid}
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 @@
.admin-multi-select[data-v-9a22912b]{min-width:200px;max-width:100%;position:relative}.admin-multi-select--block[data-v-9a22912b]{width:100%;min-width:0}.admin-multi-select__trigger[data-v-9a22912b]{width:100%;min-height:var(--control-height,40px);border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-surface);color:var(--color-text);cursor:pointer;transition:border-color var(--transition-fast), box-shadow var(--transition-fast), background var(--transition-fast);justify-content:space-between;align-items:center;gap:10px;padding:0 12px 0 10px;font-family:inherit;font-size:.875rem;font-weight:500;display:flex}.admin-multi-select--disabled .admin-multi-select__trigger[data-v-9a22912b]{opacity:.55;cursor:not-allowed}.admin-multi-select__trigger[data-v-9a22912b]:hover:not(:disabled){border-color:var(--color-primary);background:var(--color-elevated,var(--color-surface))}.admin-multi-select--open .admin-multi-select__trigger[data-v-9a22912b],.admin-multi-select__trigger[data-v-9a22912b]:focus-visible:not(:disabled){border-color:var(--color-primary);box-shadow:var(--focus-ring);outline:none}.admin-multi-select__value[data-v-9a22912b]{text-align:start;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.admin-multi-select__chevron[data-v-9a22912b]{color:var(--color-text-muted);transition:transform var(--transition-fast);flex-shrink:0;display:inline-flex}.admin-multi-select--open .admin-multi-select__chevron[data-v-9a22912b]{color:var(--color-primary);transform:rotate(180deg)}.admin-multi-select__chips[data-v-9a22912b]{flex-wrap:wrap;gap:6px;margin:8px 0 0;padding:0;list-style:none;display:flex}.admin-multi-select__chip[data-v-9a22912b]{background:var(--color-primary-soft);max-width:100%;color:var(--color-primary);border-radius:999px;align-items:center;gap:4px;padding:4px 8px;font-size:.75rem;font-weight:600;display:inline-flex}.admin-multi-select__chip-label[data-v-9a22912b]{text-overflow:ellipsis;white-space:nowrap;max-width:220px;overflow:hidden}.admin-multi-select__chip-remove[data-v-9a22912b]{color:inherit;cursor:pointer;background:0 0;border:none;padding:0 2px;font-size:1rem;line-height:1}.admin-multi-select__menu[data-v-9a22912b]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);box-shadow:var(--shadow-card,0 12px 28px #0f172a1f);box-sizing:border-box;flex-direction:column;gap:4px;margin:0;padding:8px;list-style:none;display:flex;overflow-y:auto}.admin-multi-select__search[data-v-9a22912b]{z-index:1;background:var(--color-surface);padding-bottom:4px;position:sticky;top:0}.admin-multi-select__search-input[data-v-9a22912b]{border:1px solid var(--color-border);background:var(--color-bg);width:100%;height:36px;color:var(--color-text);font:inherit;border-radius:10px;padding:0 12px;font-size:.8125rem}.admin-multi-select__search-input[data-v-9a22912b]:focus{border-color:var(--color-primary);box-shadow:var(--focus-ring);outline:none}.admin-multi-select__empty[data-v-9a22912b]{color:var(--color-text-muted);text-align:center;padding:12px;font-size:.8125rem}.admin-multi-select__option[data-v-9a22912b]{width:100%;color:var(--color-text);text-align:start;cursor:pointer;transition:background var(--transition-fast), border-color var(--transition-fast), color var(--transition-fast);background:0 0;border:1px solid #0000;border-radius:12px;justify-content:space-between;align-items:center;gap:10px;padding:10px 12px;font-family:inherit;font-size:.875rem;font-weight:500;display:flex}.admin-multi-select__option[data-v-9a22912b]:hover{background:var(--color-bg);border-color:var(--color-border)}.admin-multi-select__option--active[data-v-9a22912b]{background:var(--color-primary-soft);color:var(--color-primary);border-color:#0000;font-weight:600}.admin-multi-select__check[data-v-9a22912b]{color:var(--color-primary);flex-shrink:0}.settings-page__grid[data-v-e84a2579]{grid-template-columns:repeat(2,1fr);gap:16px;display:grid}.settings-page__section[data-v-e84a2579]{padding:20px}.settings-page__title[data-v-e84a2579]{margin:0 0 16px;font-size:1rem;font-weight:700}.settings-page__checks[data-v-e84a2579]{flex-direction:column;gap:12px;display:flex}.settings-page__check[data-v-e84a2579]{cursor:pointer;align-items:center;gap:10px;font-size:.875rem;display:flex}.settings-page__check input[data-v-e84a2579]{width:16px;height:16px;accent-color:var(--color-primary)}.settings-page__check--block[data-v-e84a2579]{margin-bottom:12px}.settings-page__broadcast-submit[data-v-e84a2579]{margin-top:4px}.settings-page__hint[data-v-e84a2579]{color:var(--color-text-muted);margin:6px 0 0;font-size:.75rem;line-height:1.5}@media (width<=1279px){.settings-page__grid[data-v-e84a2579]{grid-template-columns:1fr}}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.settings-page__grid[data-v-85da5885]{grid-template-columns:repeat(2,1fr);gap:16px;display:grid}.settings-page__section[data-v-85da5885]{padding:20px}.settings-page__title[data-v-85da5885]{margin:0 0 16px;font-size:1rem;font-weight:700}.settings-page__checks[data-v-85da5885]{flex-direction:column;gap:12px;display:flex}.settings-page__check[data-v-85da5885]{cursor:pointer;align-items:center;gap:10px;font-size:.875rem;display:flex}.settings-page__check input[data-v-85da5885]{width:16px;height:16px;accent-color:var(--color-primary)}.settings-page__customers[data-v-85da5885]{flex-direction:column;gap:8px;max-height:220px;padding:8px 0;display:flex;overflow:auto}.settings-page__phone[data-v-85da5885]{color:var(--color-text-muted);margin-right:6px;font-size:.8125rem}.settings-page__hint[data-v-85da5885]{color:var(--color-text-muted);margin:6px 0 0;font-size:.75rem;line-height:1.5}@media (width<=1279px){.settings-page__grid[data-v-85da5885]{grid-template-columns:1fr}}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{$ as e,At as t,E as n,M as r,c as i,d as a,g as o,k as s,l as c,m as l,r as u,t as d}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as f,t as p}from"./http-BjLkjdQu.js";import{A as m}from"./index-pv0Ktkd1.js";import{t as h}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as g}from"./BaseEmptyState-y-wpauXF.js";var _={class:`admin-page`},v={key:2,class:`admin-table`},y={class:`muted`,dir:`ltr`},b={class:`muted`,dir:`ltr`},x=[`onClick`],S=d(o({__name:`StockAlertsView`,setup(o){let d=e([]),S=e(!0),C=f();async function w(){S.value=!0;try{d.value=await m.list()}catch(e){C.error(e instanceof p?e.message:`بارگذاری ناموفق بود`)}finally{S.value=!1}}async function T(e){try{await m.notify(e),C.success(`اعلان موجودی ارسال شد`),await w()}catch(e){C.error(e instanceof p?e.message:`ارسال ناموفق بود`)}}return n(()=>{w()}),(e,n)=>(s(),a(`div`,_,[n[1]||=i(`header`,{class:`admin-page__head`},[i(`div`,null,[i(`h1`,null,`درخواست خبر موجودی`),i(`p`,null,`مشتریانی که برای محصول ناموجود درخواست اطلاع داده‌اند`)])],-1),S.value?(s(),c(h,{key:0,variant:`table`})):d.value.length?(s(),a(`table`,v,[n[0]||=i(`thead`,null,[i(`tr`,null,[i(`th`,null,`مشتری`),i(`th`,null,`محصول`),i(`th`,null,`موجودی فعلی`),i(`th`)])],-1),i(`tbody`,null,[(s(!0),a(u,null,r(d.value,e=>(s(),a(`tr`,{key:e.id},[i(`td`,null,[l(t(e.customerName)+` `,1),i(`span`,y,t(e.customerPhone),1)]),i(`td`,null,[l(t(e.productName)+` `,1),i(`span`,b,`(`+t(e.productCode)+`)`,1)]),i(`td`,null,t(e.stock??`—`),1),i(`td`,null,[i(`button`,{type:`button`,class:`admin-btn admin-btn--primary admin-btn--sm`,onClick:t=>T(e.id)},`ارسال اعلان`,8,x)])]))),128))])])):(s(),c(g,{key:1,title:`درخواستی فعال نیست`}))]))}}),[[`__scopeId`,`data-v-61131544`]]);export{S as default};
import{$ as e,At as t,E as n,M as r,c as i,d as a,g as o,k as s,l as c,m as l,r as u,t as d}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as f,t as p}from"./http-BjLkjdQu.js";import{j as m}from"./index-BzcByePp.js";import{t as h}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as g}from"./BaseEmptyState-B7aEMvHg.js";var _={class:`admin-page`},v={key:2,class:`admin-table`},y={class:`muted`,dir:`ltr`},b={class:`muted`,dir:`ltr`},x=[`onClick`],S=d(o({__name:`StockAlertsView`,setup(o){let d=e([]),S=e(!0),C=f();async function w(){S.value=!0;try{d.value=await m.list()}catch(e){C.error(e instanceof p?e.message:`بارگذاری ناموفق بود`)}finally{S.value=!1}}async function T(e){try{await m.notify(e),C.success(`اعلان موجودی ارسال شد`),await w()}catch(e){C.error(e instanceof p?e.message:`ارسال ناموفق بود`)}}return n(()=>{w()}),(e,n)=>(s(),a(`div`,_,[n[1]||=i(`header`,{class:`admin-page__head`},[i(`div`,null,[i(`h1`,null,`درخواست خبر موجودی`),i(`p`,null,`مشتریانی که برای محصول ناموجود درخواست اطلاع داده‌اند`)])],-1),S.value?(s(),c(h,{key:0,variant:`table`})):d.value.length?(s(),a(`table`,v,[n[0]||=i(`thead`,null,[i(`tr`,null,[i(`th`,null,`مشتری`),i(`th`,null,`محصول`),i(`th`,null,`موجودی فعلی`),i(`th`)])],-1),i(`tbody`,null,[(s(!0),a(u,null,r(d.value,e=>(s(),a(`tr`,{key:e.id},[i(`td`,null,[l(t(e.customerName)+` `,1),i(`span`,y,t(e.customerPhone),1)]),i(`td`,null,[l(t(e.productName)+` `,1),i(`span`,b,`(`+t(e.productCode)+`)`,1)]),i(`td`,null,t(e.stock??`—`),1),i(`td`,null,[i(`button`,{type:`button`,class:`admin-btn admin-btn--primary admin-btn--sm`,onClick:t=>T(e.id)},`ارسال اعلان`,8,x)])]))),128))])])):(s(),c(g,{key:1,title:`درخواستی فعال نیست`}))]))}}),[[`__scopeId`,`data-v-61131544`]]);export{S as default};
@@ -1 +1 @@
import{$ as e,At as t,E as n,H as r,M as i,U as a,Z as o,c as s,d as c,g as l,h as u,k as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as _,f as v,t as y}from"./http-BjLkjdQu.js";import{T as b,V as x,j as S,y as C}from"./index-pv0Ktkd1.js";import{t as w}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as T}from"./BaseEmptyState-y-wpauXF.js";import{t as E}from"./AdminSelect-DFH4VaRE.js";var D={class:`admin-page`},O={class:`admin-page__head`},k={key:2,class:`admin-table`},A={class:`muted`,dir:`ltr`},j=[`onClick`],M={class:`field`},N={class:`field`},P={class:`field`},F=[`disabled`],I=g(l({__name:`SuggestionsView`,setup(l){let g=e([]),I=e(!0),L=e([]),R=e([]),z=e(!1),B=_(),V=o({customerId:``,productId:``,note:``}),H=h(()=>L.value.map(e=>({value:e.id,label:e.name?.trim()||e.phone}))),U=h(()=>R.value.map(e=>({value:e.id,label:`${e.name} (${e.productCode})`})));async function W(){I.value=!0;try{let[e,t,n]=await Promise.all([S.list(),C.list(),b.list()]);g.value=e,L.value=t,R.value=n}catch(e){B.error(e instanceof y?e.message:`بارگذاری ناموفق بود`)}finally{I.value=!1}}async function G(){try{await S.create({customerId:V.customerId,productId:V.productId,note:V.note.trim()||void 0}),z.value=!1,V.note=``,B.success(`پیشنهاد ثبت شد`),await W()}catch(e){B.error(e instanceof y?e.message:`ثبت ناموفق بود`)}}async function K(e){try{await S.setActive(e.id,!e.isActive),await W()}catch(e){B.error(e instanceof y?e.message:`به‌روزرسانی ناموفق بود`)}}return n(()=>{W()}),(e,n)=>(d(),c(`div`,D,[s(`header`,O,[n[5]||=s(`div`,null,[s(`h1`,null,`پیشنهاد محصول`),s(`p`,null,`پیشنهاد دستی محصول به مشتری`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:n[0]||=e=>z.value=!0},`پیشنهاد جدید`)]),I.value?(d(),f(w,{key:0,variant:`table`})):g.value.length?(d(),c(`table`,k,[n[6]||=s(`thead`,null,[s(`tr`,null,[s(`th`,null,`مشتری`),s(`th`,null,`محصول`),s(`th`,null,`یادداشت`),s(`th`,null,`وضعیت`),s(`th`)])],-1),s(`tbody`,null,[(d(!0),c(m,null,i(g.value,e=>(d(),c(`tr`,{key:e.id},[s(`td`,null,t(e.customerName),1),s(`td`,null,[p(t(e.productName)+` `,1),s(`span`,A,`(`+t(e.productCode)+`)`,1)]),s(`td`,null,t(e.note||`—`),1),s(`td`,null,t(e.isActive?`فعال`:`غیرفعال`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>K(e)},t(e.isActive?`غیرفعال`:`فعال`),9,j)])]))),128))])])):(d(),f(T,{key:1,title:`پیشنهادی ثبت نشده`})),u(x,{open:z.value,title:`پیشنهاد محصول`,onClose:n[4]||=e=>z.value=!1},{footer:r(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!V.customerId||!V.productId,onClick:G},` ثبت `,8,F)]),default:r(()=>[s(`label`,M,[n[7]||=p(`مشتری `,-1),u(E,{modelValue:V.customerId,"onUpdate:modelValue":n[1]||=e=>V.customerId=e,options:H.value},null,8,[`modelValue`,`options`])]),s(`label`,N,[n[8]||=p(`محصول `,-1),u(E,{modelValue:V.productId,"onUpdate:modelValue":n[2]||=e=>V.productId=e,options:U.value},null,8,[`modelValue`,`options`])]),s(`label`,P,[n[9]||=p(`یادداشت`,-1),a(s(`textarea`,{"onUpdate:modelValue":n[3]||=e=>V.note=e,class:`admin-input`,rows:`3`},null,512),[[v,V.note]])])]),_:1},8,[`open`])]))}}),[[`__scopeId`,`data-v-56e3e728`]]);export{I as default};
import{$ as e,At as t,E as n,H as r,M as i,U as a,Z as o,c as s,d as c,g as l,h as u,k as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-QrIvvHC8.js";import{a as _,f as v,t as y}from"./http-BjLkjdQu.js";import{E as b,H as x,M as S,y as C}from"./index-BzcByePp.js";import{t as w}from"./AdminPageSkeleton-oWhHYjl4.js";import{t as T}from"./BaseEmptyState-B7aEMvHg.js";import{t as E}from"./AdminSelect-8XDtPu3U.js";var D={class:`admin-page`},O={class:`admin-page__head`},k={key:2,class:`admin-table`},A={class:`muted`,dir:`ltr`},j=[`onClick`],M={class:`field`},N={class:`field`},P={class:`field`},F=[`disabled`],I=g(l({__name:`SuggestionsView`,setup(l){let g=e([]),I=e(!0),L=e([]),R=e([]),z=e(!1),B=_(),V=o({customerId:``,productId:``,note:``}),H=h(()=>L.value.map(e=>({value:e.id,label:e.name?.trim()||e.phone}))),U=h(()=>R.value.map(e=>({value:e.id,label:`${e.name} (${e.productCode})`})));async function W(){I.value=!0;try{let[e,t,n]=await Promise.all([S.list(),C.list(),b.list()]);g.value=e,L.value=t,R.value=n}catch(e){B.error(e instanceof y?e.message:`بارگذاری ناموفق بود`)}finally{I.value=!1}}async function G(){try{await S.create({customerId:V.customerId,productId:V.productId,note:V.note.trim()||void 0}),z.value=!1,V.note=``,B.success(`پیشنهاد ثبت شد`),await W()}catch(e){B.error(e instanceof y?e.message:`ثبت ناموفق بود`)}}async function K(e){try{await S.setActive(e.id,!e.isActive),await W()}catch(e){B.error(e instanceof y?e.message:`به‌روزرسانی ناموفق بود`)}}return n(()=>{W()}),(e,n)=>(d(),c(`div`,D,[s(`header`,O,[n[5]||=s(`div`,null,[s(`h1`,null,`پیشنهاد محصول`),s(`p`,null,`پیشنهاد دستی محصول به مشتری`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:n[0]||=e=>z.value=!0},`پیشنهاد جدید`)]),I.value?(d(),f(w,{key:0,variant:`table`})):g.value.length?(d(),c(`table`,k,[n[6]||=s(`thead`,null,[s(`tr`,null,[s(`th`,null,`مشتری`),s(`th`,null,`محصول`),s(`th`,null,`یادداشت`),s(`th`,null,`وضعیت`),s(`th`)])],-1),s(`tbody`,null,[(d(!0),c(m,null,i(g.value,e=>(d(),c(`tr`,{key:e.id},[s(`td`,null,t(e.customerName),1),s(`td`,null,[p(t(e.productName)+` `,1),s(`span`,A,`(`+t(e.productCode)+`)`,1)]),s(`td`,null,t(e.note||`—`),1),s(`td`,null,t(e.isActive?`فعال`:`غیرفعال`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>K(e)},t(e.isActive?`غیرفعال`:`فعال`),9,j)])]))),128))])])):(d(),f(T,{key:1,title:`پیشنهادی ثبت نشده`})),u(x,{open:z.value,title:`پیشنهاد محصول`,onClose:n[4]||=e=>z.value=!1},{footer:r(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!V.customerId||!V.productId,onClick:G},` ثبت `,8,F)]),default:r(()=>[s(`label`,M,[n[7]||=p(`مشتری `,-1),u(E,{modelValue:V.customerId,"onUpdate:modelValue":n[1]||=e=>V.customerId=e,options:H.value},null,8,[`modelValue`,`options`])]),s(`label`,N,[n[8]||=p(`محصول `,-1),u(E,{modelValue:V.productId,"onUpdate:modelValue":n[2]||=e=>V.productId=e,options:U.value},null,8,[`modelValue`,`options`])]),s(`label`,P,[n[9]||=p(`یادداشت`,-1),a(s(`textarea`,{"onUpdate:modelValue":n[3]||=e=>V.note=e,class:`admin-input`,rows:`3`},null,512),[[v,V.note]])])]),_:1},8,[`open`])]))}}),[[`__scopeId`,`data-v-56e3e728`]]);export{I as default};
File diff suppressed because one or more lines are too long
+382
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-382
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,10 +11,10 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>پنل مدیریت | پارچه</title>
<script type="module" crossorigin src="/admin/assets/index-pv0Ktkd1.js"></script>
<script type="module" crossorigin src="/admin/assets/index-BzcByePp.js"></script>
<link rel="modulepreload" crossorigin href="/admin/assets/_plugin-vue_export-helper-QrIvvHC8.js">
<link rel="modulepreload" crossorigin href="/admin/assets/http-BjLkjdQu.js">
<link rel="stylesheet" crossorigin href="/admin/assets/index-Ddm1RcgL.css">
<link rel="stylesheet" crossorigin href="/admin/assets/index-CrGJrAMi.css">
</head>
<body>
<div id="app"></div>
+14 -3
View File
@@ -7,7 +7,7 @@ import BasicIcon from '@/components/BasicPacks/BasicIcon.vue'
import BaseModal from '@/components/ui/BaseModal.vue'
import { useSmsWalletSummary } from '@/composables/useSmsWalletSummary'
import { useToast } from '@/composables/useToast'
import { dashboardApi, notificationsApi } from '@/services/api'
import { dashboardApi, internalMessagesApi, notificationsApi } from '@/services/api'
import { formatDateTime, formatNumber, toPersianDigits } from '@/utils/format'
import { resolveAdminInboxLink } from '@/utils/inboxLinks'
import { formatCardNumber, formatSheba } from '@/utils/smsFormat'
@@ -78,7 +78,11 @@ function closePanel() {
}
function markMessageRead(message: InternalMessage) {
if (message.isRead) return
message.isRead = true
void internalMessagesApi.markRead(message.id).catch(() => {
message.isRead = false
})
}
function markNotificationRead(notification: AdminNotification) {
@@ -90,9 +94,16 @@ function markNotificationRead(notification: AdminNotification) {
}
function markAllMessagesRead() {
messageList.value.forEach((m) => {
const unread = messageList.value.filter((m) => !m.isRead)
if (!unread.length) return
unread.forEach((m) => {
m.isRead = true
})
void internalMessagesApi.markAllRead().catch(() => {
unread.forEach((m) => {
m.isRead = false
})
})
}
function markAllNotificationsRead() {
@@ -118,7 +129,7 @@ function openMessage(message: InternalMessage) {
markMessageRead(message)
navigateInboxTarget(
resolveAdminInboxLink({
link: message.link,
link: message.link || undefined,
orderId: message.orderId,
title: message.subject,
body: message.preview,
+1 -1
View File
@@ -66,7 +66,7 @@ function remove() {
حذف
</button>
<p v-if="error" class="image-picker__error">{{ error }}</p>
<p v-else-if="picked" class="image-picker__ok">تصویر انتخاب شد — برای ثبت نهایی «ذخیره» را بزنید.</p>
<p v-else-if="picked" class="image-picker__ok">تصویر انتخاب شد — برای ثبت نهایی «ثبت» را بزنید.</p>
</div>
</div>
<input
+436
View File
@@ -0,0 +1,436 @@
<script setup lang="ts">
import { computed, nextTick, onUnmounted, ref, watch } from 'vue'
import { onClickOutside } from '@vueuse/core'
import AdminIcon from '@/components/icons/AdminIcon.vue'
export type AdminMultiSelectOption = {
value: string
label: string
}
const props = withDefaults(
defineProps<{
modelValue: string[]
options: AdminMultiSelectOption[]
placeholder?: string
ariaLabel?: string
block?: boolean
searchable?: boolean
searchPlaceholder?: string
disabled?: boolean
id?: string
}>(),
{
placeholder: 'انتخاب کنید',
ariaLabel: 'انتخاب چندگانه',
block: false,
searchable: true,
searchPlaceholder: 'جستجو…',
disabled: false,
id: undefined,
},
)
const emit = defineEmits<{
'update:modelValue': [value: string[]]
}>()
const open = ref(false)
const searchQuery = ref('')
const rootRef = ref<HTMLElement | null>(null)
const menuRef = ref<HTMLElement | null>(null)
const searchInputRef = ref<HTMLInputElement | null>(null)
const menuStyle = ref<Record<string, string>>({})
onClickOutside(
rootRef,
() => {
open.value = false
searchQuery.value = ''
},
{ ignore: [menuRef] },
)
const selectedSet = computed(() => new Set(props.modelValue))
const selectedOptions = computed(() =>
props.options.filter((option) => selectedSet.value.has(option.value)),
)
const triggerLabel = computed(() => {
const n = props.modelValue.length
if (!n) return props.placeholder
if (n === 1) return selectedOptions.value[0]?.label ?? props.placeholder
return `${n} مورد انتخاب شده`
})
const filteredOptions = computed(() => {
const q = searchQuery.value.trim().toLowerCase()
if (!q) return props.options
return props.options.filter((option) => option.label.toLowerCase().includes(q))
})
function updateMenuPosition() {
const el = rootRef.value
if (!el) return
const rect = el.getBoundingClientRect()
const gap = 6
const maxMenu = Math.min(320, window.innerHeight * 0.5)
const spaceBelow = window.innerHeight - rect.bottom - gap
const spaceAbove = rect.top - gap
const openUp = spaceBelow < Math.min(160, maxMenu) && spaceAbove > spaceBelow
const maxHeight = Math.max(120, Math.min(maxMenu, openUp ? spaceAbove : spaceBelow))
menuStyle.value = {
position: 'fixed',
left: `${Math.max(8, Math.min(rect.left, window.innerWidth - Math.max(rect.width, 220) - 8))}px`,
width: `${Math.max(rect.width, 220)}px`,
zIndex: '1100',
maxHeight: `${maxHeight}px`,
...(openUp
? {
top: 'auto',
bottom: `${window.innerHeight - rect.top + gap}px`,
}
: {
top: `${rect.bottom + gap}px`,
bottom: 'auto',
}),
}
}
function bindPositionListeners(active: boolean) {
if (active) {
window.addEventListener('resize', updateMenuPosition)
window.addEventListener('scroll', updateMenuPosition, true)
} else {
window.removeEventListener('resize', updateMenuPosition)
window.removeEventListener('scroll', updateMenuPosition, true)
}
}
async function toggle() {
if (props.disabled) return
open.value = !open.value
if (open.value) {
await nextTick()
updateMenuPosition()
if (props.searchable) {
searchQuery.value = ''
await nextTick()
searchInputRef.value?.focus()
}
}
}
function toggleOption(value: string) {
const next = new Set(props.modelValue)
if (next.has(value)) next.delete(value)
else next.add(value)
emit('update:modelValue', [...next])
}
function removeChip(value: string) {
emit(
'update:modelValue',
props.modelValue.filter((id) => id !== value),
)
}
watch(open, (isOpen) => {
bindPositionListeners(isOpen)
if (isOpen) {
void nextTick(() => updateMenuPosition())
}
})
onUnmounted(() => {
bindPositionListeners(false)
})
</script>
<template>
<div
:id="id"
ref="rootRef"
class="admin-multi-select"
:class="{
'admin-multi-select--open': open,
'admin-multi-select--block': block,
'admin-multi-select--disabled': disabled,
}"
>
<button
type="button"
class="admin-multi-select__trigger"
:aria-label="ariaLabel"
:aria-expanded="open"
aria-haspopup="listbox"
:disabled="disabled"
@click="toggle"
>
<span class="admin-multi-select__value">{{ triggerLabel }}</span>
<span class="admin-multi-select__chevron" aria-hidden="true">
<AdminIcon name="chevron-down" :size="16" />
</span>
</button>
<ul v-if="selectedOptions.length && !disabled" class="admin-multi-select__chips">
<li v-for="option in selectedOptions" :key="option.value" class="admin-multi-select__chip">
<span class="admin-multi-select__chip-label">{{ option.label }}</span>
<button
type="button"
class="admin-multi-select__chip-remove"
aria-label="حذف"
@click="removeChip(option.value)"
>
×
</button>
</li>
</ul>
<Teleport to="body">
<ul
v-if="open"
ref="menuRef"
class="admin-multi-select__menu"
role="listbox"
:aria-label="ariaLabel"
:style="menuStyle"
aria-multiselectable="true"
>
<li v-if="searchable" class="admin-multi-select__search" role="presentation">
<input
ref="searchInputRef"
v-model="searchQuery"
type="search"
class="admin-multi-select__search-input"
:placeholder="searchPlaceholder"
@click.stop
@keydown.esc.stop="open = false"
/>
</li>
<li v-if="!filteredOptions.length" class="admin-multi-select__empty" role="presentation">
موردی یافت نشد
</li>
<li v-for="option in filteredOptions" :key="option.value" role="presentation">
<button
type="button"
class="admin-multi-select__option"
:class="{ 'admin-multi-select__option--active': selectedSet.has(option.value) }"
role="option"
:aria-selected="selectedSet.has(option.value)"
@click="toggleOption(option.value)"
>
<span class="admin-multi-select__option-label">{{ option.label }}</span>
<AdminIcon
v-if="selectedSet.has(option.value)"
name="check"
:size="16"
class="admin-multi-select__check"
/>
</button>
</li>
</ul>
</Teleport>
</div>
</template>
<style scoped>
.admin-multi-select {
position: relative;
min-width: 200px;
max-width: 100%;
}
.admin-multi-select--block {
width: 100%;
min-width: 0;
}
.admin-multi-select__trigger {
width: 100%;
min-height: var(--control-height, 40px);
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 0 12px 0 10px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text);
font-family: inherit;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition:
border-color var(--transition-fast),
box-shadow var(--transition-fast),
background var(--transition-fast);
}
.admin-multi-select--disabled .admin-multi-select__trigger {
opacity: 0.55;
cursor: not-allowed;
}
.admin-multi-select__trigger:hover:not(:disabled) {
border-color: var(--color-primary);
background: var(--color-elevated, var(--color-surface));
}
.admin-multi-select--open .admin-multi-select__trigger,
.admin-multi-select__trigger:focus-visible:not(:disabled) {
outline: none;
border-color: var(--color-primary);
box-shadow: var(--focus-ring);
}
.admin-multi-select__value {
flex: 1;
min-width: 0;
text-align: start;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.admin-multi-select__chevron {
display: inline-flex;
flex-shrink: 0;
color: var(--color-text-muted);
transition: transform var(--transition-fast);
}
.admin-multi-select--open .admin-multi-select__chevron {
transform: rotate(180deg);
color: var(--color-primary);
}
.admin-multi-select__chips {
list-style: none;
margin: 8px 0 0;
padding: 0;
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.admin-multi-select__chip {
display: inline-flex;
align-items: center;
gap: 4px;
max-width: 100%;
padding: 4px 8px;
border-radius: 999px;
background: var(--color-primary-soft);
color: var(--color-primary);
font-size: 0.75rem;
font-weight: 600;
}
.admin-multi-select__chip-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 220px;
}
.admin-multi-select__chip-remove {
border: none;
background: transparent;
color: inherit;
font-size: 1rem;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
.admin-multi-select__menu {
margin: 0;
padding: 8px;
list-style: none;
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-card, 0 12px 28px rgba(15, 23, 42, 0.12));
display: flex;
flex-direction: column;
gap: 4px;
overflow-y: auto;
box-sizing: border-box;
}
.admin-multi-select__search {
position: sticky;
top: 0;
z-index: 1;
background: var(--color-surface);
padding-bottom: 4px;
}
.admin-multi-select__search-input {
width: 100%;
height: 36px;
padding: 0 12px;
border: 1px solid var(--color-border);
border-radius: 10px;
background: var(--color-bg);
color: var(--color-text);
font: inherit;
font-size: 0.8125rem;
}
.admin-multi-select__search-input:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: var(--focus-ring);
}
.admin-multi-select__empty {
padding: 12px;
font-size: 0.8125rem;
color: var(--color-text-muted);
text-align: center;
}
.admin-multi-select__option {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 10px 12px;
border: 1px solid transparent;
border-radius: 12px;
background: transparent;
color: var(--color-text);
font-family: inherit;
font-size: 0.875rem;
font-weight: 500;
text-align: start;
cursor: pointer;
transition:
background var(--transition-fast),
border-color var(--transition-fast),
color var(--transition-fast);
}
.admin-multi-select__option:hover {
background: var(--color-bg);
border-color: var(--color-border);
}
.admin-multi-select__option--active {
background: var(--color-primary-soft);
border-color: transparent;
color: var(--color-primary);
font-weight: 600;
}
.admin-multi-select__check {
flex-shrink: 0;
color: var(--color-primary);
}
</style>
+6
View File
@@ -92,6 +92,12 @@ const router = createRouter({
component: () => import('@/views/banners/BannersView.vue'),
meta: { title: 'بنرها' },
},
{
path: 'internal-messages',
name: 'internal-messages',
component: () => import('@/views/internal-messages/InternalMessagesView.vue'),
meta: { title: 'پیام‌های داخلی' },
},
{
path: 'catalogs',
name: 'catalogs',
+32 -4
View File
@@ -152,6 +152,7 @@ export const catalogsApi = {
export type CreateProductPayload = {
companyId: string
catalogId: string
brandId?: string | null
name: string
code?: string
productCode?: string
@@ -213,7 +214,6 @@ export type CreateCustomerPayload = {
assignedAgentId?: string | null
appRole?: 'customer' | 'agent'
agentCode?: string | null
brandIds?: string[]
}
export type UpdateCustomerPayload = Partial<CreateCustomerPayload>
@@ -478,8 +478,36 @@ export const notificationsApi = {
apiRequest<{ ok: boolean }>(`/admin/notifications/${id}/read`, { method: 'PATCH' }),
markAllRead: () =>
apiRequest<{ ok: boolean }>('/admin/notifications/read-all', { method: 'PATCH' }),
broadcast: (body: { title: string; body: string; customerIds?: string[]; tone?: string }) =>
apiRequest<{ sent: number }>('/admin/notifications/broadcast', { method: 'POST', body }),
broadcast: (body: {
title: string
body: string
allActive?: boolean
customerIds?: string[]
tone?: string
}) => apiRequest<{ sent: number }>('/admin/notifications/broadcast', { method: 'POST', body }),
}
/* ── Internal messages (admin inbox) ─────────────────────── */
export type InternalMessagePayload = {
senderName: string
subject: string
preview: string
link?: string | null
}
export const internalMessagesApi = {
list: () => apiRequest<InternalMessage[]>('/admin/internal-messages'),
create: (body: InternalMessagePayload) =>
apiRequest<InternalMessage>('/admin/internal-messages', { body }),
update: (id: string, body: Partial<InternalMessagePayload>) =>
apiRequest<InternalMessage>(`/admin/internal-messages/${id}`, { method: 'PATCH', body }),
remove: (id: string) =>
apiRequest<{ ok: boolean }>(`/admin/internal-messages/${id}`, { method: 'DELETE' }),
markRead: (id: string) =>
apiRequest<InternalMessage>(`/admin/internal-messages/${id}/read`, { method: 'PATCH' }),
markAllRead: () =>
apiRequest<{ ok: boolean }>('/admin/internal-messages/read-all', { method: 'PATCH' }),
}
/* ── Catalog requests (physical) ──────────────────────────── */
@@ -539,7 +567,7 @@ export type Brand = {
description?: string
isActive: boolean
displayOrder: number
customersCount?: number
productsCount?: number
}
export type AppBanner = {
+2 -2
View File
@@ -212,8 +212,6 @@ export interface Customer {
levelLabel?: string
assignedAgentId?: string | null
assignedAgentName?: string
brandIds?: string[]
brands?: { id: string; name: string }[]
}
export interface Catalog {
@@ -234,6 +232,8 @@ export interface Product {
catalogId: string
catalogCode: string
companyName: string
brandId?: string | null
brandName?: string | null
name: string
color: string
productCode: string
+1
View File
@@ -7,6 +7,7 @@ export const ADMIN_NAV_ITEMS: NavItem[] = [
{ name: 'products', label: 'محصولات', path: '/products', icon: 'products' },
{ name: 'categories', label: 'دسته‌بندی‌ها', path: '/categories', icon: 'catalogs' },
{ name: 'banners', label: 'بنرها', path: '/banners', icon: 'image' },
{ name: 'internal-messages', label: 'پیام‌های داخلی', path: '/internal-messages', icon: 'message' },
{ name: 'catalogs', label: 'کالیته‌ها', path: '/catalogs', icon: 'catalogs' },
{
name: 'catalog-requests',
+2 -2
View File
@@ -86,7 +86,7 @@ onMounted(() => {
<tr>
<th>نام</th>
<th>شناسه لاتین</th>
<th>مشتریان</th>
<th>محصولات</th>
<th>فعال</th>
<th></th>
</tr>
@@ -95,7 +95,7 @@ onMounted(() => {
<tr v-for="b in list" :key="b.id">
<td>{{ b.name }}</td>
<td dir="ltr">{{ b.slug || '—' }}</td>
<td>{{ b.customersCount ?? 0 }}</td>
<td>{{ b.productsCount ?? 0 }}</td>
<td>{{ b.isActive ? 'بله' : 'خیر' }}</td>
<td><button type="button" class="link" @click="openEdit(b)">ویرایش</button></td>
</tr>
+2 -49
View File
@@ -7,7 +7,7 @@ import BaseEmptyState from '@/components/ui/BaseEmptyState.vue'
import ActiveSwitchCell from '@/components/ui/ActiveSwitchCell.vue'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import AdminSelect from '@/components/ui/AdminSelect.vue'
import { brandsApi, customersApi, levelsApi, type Brand, type CustomerLevel } from '@/services/api'
import { customersApi, levelsApi, type CustomerLevel } from '@/services/api'
import CustomerSuggestionsPanel from './CustomerSuggestionsPanel.vue'
import { ApiError } from '@/services/http'
import { useToast } from '@/composables/useToast'
@@ -18,7 +18,6 @@ import type { Customer, CustomerType } from '@/types'
const customerList = ref<Customer[]>([])
const levels = ref<CustomerLevel[]>([])
const agents = ref<Customer[]>([])
const brands = ref<Brand[]>([])
const searchQuery = ref('')
const roleTab = ref<'customer' | 'agent'>('customer')
const loading = ref(true)
@@ -84,7 +83,6 @@ const emptyForm = () => ({
isActive: true,
levelDefId: '' as string,
assignedAgentId: '' as string,
brandIds: [] as string[],
appRole: 'customer' as 'customer' | 'agent',
agentCode: '',
})
@@ -109,14 +107,6 @@ const filteredCustomers = computed(() => {
)
})
function toggleBrand(brandId: string, checked: boolean) {
if (checked) {
if (!form.brandIds.includes(brandId)) form.brandIds.push(brandId)
} else {
form.brandIds = form.brandIds.filter((id) => id !== brandId)
}
}
async function loadCustomers() {
loading.value = true
try {
@@ -143,14 +133,12 @@ function setRoleTab(tab: 'customer' | 'agent') {
async function loadLookups() {
try {
const [levelsData, agentsData, brandsData] = await Promise.all([
const [levelsData, agentsData] = await Promise.all([
levelsApi.list(),
customersApi.list(undefined, 'agent'),
brandsApi.list(),
])
levels.value = levelsData
agents.value = agentsData
brands.value = brandsData
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'بارگذاری اطلاعات کمکی انجام نشد.')
}
@@ -180,7 +168,6 @@ function openEditModal(customer: Customer) {
form.isActive = customer.isActive
form.levelDefId = customer.levelDefId ?? ''
form.assignedAgentId = customer.assignedAgentId ?? ''
form.brandIds = [...(customer.brandIds ?? [])]
form.appRole = customer.appRole === 'agent' ? 'agent' : 'customer'
form.agentCode = customer.agentCode ?? ''
formError.value = ''
@@ -207,7 +194,6 @@ async function submitCustomer() {
isActive: form.isActive,
levelDefId: form.levelDefId || null,
assignedAgentId: form.assignedAgentId || null,
brandIds: [...form.brandIds],
appRole: form.appRole,
agentCode: form.appRole === 'agent' ? form.agentCode.trim() || null : null,
}
@@ -483,20 +469,6 @@ function clearSearch() {
</div>
</div>
<div v-if="brands.length" class="admin-form__field">
<label>برندها</label>
<div class="customers-page__brands">
<label v-for="brand in brands" :key="brand.id" class="customers-page__brand">
<input
type="checkbox"
:checked="form.brandIds.includes(brand.id)"
@change="toggleBrand(brand.id, ($event.target as HTMLInputElement).checked)"
/>
<span>{{ brand.name }}</span>
</label>
</div>
</div>
<div class="admin-form__field">
<BaseSwitch v-model="form.isActive" label="وضعیت" />
</div>
@@ -570,23 +542,4 @@ function clearSearch() {
overflow: hidden;
}
.customers-page__brands {
display: flex;
flex-wrap: wrap;
gap: 10px 16px;
}
.customers-page__brand {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.875rem;
cursor: pointer;
}
.customers-page__brand input {
width: 16px;
height: 16px;
accent-color: var(--color-primary);
}
</style>
@@ -0,0 +1,232 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import AdminPageSkeleton from '@/components/ui/AdminPageSkeleton.vue'
import BaseModal from '@/components/ui/BaseModal.vue'
import BaseEmptyState from '@/components/ui/BaseEmptyState.vue'
import { internalMessagesApi } from '@/services/api'
import { ApiError } from '@/services/http'
import { useToast } from '@/composables/useToast'
import { useAuthStore } from '@/stores/auth'
import { formatDateTime } from '@/utils/format'
import type { InternalMessage } from '@/types'
const auth = useAuthStore()
const list = ref<InternalMessage[]>([])
const loading = ref(true)
const saving = ref(false)
const modalOpen = ref(false)
const editingId = ref<string | null>(null)
const toast = useToast()
const form = reactive({
senderName: '',
subject: '',
preview: '',
link: '',
})
const modalTitle = computed(() =>
editingId.value ? 'ویرایش پیام داخلی' : 'پیام داخلی جدید',
)
async function load() {
loading.value = true
try {
list.value = await internalMessagesApi.list()
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'بارگذاری پیام‌ها ناموفق بود.')
} finally {
loading.value = false
}
}
function openAdd() {
editingId.value = null
Object.assign(form, {
senderName: auth.fullName || 'پشتیبانی',
subject: '',
preview: '',
link: '',
})
modalOpen.value = true
}
function openEdit(row: InternalMessage) {
editingId.value = row.id
Object.assign(form, {
senderName: row.senderName,
subject: row.subject,
preview: row.preview,
link: row.link || '',
})
modalOpen.value = true
}
async function save() {
if (!form.senderName.trim() || !form.subject.trim() || !form.preview.trim()) {
toast.error('فرستنده، موضوع و متن پیام الزامی است.')
return
}
saving.value = true
const payload = {
senderName: form.senderName.trim(),
subject: form.subject.trim(),
preview: form.preview.trim(),
link: form.link.trim() || null,
}
try {
if (editingId.value) {
await internalMessagesApi.update(editingId.value, payload)
toast.success('پیام به‌روزرسانی شد.')
} else {
await internalMessagesApi.create(payload)
toast.success('پیام برای همه اپراتورها ارسال شد.')
}
modalOpen.value = false
await load()
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'ذخیره پیام ناموفق بود.')
} finally {
saving.value = false
}
}
async function remove(row: InternalMessage) {
if (!window.confirm(`پیام «${row.subject}» حذف شود؟`)) return
try {
await internalMessagesApi.remove(row.id)
toast.success('پیام حذف شد.')
await load()
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'حذف پیام ناموفق بود.')
}
}
onMounted(() => {
void load()
})
</script>
<template>
<div class="internal-messages-page">
<div class="admin-page-header">
<div>
<h1 class="admin-page-header__title">پیام‌های داخلی</h1>
<p class="admin-page-header__sub">
پیام برای تیم پشتیبانی در آیکن نامه بالای پنل نمایش داده می‌شود. با کلیک روی پیام، در صورت
تعیین لینک به همان صفحه می‌روید.
</p>
</div>
<button type="button" class="admin-btn admin-btn--primary" @click="openAdd">پیام جدید</button>
</div>
<AdminPageSkeleton v-if="loading" variant="table" />
<BaseEmptyState
v-else-if="!list.length"
title="پیامی ثبت نشده"
description="اولین پیام داخلی را برای تیم ارسال کنید."
>
<button type="button" class="admin-btn admin-btn--primary admin-btn--sm" @click="openAdd">
پیام جدید
</button>
</BaseEmptyState>
<div v-else class="admin-card">
<table class="admin-table">
<thead>
<tr>
<th>موضوع</th>
<th>فرستنده</th>
<th>خلاصه</th>
<th>لینک</th>
<th>زمان</th>
<th>خوانده‌شده</th>
<th class="actions">عملیات</th>
</tr>
</thead>
<tbody>
<tr v-for="row in list" :key="row.id">
<td><strong>{{ row.subject }}</strong></td>
<td>{{ row.senderName }}</td>
<td class="preview-cell">{{ row.preview }}</td>
<td dir="ltr" class="link-cell">{{ row.link || '—' }}</td>
<td>{{ formatDateTime(row.sentAt) }}</td>
<td>{{ row.isRead ? 'بله' : 'خیر' }}</td>
<td class="actions">
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="openEdit(row)">
ویرایش
</button>
<button type="button" class="admin-btn admin-btn--ghost admin-btn--xs" @click="remove(row)">
حذف
</button>
</td>
</tr>
</tbody>
</table>
</div>
<BaseModal :open="modalOpen" :title="modalTitle" size="md" @close="modalOpen = false">
<label class="field">
نام فرستنده
<input v-model="form.senderName" type="text" class="admin-input" />
</label>
<label class="field">
موضوع
<input v-model="form.subject" type="text" class="admin-input" />
</label>
<label class="field">
متن پیام (خلاصه در لیست نامه)
<textarea v-model="form.preview" class="admin-input" rows="4" />
</label>
<label class="field">
لینک (اختیاری)
<input
v-model="form.link"
type="text"
class="admin-input"
dir="ltr"
placeholder="/orders یا /settings"
/>
</label>
<template #footer>
<button type="button" class="admin-btn admin-btn--outline" @click="modalOpen = false">انصراف</button>
<button type="button" class="admin-btn admin-btn--primary" :disabled="saving" @click="save">
{{ saving ? 'در حال ذخیره…' : 'ذخیره' }}
</button>
</template>
</BaseModal>
</div>
</template>
<style scoped>
.admin-page-header__sub {
margin: 6px 0 0;
max-width: 52rem;
font-size: 0.8125rem;
color: var(--color-text-muted);
line-height: 1.6;
}
.preview-cell {
max-width: 280px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.link-cell {
max-width: 160px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
margin-bottom: 12px;
font-size: 0.875rem;
font-weight: 600;
}
</style>
+58 -97
View File
@@ -8,16 +8,17 @@ import BaseEmptyState from '@/components/ui/BaseEmptyState.vue'
import ActiveSwitchCell from '@/components/ui/ActiveSwitchCell.vue'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import AdminSelect from '@/components/ui/AdminSelect.vue'
import { catalogsApi, productsApi, settingsApi } from '@/services/api'
import { brandsApi, catalogsApi, productsApi, settingsApi, type Brand } from '@/services/api'
import { ApiError } from '@/services/http'
import { useToast } from '@/composables/useToast'
import { formatCurrency, formatNumber } from '@/utils/format'
import type { Catalog, Product } from '@/types'
import AdminCurrencyInput from '@/components/ui/AdminCurrencyInput.vue'
import { fileToCatalogImage, toCssBackgroundUrl } from '@/utils/catalogImage'
import { toCssBackgroundUrl } from '@/utils/catalogImage'
const productList = ref<Product[]>([])
const catalogs = ref<Catalog[]>([])
const brands = ref<Brand[]>([])
const usageOptions = ref<Array<{ value: string; label: string }>>([
{ value: 'trousers', label: 'شلوار' },
{ value: 'suit', label: 'کت‌وشلوار' },
@@ -53,8 +54,16 @@ const catalogFormOptions = computed(() =>
})),
)
const brandFormOptions = computed(() => [
{ value: '', label: 'بدون برند' },
...brands.value
.filter((b) => b.isActive)
.map((b) => ({ value: b.id, label: b.name })),
])
const emptyForm = () => ({
catalogId: catalogs.value[0]?.id ?? '',
brandId: '',
name: '',
color: '',
productCode: '',
@@ -70,8 +79,11 @@ const emptyForm = () => ({
subtitle: '',
rollMeters: 50,
isNew: false,
colorRows: [{ name: '', imageUrl: '' }] as Array<{ name: string; imageUrl: string }>,
extraImageUrls: [] as string[],
colorRows: [{ id: crypto.randomUUID(), name: '', imageUrl: '' }] as Array<{
id: string
name: string
imageUrl: string
}>,
})
const form = reactive(emptyForm())
@@ -104,13 +116,15 @@ const filteredProducts = computed(() => {
async function loadData() {
loading.value = true
try {
const [productsData, catalogsData, settings] = await Promise.all([
const [productsData, catalogsData, brandsData, settings] = await Promise.all([
productsApi.list(),
catalogsApi.list(),
brandsApi.list(),
settingsApi.get().catch(() => null),
])
productList.value = productsData
catalogs.value = catalogsData
brands.value = brandsData
if (settings?.categoryDefsJson?.usage?.length) {
usageOptions.value = [
...settings.categoryDefsJson.usage.map((item) => ({
@@ -152,6 +166,7 @@ function openEditModal(product: Product) {
modalMode.value = 'edit'
editingId.value = product.id
form.catalogId = product.catalogId
form.brandId = product.brandId ?? ''
form.name = product.name
form.color = product.color
form.productCode = product.productCode
@@ -172,53 +187,47 @@ function openEditModal(product: Product) {
const gallery = [...(product.imageUrls ?? [])]
if (colors.length) {
form.colorRows = colors.map((name, index) => ({
id: crypto.randomUUID(),
name,
imageUrl: colorImages[index] || gallery[index] || '',
}))
const used = new Set(form.colorRows.map((row) => row.imageUrl).filter(Boolean))
form.extraImageUrls = gallery.filter((url) => !used.has(url))
for (const url of gallery) {
if (!used.has(url)) {
form.colorRows.push({
id: crypto.randomUUID(),
name: `رنگ ${form.colorRows.length + 1}`,
imageUrl: url,
})
used.add(url)
}
}
} else if (gallery.length) {
form.colorRows = gallery.map((url, index) => ({
id: crypto.randomUUID(),
name: index === 0 ? product.color || '' : `رنگ ${index + 1}`,
imageUrl: url,
}))
} else {
form.colorRows = [{ name: product.color || '', imageUrl: gallery[0] || '' }]
form.extraImageUrls = gallery.slice(1)
form.colorRows = [
{ id: crypto.randomUUID(), name: product.color || '', imageUrl: '' },
]
}
formError.value = ''
}
function addColorRow() {
form.colorRows.push({ name: '', imageUrl: '' })
form.colorRows.push({ id: crypto.randomUUID(), name: '', imageUrl: '' })
}
function removeColorRow(index: number) {
if (form.colorRows.length <= 1) {
form.colorRows[0] = { name: '', imageUrl: '' }
form.colorRows[0] = { id: form.colorRows[0]?.id ?? crypto.randomUUID(), name: '', imageUrl: '' }
return
}
form.colorRows.splice(index, 1)
}
const imageBusy = ref(false)
async function addProductImages(event: Event) {
const input = event.target as HTMLInputElement
const files = [...(input.files ?? [])]
input.value = ''
if (!files.length) return
imageBusy.value = true
formError.value = ''
try {
const next = await Promise.all(files.map((file) => fileToCatalogImage(file)))
form.extraImageUrls = [...form.extraImageUrls, ...next]
} catch (e) {
formError.value = e instanceof Error ? e.message : 'بارگذاری تصویر ناموفق بود.'
} finally {
imageBusy.value = false
}
}
function removeProductImage(index: number) {
form.extraImageUrls.splice(index, 1)
}
function buildProductMedia() {
const colorEntries: Array<{ name: string; imageUrl?: string }> = []
const imageUrls: string[] = []
@@ -244,8 +253,6 @@ function buildProductMedia() {
pushImage(imageUrl)
})
form.extraImageUrls.forEach((url) => pushImage(url))
return { colorEntries, imageUrls }
}
@@ -294,6 +301,7 @@ async function submitProduct() {
price: form.price,
internalStock: form.internalStock,
isActive: form.isActive,
brandId: form.brandId || null,
...catalogPayload,
})
const index = productList.value.findIndex((p) => p.id === editingId.value)
@@ -312,6 +320,7 @@ async function submitProduct() {
price: form.price,
internalStock: form.internalStock,
isActive: form.isActive,
brandId: form.brandId || null,
...catalogPayload,
})
productList.value.push(created)
@@ -396,6 +405,7 @@ function clearFilters() {
<th>کد محصول</th>
<th>نام</th>
<th>رنگ</th>
<th>برند</th>
<th>کالیته</th>
<th>شرکت</th>
<th class="numeric">حداقل متراژ</th>
@@ -418,6 +428,7 @@ function clearFilters() {
<td>{{ product.productCode }}</td>
<td>{{ product.name }}</td>
<td>{{ product.color }}</td>
<td>{{ product.brandName || '—' }}</td>
<td>{{ product.catalogCode }}</td>
<td>{{ product.companyName }}</td>
<td class="numeric">{{ formatNumber(product.minMeterage) }} متر</td>
@@ -485,6 +496,17 @@ function clearFilters() {
/>
</div>
<div class="admin-form__field">
<label>برند</label>
<AdminSelect
v-model="form.brandId"
block
aria-label="برند محصول"
placeholder="انتخاب برند"
:options="brandFormOptions"
/>
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-name">نام محصول <span class="admin-form__required">*</span></label>
@@ -571,7 +593,7 @@ function clearFilters() {
تصویر با زدن «ذخیره تغییرات» روی سرور ثبت می‌شود.
</p>
<div class="color-variants">
<div v-for="(row, index) in form.colorRows" :key="index" class="color-variant">
<div v-for="(row, index) in form.colorRows" :key="row.id" class="color-variant">
<AdminImagePicker
:model-value="row.imageUrl || null"
@update:model-value="row.imageUrl = $event ?? ''"
@@ -594,27 +616,6 @@ function clearFilters() {
</div>
</div>
<div class="admin-form__field">
<label>تصاویر عمومی محصول (اختیاری)</label>
<p class="color-variants__hint">پس از انتخاب فایل، حتماً دکمه ذخیره را بزنید.</p>
<div class="product-images">
<div
v-for="(src, index) in form.extraImageUrls"
:key="`${index}-${src.slice(0, 24)}`"
class="product-images__item"
>
<span class="product-images__thumb" :style="{ backgroundImage: toCssBackgroundUrl(src) }" />
<button type="button" class="product-images__remove" @click="removeProductImage(index)">
حذف
</button>
</div>
<label class="product-images__add">
<input type="file" accept="image/*" multiple hidden :disabled="imageBusy" @change="addProductImages" />
{{ imageBusy ? '...' : '+ افزودن تصویر' }}
</label>
</div>
</div>
<div class="admin-form__field">
<BaseSwitch v-model="form.isNew" label="محصول جدید" />
</div>
@@ -714,44 +715,4 @@ function clearFilters() {
align-items: flex-start;
}
.product-images {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.product-images__item {
display: flex;
flex-direction: column;
gap: 6px;
width: 92px;
}
.product-images__thumb {
display: block;
width: 92px;
height: 72px;
border-radius: 10px;
background: #e8eceb center / cover no-repeat;
border: 1px solid var(--color-border);
}
.product-images__remove,
.product-images__add {
border: 1px dashed var(--color-border);
background: transparent;
border-radius: 8px;
padding: 6px 8px;
font-size: 0.75rem;
cursor: pointer;
text-align: center;
}
.product-images__add {
display: grid;
place-items: center;
min-width: 92px;
min-height: 72px;
color: var(--color-text-muted);
}
</style>
+66 -47
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from 'vue'
import { computed, onMounted, reactive, ref, watch } from 'vue'
import AdminPageSkeleton from '@/components/ui/AdminPageSkeleton.vue'
import AdminMultiSelect from '@/components/ui/AdminMultiSelect.vue'
import { useToast } from '@/composables/useToast'
import {
customersApi,
@@ -34,25 +35,23 @@ const broadcast = reactive({ title: '', body: '' })
const broadcasting = ref(false)
const customers = ref<Customer[]>([])
const selectedCustomerIds = ref<string[]>([])
const sendToAllActive = ref(false)
const loading = ref(true)
const saving = ref(false)
const broadcastButtonLabel = computed(() => {
if (broadcasting.value) return 'در حال ارسال…'
if (selectedCustomerIds.value.length) {
return `ارسال به ${selectedCustomerIds.value.length} مشتری انتخاب‌شده`
}
return 'ارسال به همه مشتریان فعال'
})
const activeCustomerOptions = computed(() =>
customers.value
.filter((c) => c.isActive)
.map((c) => ({
value: c.id,
label: `${c.name} · ${c.phone}${c.appRole === 'agent' ? ' (واسطه)' : ''}`,
})),
)
function toggleCustomer(id: string, checked: boolean) {
if (checked) {
if (!selectedCustomerIds.value.includes(id)) selectedCustomerIds.value.push(id)
} else {
selectedCustomerIds.value = selectedCustomerIds.value.filter((cid) => cid !== id)
}
}
watch(sendToAllActive, (all) => {
if (all) selectedCustomerIds.value = []
})
onMounted(async () => {
loading.value = true
@@ -97,21 +96,41 @@ async function saveSettings() {
}
async function sendBroadcast() {
if (!broadcast.title.trim() || !broadcast.body.trim() || broadcasting.value) return
if (broadcasting.value) return
const title = broadcast.title.trim()
const body = broadcast.body.trim()
if (!title || !body) {
toast.error('عنوان و متن اعلان را وارد کنید.')
return
}
if (!sendToAllActive.value && !selectedCustomerIds.value.length) {
toast.error('حداقل یک مشتری انتخاب کنید یا «ارسال به همه مشتریان فعال» را فعال کنید.')
return
}
broadcasting.value = true
try {
const payload: { title: string; body: string; customerIds?: string[] } = {
title: broadcast.title.trim(),
body: broadcast.body.trim(),
}
if (selectedCustomerIds.value.length) {
const payload: {
title: string
body: string
allActive?: boolean
customerIds?: string[]
} = { title, body }
if (sendToAllActive.value) {
payload.allActive = true
} else {
payload.customerIds = [...selectedCustomerIds.value]
}
const res = await notificationsApi.broadcast(payload)
if (!res.sent) {
toast.warning('هیچ مشتری فعالی برای ارسال پیدا نشد.')
return
}
toast.success(`اعلان برای ${res.sent} مشتری ارسال شد.`)
broadcast.title = ''
broadcast.body = ''
selectedCustomerIds.value = []
sendToAllActive.value = false
} catch (e) {
toast.error(e instanceof Error ? e.message : 'ارسال اعلان ناموفق بود.')
} finally {
@@ -262,26 +281,33 @@ async function sendBroadcast() {
<label for="broadcast-body">متن</label>
<textarea id="broadcast-body" v-model="broadcast.body" class="admin-input" rows="4" />
</div>
<div v-if="customers.length" class="admin-form__field">
<label>گیرندگان (بدون انتخاب = همه)</label>
<div class="settings-page__customers">
<label v-for="customer in customers" :key="customer.id" class="settings-page__check">
<input
type="checkbox"
:checked="selectedCustomerIds.includes(customer.id)"
@change="toggleCustomer(customer.id, ($event.target as HTMLInputElement).checked)"
/>
<span>{{ customer.name }} <span class="settings-page__phone" dir="ltr">{{ customer.phone }}</span></span>
</label>
</div>
<label class="settings-page__check settings-page__check--block">
<input v-model="sendToAllActive" type="checkbox" />
<span>ارسال به همه مشتریان فعال (نقش مشتری در اپ)</span>
</label>
<div class="admin-form__field">
<label for="broadcast-recipients">گیرندگان</label>
<AdminMultiSelect
id="broadcast-recipients"
v-model="selectedCustomerIds"
:options="activeCustomerOptions"
:disabled="sendToAllActive || !activeCustomerOptions.length"
block
placeholder="انتخاب مشتریان…"
aria-label="گیرندگان اعلان"
/>
<p class="settings-page__hint">
با غیرفعال بودن «ارسال به همه»، یک یا چند مشتری فعال را انتخاب کنید. اعلان در بخش
اعلان‌های اپ مشتری نمایش داده می‌شود.
</p>
</div>
<button
type="button"
class="admin-btn admin-btn--outline"
:disabled="broadcasting || !broadcast.title.trim() || !broadcast.body.trim()"
class="admin-btn admin-btn--primary settings-page__broadcast-submit"
:disabled="broadcasting"
@click="sendBroadcast"
>
{{ broadcastButtonLabel }}
{{ broadcasting ? 'در حال ارسال…' : 'ثبت و ارسال اعلان' }}
</button>
</section>
</div>
@@ -325,19 +351,12 @@ async function sendBroadcast() {
accent-color: var(--color-primary);
}
.settings-page__customers {
max-height: 220px;
overflow: auto;
display: flex;
flex-direction: column;
gap: 8px;
padding: 8px 0;
.settings-page__check--block {
margin-bottom: 12px;
}
.settings-page__phone {
color: var(--color-text-muted);
font-size: 0.8125rem;
margin-right: 6px;
.settings-page__broadcast-submit {
margin-top: 4px;
}
.settings-page__hint {