feat: complete application updates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-09-24 22:29:28 +03:30
co-authored by Copilot
parent 981f4fdaf9
commit 148f00a75d
94 changed files with 1351 additions and 458 deletions
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+1
View File
@@ -0,0 +1 @@
import{O as e,Z as t,at as n,g as r,l as i}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{a}from"./http-Djf2pGxE.js";import{B as o}from"./index-CznUUXv3.js";import{t as s}from"./BaseSwitch-TV5Yvtf7.js";function c(e=480){return new Promise(t=>{window.setTimeout(t,e)})}function l(e){let n=t(null),r=t(null),i=a();async function o(t,a){if(n.value)return a;let o=!a;if(!o&&e.confirmOff&&!await e.confirmOff())return a;n.value=t,r.value=null;let s=a;try{return await e.onPersist(o),i.success(e.successMessage?.(o)??(o?`وضعیت فعال شد.`:`وضعیت غیرفعال شد.`)),o}catch{return r.value=t,i.error(e.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=r({__name:`ActiveSwitchCell`,props:{id:{},modelValue:{type:Boolean},entityName:{},entityType:{},persist:{type:Function}},emits:[`update:modelValue`],setup(t,{emit:r}){let a=t,u=r,{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(r,a)=>(e(),i(s,{"model-value":t.modelValue,loading:n(f)(t.id),error:n(p)(t.id),"onUpdate:modelValue":m},null,8,[`model-value`,`loading`,`error`]))}});export{u as t};
-1
View File
@@ -1 +0,0 @@
import{O as e,Z as t,at as n,g as r,l as i}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{L as a,z as o}from"./index-CXa4EMLW.js";import{t as s}from"./BaseSwitch-TV5Yvtf7.js";function c(e=480){return new Promise(t=>{window.setTimeout(t,e)})}function l(e){let n=t(null),r=t(null),i=o();async function a(t,a){if(n.value)return a;let o=!a;if(!o&&e.confirmOff&&!await e.confirmOff())return a;n.value=t,r.value=null;let s=a;try{return await e.onPersist(o),i.success(e.successMessage?.(o)??(o?`وضعیت فعال شد.`:`وضعیت غیرفعال شد.`)),o}catch{return r.value=t,i.error(e.errorMessage??`به‌روزرسانی وضعیت انجام نشد.`),s}finally{n.value=null}}function s(e){return n.value===e}function c(e){return r.value===e}return{toggle:a,isLoading:s,hasError:c}}var u=r({__name:`ActiveSwitchCell`,props:{id:{},modelValue:{type:Boolean},entityName:{},entityType:{},persist:{type:Function}},emits:[`update:modelValue`],setup(t,{emit:r}){let o=t,u=r,{toggle:d,isLoading:f,hasError:p}=l({onPersist:async e=>{o.persist?await o.persist(o.id,e):await c()},successMessage:e=>e?`${o.entityType??`مورد`} «${o.entityName}» فعال شد.`:`${o.entityType??`مورد`} «${o.entityName}» غیرفعال شد.`});async function m(e){if(e===o.modelValue)return;let t=o.entityType??`مورد`;if(!await a({title:e?`فعال‌سازی ${t}`:`غیرفعال‌سازی ${t}`,message:e?`آیا از فعال کردن «${o.entityName}» مطمئن هستید؟`:`آیا از غیرفعال کردن «${o.entityName}» مطمئن هستید؟`,tone:e?`primary`:`danger`,confirmLabel:e?`فعال کردن`:`غیرفعال کردن`,cancelLabel:`انصراف`}))return;let n=await d(o.id,o.modelValue);u(`update:modelValue`,n)}return(r,a)=>(e(),i(s,{"model-value":t.modelValue,loading:n(f)(t.id),error:n(p)(t.id),"onUpdate:modelValue":m},null,8,[`model-value`,`loading`,`error`]))}});export{u as t};
+1
View File
@@ -0,0 +1 @@
import{O as e,Tt as t,d as n,g as r,s as i,t as a}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{c as o,r as s}from"./index-CznUUXv3.js";var c=[`id`,`value`,`placeholder`,`disabled`],l=a(r({__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(r,{emit:a}){let l=r,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)=>(e(),n(`input`,{id:r.id,type:`text`,inputmode:`numeric`,dir:`ltr`,class:t([`admin-currency-input`,r.inputClass]),value:d.value,placeholder:r.placeholder,disabled:r.disabled,onInput:f},null,42,c))}}),[[`__scopeId`,`data-v-56435fc8`]]);export{l as t};
-1
View File
@@ -1 +0,0 @@
import{O as e,Tt as t,d as n,g as r,s as i,t as a}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{o,t as s}from"./index-CXa4EMLW.js";var c=[`id`,`value`,`placeholder`,`disabled`],l=a(r({__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(r,{emit:a}){let l=r,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)=>(e(),n(`input`,{id:r.id,type:`text`,inputmode:`numeric`,dir:`ltr`,class:t([`admin-currency-input`,r.inputClass]),value:d.value,placeholder:r.placeholder,disabled:r.disabled,onInput:f},null,42,c))}}),[[`__scopeId`,`data-v-56435fc8`]]);export{l as t};
-1
View File
@@ -1 +0,0 @@
import{C as e,Dt as t,E as n,M as r,O as i,Ot as a,R as o,Tt as s,V as c,Z as l,a as u,c as d,d as f,g as p,h as m,j as h,l as g,r as _,s as v,t as y,u as b}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{G as x,K as S,O as C,P as w,W as T}from"./index-CXa4EMLW.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=y(p({__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(p,{emit:y}){let L=p,R=y,z=l(!1),B=l(``),V=l(null),H=l(null),U=l(null),W=l({});C(V,()=>{z.value=!1,B.value=``},{ignore:[H]});let G=v(()=>L.options.find(e=>e.value===L.modelValue)),K=v(()=>G.value?.label??L.placeholder),q=v(()=>{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 e(),J(),L.searchable&&(B.value=``,await e(),U.value?.focus()))}function Z(e){R(`update:modelValue`,e),z.value=!1,B.value=``}return o(z,t=>{Y(t),t&&e(()=>J())}),n(()=>{Y(!1)}),(e,n)=>(i(),f(`div`,{id:p.id,ref_key:`rootRef`,ref:V,class:s([`admin-select`,{"admin-select--open":z.value,"admin-select--block":p.block}])},[d(`button`,{type:`button`,class:`admin-select__trigger`,"aria-label":p.ariaLabel,"aria-expanded":z.value,"aria-haspopup":`listbox`,onClick:X},[d(`span`,O,[r(e.$slots,`option-leading`,{option:G.value},void 0,!0),d(`span`,k,a(K.value),1)]),d(`span`,A,[m(w,{name:`chevron-down`,size:16})])],8,D),(i(),g(u,{to:`body`},[z.value?(i(),f(`ul`,{key:0,ref_key:`menuRef`,ref:H,class:`admin-select__menu`,role:`listbox`,"aria-label":p.ariaLabel,style:t(W.value)},[p.searchable?(i(),f(`li`,M,[c(d(`input`,{ref_key:`searchInputRef`,ref:U,"onUpdate:modelValue":n[0]||=e=>B.value=e,type:`search`,class:`admin-select__search-input`,placeholder:p.searchPlaceholder,onClick:n[1]||=S(()=>{},[`stop`]),onKeydown:n[2]||=x(S(e=>z.value=!1,[`stop`]),[`esc`])},null,40,N),[[T,B.value]])])):b(``,!0),q.value.length?b(``,!0):(i(),f(`li`,P,` موردی یافت نشد `)),(i(!0),f(_,null,h(q.value,t=>(i(),f(`li`,{key:t.value,role:`presentation`},[d(`button`,{type:`button`,class:s([`admin-select__option`,{"admin-select__option--active":t.value===p.modelValue}]),role:`option`,"aria-selected":t.value===p.modelValue,onClick:e=>Z(t.value)},[d(`span`,I,[r(e.$slots,`option-leading`,{option:t},void 0,!0),d(`span`,null,a(t.label),1)]),t.value===p.modelValue?(i(),g(w,{key:0,name:`check`,size:16,class:`admin-select__check`})):b(``,!0)],10,F)]))),128))],12,j)):b(``,!0)]))],10,E))}}),[[`__scopeId`,`data-v-4623e322`]]);export{L as t};
+1
View File
@@ -0,0 +1 @@
import{C as e,Dt as t,E as n,M as r,O as i,Ot as a,R as o,Tt as s,V as c,Z as l,a as u,c as d,d as f,g as p,h as m,j as h,l as g,r as _,s as v,t as y,u as b}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{f as x,m as S,p as C}from"./http-Djf2pGxE.js";import{L as w,M as T}from"./index-CznUUXv3.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=y(p({__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(p,{emit:y}){let L=p,R=y,z=l(!1),B=l(``),V=l(null),H=l(null),U=l(null),W=l({});T(V,()=>{z.value=!1,B.value=``},{ignore:[H]});let G=v(()=>L.options.find(e=>e.value===L.modelValue)),K=v(()=>G.value?.label??L.placeholder),q=v(()=>{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 e(),J(),L.searchable&&(B.value=``,await e(),U.value?.focus()))}function Z(e){R(`update:modelValue`,e),z.value=!1,B.value=``}return o(z,t=>{Y(t),t&&e(()=>J())}),n(()=>{Y(!1)}),(e,n)=>(i(),f(`div`,{id:p.id,ref_key:`rootRef`,ref:V,class:s([`admin-select`,{"admin-select--open":z.value,"admin-select--block":p.block}])},[d(`button`,{type:`button`,class:`admin-select__trigger`,"aria-label":p.ariaLabel,"aria-expanded":z.value,"aria-haspopup":`listbox`,onClick:X},[d(`span`,O,[r(e.$slots,`option-leading`,{option:G.value},void 0,!0),d(`span`,k,a(K.value),1)]),d(`span`,A,[m(w,{name:`chevron-down`,size:16})])],8,D),(i(),g(u,{to:`body`},[z.value?(i(),f(`ul`,{key:0,ref_key:`menuRef`,ref:H,class:`admin-select__menu`,role:`listbox`,"aria-label":p.ariaLabel,style:t(W.value)},[p.searchable?(i(),f(`li`,M,[c(d(`input`,{ref_key:`searchInputRef`,ref:U,"onUpdate:modelValue":n[0]||=e=>B.value=e,type:`search`,class:`admin-select__search-input`,placeholder:p.searchPlaceholder,onClick:n[1]||=S(()=>{},[`stop`]),onKeydown:n[2]||=C(S(e=>z.value=!1,[`stop`]),[`esc`])},null,40,N),[[x,B.value]])])):b(``,!0),q.value.length?b(``,!0):(i(),f(`li`,P,` موردی یافت نشد `)),(i(!0),f(_,null,h(q.value,t=>(i(),f(`li`,{key:t.value,role:`presentation`},[d(`button`,{type:`button`,class:s([`admin-select__option`,{"admin-select__option--active":t.value===p.modelValue}]),role:`option`,"aria-selected":t.value===p.modelValue,onClick:e=>Z(t.value)},[d(`span`,I,[r(e.$slots,`option-leading`,{option:t},void 0,!0),d(`span`,null,a(t.label),1)]),t.value===p.modelValue?(i(),g(w,{key:0,name:`check`,size:16,class:`admin-select__check`})):b(``,!0)],10,F)]))),128))],12,j)):b(``,!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
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{M as e,O as t,Ot as n,Tt as r,c as i,d as a,g as o,h as s,t as c,u as l}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{P as u}from"./index-CXa4EMLW.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(o({__name:`BaseEmptyState`,props:{icon:{default:`file`},title:{},description:{},compact:{type:Boolean}},emits:[`action`,`secondary`],setup(o){return(c,h)=>(t(),a(`div`,{class:r([`base-empty`,{"base-empty--compact":o.compact}])},[i(`div`,d,[s(u,{name:o.icon,size:o.compact?28:36},null,8,[`name`,`size`])]),i(`h3`,f,n(o.title),1),o.description?(t(),a(`p`,p,n(o.description),1)):l(``,!0),c.$slots.default||c.$slots.secondary?(t(),a(`div`,m,[e(c.$slots,`default`,{},void 0,!0),e(c.$slots,`secondary`,{},void 0,!0)])):l(``,!0)],2))}}),[[`__scopeId`,`data-v-47ea81e4`]]);export{h as t};
import{M as e,O as t,Ot as n,Tt as r,c as i,d as a,g as o,h as s,t as c,u as l}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{L as u}from"./index-CznUUXv3.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(o({__name:`BaseEmptyState`,props:{icon:{default:`file`},title:{},description:{},compact:{type:Boolean}},emits:[`action`,`secondary`],setup(o){return(c,h)=>(t(),a(`div`,{class:r([`base-empty`,{"base-empty--compact":o.compact}])},[i(`div`,d,[s(u,{name:o.icon,size:o.compact?28:36},null,8,[`name`,`size`])]),i(`h3`,f,n(o.title),1),o.description?(t(),a(`p`,p,n(o.description),1)):l(``,!0),c.$slots.default||c.$slots.secondary?(t(),a(`div`,m,[e(c.$slots,`default`,{},void 0,!0),e(c.$slots,`secondary`,{},void 0,!0)])):l(``,!0)],2))}}),[[`__scopeId`,`data-v-47ea81e4`]]);export{h as t};
+1
View File
@@ -0,0 +1 @@
import{B as e,O as t,Ot as n,T as r,V as i,Y as a,Z as o,c as s,d as c,g as l,h as u,j as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{a as _,f as v,t as y}from"./http-Djf2pGxE.js";import{V as b,p as x}from"./index-CznUUXv3.js";import{t as S}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as C}from"./BaseSwitch-TV5Yvtf7.js";import{t as w}from"./BaseEmptyState-D6sk-aI_.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=o([]),P=o(!0),F=o(!1),I=o(null),L=_(),R=a({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 r(()=>{B()}),(r,a)=>(t(),c(`div`,T,[s(`header`,{class:`admin-page__head`},[a[6]||=s(`div`,null,[s(`h1`,null,`برندها`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:V},`افزودن برند`)]),P.value?(t(),f(S,{key:0,variant:`table`})):g.value.length?(t(),c(`table`,E,[a[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,[(t(!0),c(m,null,d(g.value,e=>(t(),c(`tr`,{key:e.id},[s(`td`,null,n(e.name),1),s(`td`,D,n(e.slug||`—`),1),s(`td`,null,n(e.customersCount??0),1),s(`td`,null,n(e.isActive?`بله`:`خیر`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>H(e)},`ویرایش`,8,O)])]))),128))])])):(t(),f(w,{key:1,title:`برندی ثبت نشده`})),u(b,{open:F.value,title:z.value,onClose:a[5]||=e=>F.value=!1},{footer:e(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!R.name.trim(),onClick:U},`ذخیره`,8,N)]),default:e(()=>[s(`label`,k,[a[8]||=p(`نام`,-1),i(s(`input`,{"onUpdate:modelValue":a[0]||=e=>R.name=e,class:`admin-input`},null,512),[[v,R.name]])]),s(`label`,A,[a[9]||=p(`شناسه لاتین`,-1),i(s(`input`,{"onUpdate:modelValue":a[1]||=e=>R.slug=e,class:`admin-input`,dir:`ltr`,placeholder:`مثلاً nademi-silk`},null,512),[[v,R.slug]])]),s(`label`,j,[a[10]||=p(`توضیح`,-1),i(s(`textarea`,{"onUpdate:modelValue":a[2]||=e=>R.description=e,class:`admin-input`,rows:`3`},null,512),[[v,R.description]])]),s(`label`,M,[a[11]||=p(`ترتیب`,-1),i(s(`input`,{"onUpdate:modelValue":a[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":a[4]||=e=>R.isActive=e,label:`فعال`},null,8,[`modelValue`])]),_:1},8,[`open`,`title`])]))}}),[[`__scopeId`,`data-v-88043806`]]);export{P as default};
-1
View File
@@ -1 +0,0 @@
import{B as e,O as t,Ot as n,T as r,V as i,Y as a,Z as o,c as s,d as c,g as l,h as u,j as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{N as _,R as v,W as y,u as b,z as x}from"./index-CXa4EMLW.js";import{t as S}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as C}from"./BaseSwitch-TV5Yvtf7.js";import{t as w}from"./BaseEmptyState-BmO9aDcK.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=o([]),P=o(!0),F=o(!1),I=o(null),L=x(),R=a({name:``,slug:``,description:``,displayOrder:0,isActive:!0}),z=h(()=>I.value?`ویرایش برند`:`افزودن برند`);async function B(){P.value=!0;try{g.value=await b.list()}catch(e){L.error(e instanceof _?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 b.update(I.value,e):await b.create(e),F.value=!1,L.success(`ذخیره شد`),await B()}catch(e){L.error(e instanceof _?e.message:`ذخیره ناموفق بود`)}}return r(()=>{B()}),(r,a)=>(t(),c(`div`,T,[s(`header`,{class:`admin-page__head`},[a[6]||=s(`div`,null,[s(`h1`,null,`برندها`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:V},`افزودن برند`)]),P.value?(t(),f(S,{key:0,variant:`table`})):g.value.length?(t(),c(`table`,E,[a[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,[(t(!0),c(m,null,d(g.value,e=>(t(),c(`tr`,{key:e.id},[s(`td`,null,n(e.name),1),s(`td`,D,n(e.slug||`—`),1),s(`td`,null,n(e.customersCount??0),1),s(`td`,null,n(e.isActive?`بله`:`خیر`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>H(e)},`ویرایش`,8,O)])]))),128))])])):(t(),f(w,{key:1,title:`برندی ثبت نشده`})),u(v,{open:F.value,title:z.value,onClose:a[5]||=e=>F.value=!1},{footer:e(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!R.name.trim(),onClick:U},`ذخیره`,8,N)]),default:e(()=>[s(`label`,k,[a[8]||=p(`نام`,-1),i(s(`input`,{"onUpdate:modelValue":a[0]||=e=>R.name=e,class:`admin-input`},null,512),[[y,R.name]])]),s(`label`,A,[a[9]||=p(`شناسه لاتین`,-1),i(s(`input`,{"onUpdate:modelValue":a[1]||=e=>R.slug=e,class:`admin-input`,dir:`ltr`,placeholder:`مثلاً nademi-silk`},null,512),[[y,R.slug]])]),s(`label`,j,[a[10]||=p(`توضیح`,-1),i(s(`textarea`,{"onUpdate:modelValue":a[2]||=e=>R.description=e,class:`admin-input`,rows:`3`},null,512),[[y,R.description]])]),s(`label`,M,[a[11]||=p(`ترتیب`,-1),i(s(`input`,{"onUpdate:modelValue":a[3]||=e=>R.displayOrder=e,type:`number`,class:`admin-input`},null,512),[[y,R.displayOrder,void 0,{number:!0}]])]),u(C,{modelValue:R.isActive,"onUpdate:modelValue":a[4]||=e=>R.isActive=e,label:`فعال`},null,8,[`modelValue`])]),_:1},8,[`open`,`title`])]))}}),[[`__scopeId`,`data-v-88043806`]]);export{P as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
import{O as e,Ot as t,T as n,Z as r,at as i,c as a,d as o,g as s,h as c,j as l,l as u,m as d,r as f,t as p,u as m}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{a as h,t as g}from"./http-Djf2pGxE.js";import{h as _,o as v}from"./index-CznUUXv3.js";import{t as y}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as b}from"./BaseEmptyState-D6sk-aI_.js";import{t as x}from"./AdminSelect-DK-_otkw.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(s({__name:`CatalogRequestsView`,setup(s){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=r([]),I=r(``),L=r(!0),R=r(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()}),(n,r)=>(e(),o(`div`,S,[a(`header`,C,[r[1]||=a(`div`,null,[a(`h1`,null,`درخواست کالیته فیزیکی`),a(`p`,null,`بررسی، گردش وضعیت و محل نگهداری اقلام`)],-1),c(x,{modelValue:I.value,"onUpdate:modelValue":[r[0]||=e=>I.value=e,B],options:p},null,8,[`modelValue`])]),L.value?(e(),u(y,{key:0,variant:`cards`})):F.value.length?(e(!0),o(f,{key:2},l(F.value,n=>(e(),o(`div`,{key:n.id,class:`request-card`},[a(`div`,w,[a(`div`,null,[a(`strong`,null,t(n.customerName),1),a(`span`,T,t(n.customerPhone),1),a(`span`,E,t(i(N)[n.status]??n.status),1)]),c(x,{"model-value":n.status,options:p.filter(e=>e.value),"onUpdate:modelValue":e=>V(n.id,String(e))},null,8,[`model-value`,`options`,`onUpdate:modelValue`])]),n.note?(e(),o(`p`,D,t(n.note),1)):m(``,!0),a(`table`,O,[r[2]||=a(`thead`,null,[a(`tr`,null,[a(`th`,null,`محصول`),a(`th`,null,`کد`),a(`th`,null,`تعداد`),a(`th`,null,`محل`)])],-1),a(`tbody`,null,[(e(!0),o(f,null,l(n.items,n=>(e(),o(`tr`,{key:n.id},[a(`td`,null,t(n.productName),1),a(`td`,k,t(n.productCode),1),a(`td`,null,t(n.quantity),1),a(`td`,null,[c(x,{"model-value":n.holderType,options:M,"onUpdate:modelValue":e=>H(n.id,String(e))},null,8,[`model-value`,`onUpdate:modelValue`]),n.movements?.length?(e(),o(`ul`,A,[(e(!0),o(f,null,l(n.movements,n=>(e(),o(`li`,{key:n.id},[d(t(U(n.fromHolder))+` → `+t(U(n.toHolder))+` `,1),a(`span`,j,t(i(v)(n.movedAt)),1)]))),128))])):m(``,!0)])]))),128))])])]))),128)):(e(),u(b,{key:1,title:`درخواستی نیست`}))]))}}),[[`__scopeId`,`data-v-747d8f88`]]);export{M as default};
-1
View File
@@ -1 +0,0 @@
import{O as e,Ot as t,T as n,Z as r,at as i,c as a,d as o,g as s,h as c,j as l,l as u,m as d,r as f,t as p,u as m}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{N as h,f as g,i as _,z as v}from"./index-CXa4EMLW.js";import{t as y}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as b}from"./BaseEmptyState-BmO9aDcK.js";import{t as x}from"./AdminSelect-9UfEJQzN.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(s({__name:`CatalogRequestsView`,setup(s){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=r([]),I=r(``),L=r(!0),R=r(null),z=v();async function B(){L.value=!0;try{F.value=await g.list(I.value||void 0)}catch(e){z.error(e instanceof h?e.message:`بارگذاری انجام نشد`)}finally{L.value=!1}}async function V(e,t){try{await g.updateStatus(e,t),z.success(`وضعیت به‌روز شد`),await B()}catch(e){z.error(e instanceof h?e.message:`به‌روزرسانی ناموفق بود`)}}async function H(e,t){if(!R.value){R.value=e;try{await g.updateHolder(e,{holderType:t}),z.success(`محل نگهداری به‌روز شد`),await B()}catch(e){z.error(e instanceof h?e.message:`به‌روزرسانی محل ناموفق بود`)}finally{R.value=null}}}function U(e){return P[e]??e}return n(()=>{B()}),(n,r)=>(e(),o(`div`,S,[a(`header`,C,[r[1]||=a(`div`,null,[a(`h1`,null,`درخواست کالیته فیزیکی`),a(`p`,null,`بررسی، گردش وضعیت و محل نگهداری اقلام`)],-1),c(x,{modelValue:I.value,"onUpdate:modelValue":[r[0]||=e=>I.value=e,B],options:p},null,8,[`modelValue`])]),L.value?(e(),u(y,{key:0,variant:`cards`})):F.value.length?(e(!0),o(f,{key:2},l(F.value,n=>(e(),o(`div`,{key:n.id,class:`request-card`},[a(`div`,w,[a(`div`,null,[a(`strong`,null,t(n.customerName),1),a(`span`,T,t(n.customerPhone),1),a(`span`,E,t(i(N)[n.status]??n.status),1)]),c(x,{"model-value":n.status,options:p.filter(e=>e.value),"onUpdate:modelValue":e=>V(n.id,String(e))},null,8,[`model-value`,`options`,`onUpdate:modelValue`])]),n.note?(e(),o(`p`,D,t(n.note),1)):m(``,!0),a(`table`,O,[r[2]||=a(`thead`,null,[a(`tr`,null,[a(`th`,null,`محصول`),a(`th`,null,`کد`),a(`th`,null,`تعداد`),a(`th`,null,`محل`)])],-1),a(`tbody`,null,[(e(!0),o(f,null,l(n.items,n=>(e(),o(`tr`,{key:n.id},[a(`td`,null,t(n.productName),1),a(`td`,k,t(n.productCode),1),a(`td`,null,t(n.quantity),1),a(`td`,null,[c(x,{"model-value":n.holderType,options:M,"onUpdate:modelValue":e=>H(n.id,String(e))},null,8,[`model-value`,`onUpdate:modelValue`]),n.movements?.length?(e(),o(`ul`,A,[(e(!0),o(f,null,l(n.movements,n=>(e(),o(`li`,{key:n.id},[d(t(U(n.fromHolder))+` → `+t(U(n.toHolder))+` `,1),a(`span`,j,t(i(_)(n.movedAt)),1)]))),128))])):m(``,!0)])]))),128))])])]))),128)):(e(),u(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
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 @@
.sales-chart__header[data-v-85c70c78]{margin-bottom:12px}.sales-chart__title[data-v-85c70c78]{margin:0;font-size:.9375rem;font-weight:700}.sales-chart__subtitle[data-v-85c70c78]{color:var(--color-text-muted);margin:4px 0 0;font-size:.8125rem}.sales-chart__body[data-v-85c70c78]{position:relative}.sales-chart__svg[data-v-85c70c78]{width:100%;height:100%;display:block}.sales-chart__grid[data-v-85c70c78]{stroke:var(--color-border);stroke-width:1px;stroke-dasharray:4 4}.sales-chart__line[data-v-85c70c78]{fill:none;stroke-width:2.25px;stroke-linecap:round;stroke-linejoin:round}.sales-chart__cursor[data-v-85c70c78]{stroke:var(--color-border);stroke-width:1px;stroke-dasharray:3 3}.sales-chart__dot[data-v-85c70c78]{stroke:#fff;stroke-width:2px}.sales-chart__tooltip[data-v-85c70c78]{border-radius:var(--radius-sm);border:1px solid var(--color-border);background:var(--color-surface);min-width:120px;box-shadow:var(--shadow-sm);pointer-events:none;text-align:center;padding:8px 10px;position:absolute;top:8px;transform:translate(-50%)}.sales-chart__tooltip-label[data-v-85c70c78]{color:var(--color-text-muted);margin-bottom:2px;font-size:.6875rem;display:block}.sales-chart__tooltip strong[data-v-85c70c78]{font-size:.8125rem}.sales-chart__axis[data-v-85c70c78]{color:var(--color-text-muted);justify-content:space-between;margin-top:8px;font-size:.6875rem;display:flex}.dashboard__toolbar[data-v-bf2ebdae]{justify-content:flex-end;margin-bottom:16px;display:flex}.dashboard__stats[data-v-bf2ebdae]{grid-template-columns:repeat(5,1fr);gap:16px;margin-bottom:20px;display:grid}.dashboard__sales-header[data-v-bf2ebdae]{justify-content:space-between;align-items:flex-start;display:flex}.dashboard__sales-value[data-v-bf2ebdae]{font-size:1.25rem!important}.dashboard__sales-growth[data-v-bf2ebdae]{color:var(--color-success);font-size:.75rem;font-weight:600}.dashboard__sales-icon[data-v-bf2ebdae]{color:var(--color-text-muted)}.dashboard__sparkline[data-v-bf2ebdae]{width:100%;height:32px;margin-top:8px}.dashboard__grid[data-v-bf2ebdae]{grid-template-columns:1fr 1.4fr .9fr;gap:16px;display:grid}.dashboard__chart-card[data-v-bf2ebdae],.dashboard__orders-card[data-v-bf2ebdae],.dashboard__checks-card[data-v-bf2ebdae]{padding:20px}.dashboard__chart-card[data-v-bf2ebdae] .sales-chart__header{margin-bottom:0}.dashboard__card-header[data-v-bf2ebdae]{margin-bottom:16px}.dashboard__card-title[data-v-bf2ebdae]{margin:0;font-size:.9375rem;font-weight:700}.dashboard__card-value[data-v-bf2ebdae]{margin:4px 0 0;font-size:1.125rem;font-weight:700}.dashboard__chart[data-v-bf2ebdae]{width:100%;height:140px}.dashboard__chart-axis[data-v-bf2ebdae]{color:var(--color-text-muted);justify-content:space-between;margin-top:8px;font-size:.6875rem;display:flex}.dashboard__card-footer[data-v-bf2ebdae]{border-top:1px solid var(--color-border-light);justify-content:flex-start;margin-top:12px;padding-top:12px;display:flex}.dashboard__card-footer .admin-btn[data-v-bf2ebdae]{text-decoration:none}.dashboard__deadline-list[data-v-bf2ebdae]{flex-direction:column;gap:12px;margin:0;padding:0;list-style:none;display:flex}.dashboard__deadline-item[data-v-bf2ebdae]{grid-template-columns:auto 1fr auto auto;align-items:center;gap:10px;font-size:.8125rem;display:grid}.dashboard__deadline-count[data-v-bf2ebdae]{color:var(--color-text-muted)}.dashboard__deadline-amount[data-v-bf2ebdae]{font-size:.75rem;font-weight:600}
.sales-chart__header[data-v-36d15f13]{margin-bottom:12px}.sales-chart__title[data-v-36d15f13]{margin:0;font-size:.9375rem;font-weight:700}.sales-chart__subtitle[data-v-36d15f13]{color:var(--color-text-muted);margin:4px 0 0;font-size:.8125rem}.sales-chart__body[data-v-36d15f13]{position:relative}.sales-chart__svg[data-v-36d15f13]{width:100%;height:100%;display:block}.sales-chart__grid[data-v-36d15f13]{stroke:var(--color-border);stroke-width:1px;stroke-dasharray:4 4}.sales-chart__line[data-v-36d15f13]{fill:none;stroke-width:2.25px;stroke-linecap:round;stroke-linejoin:round}.sales-chart__cursor[data-v-36d15f13]{stroke:var(--color-border);stroke-width:1px;stroke-dasharray:3 3}.sales-chart__dot[data-v-36d15f13]{stroke:#fff;stroke-width:2px}.sales-chart__tooltip[data-v-36d15f13]{border-radius:var(--radius-sm);border:1px solid var(--color-border);background:var(--color-surface);min-width:120px;box-shadow:var(--shadow-sm);pointer-events:none;text-align:center;padding:8px 10px;position:absolute;top:8px;transform:translate(-50%)}.sales-chart__tooltip-label[data-v-36d15f13]{color:var(--color-text-muted);margin-bottom:2px;font-size:.6875rem;display:block}.sales-chart__tooltip strong[data-v-36d15f13]{font-size:.8125rem}.sales-chart__axis[data-v-36d15f13]{color:var(--color-text-muted);justify-content:space-between;margin-top:8px;font-size:.6875rem;display:flex}.dashboard__toolbar[data-v-bf2ebdae]{justify-content:flex-end;margin-bottom:16px;display:flex}.dashboard__stats[data-v-bf2ebdae]{grid-template-columns:repeat(5,1fr);gap:16px;margin-bottom:20px;display:grid}.dashboard__sales-header[data-v-bf2ebdae]{justify-content:space-between;align-items:flex-start;display:flex}.dashboard__sales-value[data-v-bf2ebdae]{font-size:1.25rem!important}.dashboard__sales-growth[data-v-bf2ebdae]{color:var(--color-success);font-size:.75rem;font-weight:600}.dashboard__sales-icon[data-v-bf2ebdae]{color:var(--color-text-muted)}.dashboard__sparkline[data-v-bf2ebdae]{width:100%;height:32px;margin-top:8px}.dashboard__grid[data-v-bf2ebdae]{grid-template-columns:1fr 1.4fr .9fr;gap:16px;display:grid}.dashboard__chart-card[data-v-bf2ebdae],.dashboard__orders-card[data-v-bf2ebdae],.dashboard__checks-card[data-v-bf2ebdae]{padding:20px}.dashboard__chart-card[data-v-bf2ebdae] .sales-chart__header{margin-bottom:0}.dashboard__card-header[data-v-bf2ebdae]{margin-bottom:16px}.dashboard__card-title[data-v-bf2ebdae]{margin:0;font-size:.9375rem;font-weight:700}.dashboard__card-value[data-v-bf2ebdae]{margin:4px 0 0;font-size:1.125rem;font-weight:700}.dashboard__chart[data-v-bf2ebdae]{width:100%;height:140px}.dashboard__chart-axis[data-v-bf2ebdae]{color:var(--color-text-muted);justify-content:space-between;margin-top:8px;font-size:.6875rem;display:flex}.dashboard__card-footer[data-v-bf2ebdae]{border-top:1px solid var(--color-border-light);justify-content:flex-start;margin-top:12px;padding-top:12px;display:flex}.dashboard__card-footer .admin-btn[data-v-bf2ebdae]{text-decoration:none}.dashboard__deadline-list[data-v-bf2ebdae]{flex-direction:column;gap:12px;margin:0;padding:0;list-style:none;display:flex}.dashboard__deadline-item[data-v-bf2ebdae]{grid-template-columns:auto 1fr auto auto;align-items:center;gap:10px;font-size:.8125rem;display:grid}.dashboard__deadline-count[data-v-bf2ebdae]{color:var(--color-text-muted)}.dashboard__deadline-amount[data-v-bf2ebdae]{font-size:.75rem;font-weight:600}
File diff suppressed because one or more lines are too long
@@ -1,3 +1,3 @@
import{B as e,O as t,Ot as n,T as r,V as i,Y as a,Z as o,c as s,d as c,g as l,h as u,j as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{N as _,R as v,W as y,v as b,z as x}from"./index-CXa4EMLW.js";import{t as S}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as C}from"./BaseEmptyState-BmO9aDcK.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=o([]),N=o(!0),P=o(!1),F=o(null),I=x(),L=a({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 b.list()}catch(e){I.error(e instanceof _?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 b.update(F.value,e):await b.create(e),P.value=!1,I.success(`ذخیره شد`),await B()}catch(e){I.error(e instanceof _?e.message:`ذخیره ناموفق بود`)}}return r(()=>{B()}),(r,a)=>(t(),c(`div`,w,[s(`header`,{class:`admin-page__head`},[a[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?(t(),f(S,{key:0,variant:`table`})):g.value.length?(t(),c(`table`,T,[a[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,[(t(!0),c(m,null,d(g.value,e=>(t(),c(`tr`,{key:e.id},[s(`td`,E,n(e.code),1),s(`td`,null,n(e.label),1),s(`td`,null,n(e.benefits.map(e=>e.title).join(`، `)||`—`),1),s(`td`,null,n(e.customersCount),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>H(e)},`ویرایش`,8,D)])]))),128))])])):(t(),f(C,{key:1,title:`سطحی تعریف نشده`})),u(v,{open:P.value,title:R.value,onClose:a[4]||=e=>P.value=!1},{footer:e(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!L.code.trim()||!L.label.trim(),onClick:U},` ذخیره `,8,M)]),default:e(()=>[s(`label`,O,[a[7]||=p(`کد`,-1),i(s(`input`,{"onUpdate:modelValue":a[0]||=e=>L.code=e,class:`admin-input`,dir:`ltr`},null,512),[[y,L.code]])]),s(`label`,k,[a[8]||=p(`عنوان`,-1),i(s(`input`,{"onUpdate:modelValue":a[1]||=e=>L.label=e,class:`admin-input`},null,512),[[y,L.label]])]),s(`label`,A,[a[9]||=p(`ترتیب`,-1),i(s(`input`,{"onUpdate:modelValue":a[2]||=e=>L.sortOrder=e,type:`number`,class:`admin-input`},null,512),[[y,L.sortOrder,void 0,{number:!0}]])]),s(`label`,j,[a[10]||=p(` مزایا (هر خط یک مورد) `,-1),i(s(`textarea`,{"onUpdate:modelValue":a[3]||=e=>L.benefitsText=e,class:`admin-input`,rows:`4`},null,512),[[y,L.benefitsText]])])]),_:1},8,[`open`,`title`])]))}}),[[`__scopeId`,`data-v-44c7f808`]]);export{N as default};
import{B as e,O as t,Ot as n,T as r,V as i,Y as a,Z as o,c as s,d as c,g as l,h as u,j as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{a as _,f as v,t as y}from"./http-Djf2pGxE.js";import{V as b,x}from"./index-CznUUXv3.js";import{t as S}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as C}from"./BaseEmptyState-D6sk-aI_.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=o([]),N=o(!0),P=o(!1),F=o(null),I=_(),L=a({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 r(()=>{B()}),(r,a)=>(t(),c(`div`,w,[s(`header`,{class:`admin-page__head`},[a[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?(t(),f(S,{key:0,variant:`table`})):g.value.length?(t(),c(`table`,T,[a[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,[(t(!0),c(m,null,d(g.value,e=>(t(),c(`tr`,{key:e.id},[s(`td`,E,n(e.code),1),s(`td`,null,n(e.label),1),s(`td`,null,n(e.benefits.map(e=>e.title).join(`، `)||`—`),1),s(`td`,null,n(e.customersCount),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>H(e)},`ویرایش`,8,D)])]))),128))])])):(t(),f(C,{key:1,title:`سطحی تعریف نشده`})),u(b,{open:P.value,title:R.value,onClose:a[4]||=e=>P.value=!1},{footer:e(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!L.code.trim()||!L.label.trim(),onClick:U},` ذخیره `,8,M)]),default:e(()=>[s(`label`,O,[a[7]||=p(`کد`,-1),i(s(`input`,{"onUpdate:modelValue":a[0]||=e=>L.code=e,class:`admin-input`,dir:`ltr`},null,512),[[v,L.code]])]),s(`label`,k,[a[8]||=p(`عنوان`,-1),i(s(`input`,{"onUpdate:modelValue":a[1]||=e=>L.label=e,class:`admin-input`},null,512),[[v,L.label]])]),s(`label`,A,[a[9]||=p(`ترتیب`,-1),i(s(`input`,{"onUpdate:modelValue":a[2]||=e=>L.sortOrder=e,type:`number`,class:`admin-input`},null,512),[[v,L.sortOrder,void 0,{number:!0}]])]),s(`label`,j,[a[10]||=p(` مزایا (هر خط یک مورد) `,-1),i(s(`textarea`,{"onUpdate:modelValue":a[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
View File
@@ -0,0 +1 @@
.login-page[data-v-df8d196a]{background:var(--color-bg);grid-template-columns:1.05fr 1fr;min-height:100dvh;display:grid}.login-page__brand[data-v-df8d196a]{background:linear-gradient(160deg, var(--color-primary-dark) 0%, var(--color-primary) 55%, var(--color-sidebar-active) 100%);color:#fff;justify-content:center;align-items:center;padding:48px 40px;display:flex}.login-page__brand-inner[data-v-df8d196a]{max-width:420px}.login-page__brand-logo[data-v-df8d196a]{object-fit:contain;width:min(220px,70%);height:150px;margin-bottom:28px;display:block}.login-page__brand-badge[data-v-df8d196a]{background:#ffffff1f;border-radius:999px;margin:0 0 16px;padding:6px 12px;font-size:.8125rem;font-weight:700;display:inline-flex}.login-page__brand-title[data-v-df8d196a]{margin:0 0 12px;font-size:clamp(1.5rem,2.4vw,2rem);line-height:1.35}.login-page__brand-text[data-v-df8d196a]{color:#ffffffc7;margin:0;font-size:.9375rem;line-height:1.8}.login-page__form-wrap[data-v-df8d196a]{justify-content:center;align-items:center;padding:32px 24px;display:flex}.login-page__card[data-v-df8d196a]{width:min(100%,420px);padding:28px 24px}.login-page__header[data-v-df8d196a]{margin-bottom:20px}.login-page__title[data-v-df8d196a]{margin:0 0 6px;font-size:1.25rem;font-weight:700}.login-page__subtitle[data-v-df8d196a]{color:var(--color-text-muted);margin:0;font-size:.875rem;line-height:1.6}.login-page__mock-hint[data-v-df8d196a]{background:color-mix(in srgb, var(--color-primary) 12%, transparent);color:var(--color-primary);border-radius:8px;margin:10px 0 0;padding:8px 10px;font-size:.8125rem;line-height:1.5}.login-page__input-wrap[data-v-df8d196a]{color:var(--color-text-muted);position:relative}.login-page__field-icon[data-v-df8d196a]{pointer-events:none;z-index:1;display:inline-flex;position:absolute;top:50%;right:12px;transform:translateY(-50%)}.login-page__input[data-v-df8d196a]{padding-right:42px}.login-page__input--with-toggle[data-v-df8d196a]{padding-left:44px}.login-page__toggle-password[data-v-df8d196a]{z-index:1;width:32px;height:32px;color:var(--color-text-muted);cursor:pointer;background:0 0;border:none;border-radius:8px;justify-content:center;align-items:center;padding:0;transition:color .15s,background-color .15s;display:inline-flex;position:absolute;top:50%;left:6px;transform:translateY(-50%)}.login-page__toggle-password[data-v-df8d196a]:hover{color:var(--color-primary);background:var(--color-bg-muted,#0000000a)}.login-page__remember[data-v-df8d196a]{color:var(--color-text-muted);align-items:center;gap:8px;font-size:.8125rem;display:flex}.login-page__remember input[data-v-df8d196a]{width:16px;height:16px;accent-color:var(--color-primary)}.login-page__submit[data-v-df8d196a]{width:100%;margin-top:4px}@media (width<=900px){.login-page[data-v-df8d196a]{grid-template-columns:1fr}.login-page__form-wrap[data-v-df8d196a]{order:2}.login-page__brand[data-v-df8d196a]{order:1;min-height:auto;padding:28px 20px}.login-page__brand-text[data-v-df8d196a]{font-size:.875rem}}
+1
View File
@@ -0,0 +1 @@
import{O as e,Ot as t,V as n,Z as r,at as i,c as a,d as o,g as s,h as c,m as l,s as u,t as d,u as f}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{a as p,f as m,l as h,m as g,u as _}from"./http-Djf2pGxE.js";import{F as v,I as y,L as b,P as x,R as S,z as C}from"./index-CznUUXv3.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(s({__name:`LoginView`,setup(s){let d=C(),U=S(),W=x(),G=p(),K=r(y()?v.phone:``),q=r(y()?v.password:``),J=r(!0),Y=r(!1),X=y(),Z=u(()=>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(r,s)=>(e(),o(`div`,w,[a(`div`,T,[a(`div`,E,[a(`div`,D,[s[8]||=a(`h2`,{class:`login-page__title`},`ورود به پنل مدیریت`,-1),s[9]||=a(`p`,{class:`login-page__subtitle`},`برای ادامه، اطلاعات حساب کاربری خود را وارد کنید.`,-1),i(X)?(e(),o(`p`,O,[s[6]||=l(` حالت موک فعال است — `,-1),a(`span`,k,t(i(v).phone),1),s[7]||=l(` / `,-1),a(`span`,A,t(i(v).password),1)])):f(``,!0)]),a(`form`,{class:`admin-form login-page__form`,onSubmit:g($,[`prevent`])},[i(W).error?(e(),o(`p`,j,t(i(W).error),1)):f(``,!0),a(`div`,M,[s[10]||=a(`label`,{for:`login-phone`},`شماره موبایل`,-1),a(`div`,N,[a(`span`,P,[c(b,{name:`phone`,size:18})]),n(a(`input`,{id:`login-phone`,"onUpdate:modelValue":s[0]||=e=>K.value=e,type:`tel`,class:`admin-input login-page__input`,placeholder:`۰۹۱۲۱۱۱۱۲۲۲۲`,autocomplete:`username`,inputmode:`tel`,dir:`ltr`,onInput:s[1]||=e=>i(W).clearError()},null,544),[[m,K.value]])])]),a(`div`,F,[s[11]||=a(`label`,{for:`login-password`},`رمز عبور`,-1),a(`div`,I,[a(`span`,L,[c(b,{name:`lock`,size:18})]),n(a(`input`,{id:`login-password`,"onUpdate:modelValue":s[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:s[3]||=e=>i(W).clearError()},null,40,R),[[_,q.value]]),a(`button`,{type:`button`,class:`login-page__toggle-password`,"aria-label":Y.value?`پنهان کردن رمز`:`نمایش رمز`,onClick:s[4]||=e=>Y.value=!Y.value},[c(b,{name:Y.value?`eye-off`:`eye`,size:18},null,8,[`name`])],8,z)])]),a(`label`,B,[n(a(`input`,{"onUpdate:modelValue":s[5]||=e=>J.value=e,type:`checkbox`},null,512),[[h,J.value]]),s[12]||=a(`span`,null,`مرا به خاطر بسپار`,-1)]),a(`button`,{type:`submit`,class:`admin-btn admin-btn--primary login-page__submit`,disabled:!Z.value||i(W).loading},[i(W).loading?(e(),o(`span`,H)):f(``,!0),s[13]||=l(` ورود به پنل `,-1)],8,V)],32)])]),a(`div`,{class:`login-page__brand`},[a(`div`,{class:`login-page__brand-inner`},[a(`img`,{src:`/admin/brand-logo.png`,alt:`نادمی تکس`,class:`login-page__brand-logo`}),s[14]||=a(`p`,{class:`login-page__brand-badge`},`پارچه`,-1),s[15]||=a(`h1`,{class:`login-page__brand-title`},`پنل مدیریت سفارش‌های عمده`,-1),s[16]||=a(`p`,{class:`login-page__brand-text`},` مدیریت سفارش‌ها، مشتریان، قیمت‌گذاری، چک‌ها و گزارش‌های فروش در یک محیط یکپارچه. `,-1)])])]))}}),[[`__scopeId`,`data-v-df8d196a`]]);export{U as default};
-1
View File
@@ -1 +0,0 @@
.login-page[data-v-9aef901d]{background:var(--color-bg);grid-template-columns:1.05fr 1fr;min-height:100dvh;display:grid}.login-page__brand[data-v-9aef901d]{color:#fff;background:linear-gradient(160deg,#062428 0%,#0a353c 55%,#0e454e 100%);justify-content:center;align-items:center;padding:48px 40px;display:flex}.login-page__brand-inner[data-v-9aef901d]{max-width:420px}.login-page__brand-badge[data-v-9aef901d]{background:#ffffff1f;border-radius:999px;margin:0 0 16px;padding:6px 12px;font-size:.8125rem;font-weight:700;display:inline-flex}.login-page__brand-title[data-v-9aef901d]{margin:0 0 12px;font-size:clamp(1.5rem,2.4vw,2rem);line-height:1.35}.login-page__brand-text[data-v-9aef901d]{color:#ffffffc7;margin:0;font-size:.9375rem;line-height:1.8}.login-page__form-wrap[data-v-9aef901d]{justify-content:center;align-items:center;padding:32px 24px;display:flex}.login-page__card[data-v-9aef901d]{width:min(100%,420px);padding:28px 24px}.login-page__header[data-v-9aef901d]{margin-bottom:20px}.login-page__title[data-v-9aef901d]{margin:0 0 6px;font-size:1.25rem;font-weight:700}.login-page__subtitle[data-v-9aef901d]{color:var(--color-text-muted);margin:0;font-size:.875rem;line-height:1.6}.login-page__mock-hint[data-v-9aef901d]{background:color-mix(in srgb, var(--color-primary) 12%, transparent);color:var(--color-primary);border-radius:8px;margin:10px 0 0;padding:8px 10px;font-size:.8125rem;line-height:1.5}.login-page__input-wrap[data-v-9aef901d]{color:var(--color-text-muted);position:relative}.login-page__field-icon[data-v-9aef901d]{pointer-events:none;z-index:1;display:inline-flex;position:absolute;top:50%;right:12px;transform:translateY(-50%)}.login-page__input[data-v-9aef901d]{padding-right:42px}.login-page__input--with-toggle[data-v-9aef901d]{padding-left:44px}.login-page__toggle-password[data-v-9aef901d]{z-index:1;width:32px;height:32px;color:var(--color-text-muted);cursor:pointer;background:0 0;border:none;border-radius:8px;justify-content:center;align-items:center;padding:0;transition:color .15s,background-color .15s;display:inline-flex;position:absolute;top:50%;left:6px;transform:translateY(-50%)}.login-page__toggle-password[data-v-9aef901d]:hover{color:var(--color-primary);background:var(--color-bg-muted,#0000000a)}.login-page__remember[data-v-9aef901d]{color:var(--color-text-muted);align-items:center;gap:8px;font-size:.8125rem;display:flex}.login-page__remember input[data-v-9aef901d]{width:16px;height:16px;accent-color:var(--color-primary)}.login-page__submit[data-v-9aef901d]{width:100%;margin-top:4px}@media (width<=900px){.login-page[data-v-9aef901d]{grid-template-columns:1fr}.login-page__form-wrap[data-v-9aef901d]{order:2}.login-page__brand[data-v-9aef901d]{order:1;min-height:auto;padding:28px 20px}.login-page__brand-text[data-v-9aef901d]{font-size:.875rem}}
-1
View File
@@ -1 +0,0 @@
import{O as e,Ot as t,V as n,Z as r,at as i,c as a,d as o,g as s,h as c,m as l,p as u,s as d,t as f,u as p}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{A as m,F as h,H as g,I as _,K as v,M as y,P as b,V as x,W as S,j as C,z as w}from"./index-CXa4EMLW.js";var T={class:`login-page`},E={class:`login-page__form-wrap`},D={class:`login-page__card admin-card`},O={class:`login-page__header`},k={key:0,class:`login-page__mock-hint`},A={dir:`ltr`},j={dir:`ltr`},M={key:0,class:`admin-form__error`,role:`alert`},N={class:`admin-form__field`},P={class:`login-page__input-wrap`},F={class:`login-page__field-icon`,"aria-hidden":`true`},I={class:`admin-form__field`},L={class:`login-page__input-wrap login-page__input-wrap--password`},R={class:`login-page__field-icon`,"aria-hidden":`true`},z=[`type`],B=[`aria-label`],V={class:`login-page__remember`},H=[`disabled`],U={key:0,class:`admin-btn__spinner`,"aria-hidden":`true`},W=f(s({__name:`LoginView`,setup(s){let f=_(),W=h(),G=m(),K=w(),q=r(y()?C.phone:``),J=r(y()?C.password:``),Y=r(!0),X=r(!1),Z=y(),Q=d(()=>q.value.trim().length>0&&J.value.length>0);function $(e){return e.replace(/[۰-۹]/g,e=>String(`۰۱۲۳۴۵۶۷۸۹`.indexOf(e))).replace(/[٠-٩]/g,e=>String(`٠١٢٣٤٥٦٧٨٩`.indexOf(e)))}async function ee(){if(!Q.value){G.error=`لطفاً شماره موبایل و رمز عبور را وارد کنید.`;return}if(!await G.login($(q.value.trim()),J.value,Y.value))return;K.success(`خوش آمدید، ${G.fullName}`);let e=typeof W.query.redirect==`string`?W.query.redirect:`/`,t=`/admin/`.replace(/\/$/,``),n=e.startsWith(t)?e.slice(t.length)||`/`:e;await f.replace(n)}return(r,s)=>(e(),o(`div`,T,[a(`div`,E,[a(`div`,D,[a(`div`,O,[s[8]||=a(`h2`,{class:`login-page__title`},`ورود به پنل مدیریت`,-1),s[9]||=a(`p`,{class:`login-page__subtitle`},`برای ادامه، اطلاعات حساب کاربری خود را وارد کنید.`,-1),i(Z)?(e(),o(`p`,k,[s[6]||=l(` حالت موک فعال است — `,-1),a(`span`,A,t(i(C).phone),1),s[7]||=l(` / `,-1),a(`span`,j,t(i(C).password),1)])):p(``,!0)]),a(`form`,{class:`admin-form login-page__form`,onSubmit:v(ee,[`prevent`])},[i(G).error?(e(),o(`p`,M,t(i(G).error),1)):p(``,!0),a(`div`,N,[s[10]||=a(`label`,{for:`login-phone`},`شماره موبایل`,-1),a(`div`,P,[a(`span`,F,[c(b,{name:`phone`,size:18})]),n(a(`input`,{id:`login-phone`,"onUpdate:modelValue":s[0]||=e=>q.value=e,type:`tel`,class:`admin-input login-page__input`,placeholder:`۰۹۱۲۱۱۱۱۲۲۲۲`,autocomplete:`username`,inputmode:`tel`,dir:`ltr`,onInput:s[1]||=e=>i(G).clearError()},null,544),[[S,q.value]])])]),a(`div`,I,[s[11]||=a(`label`,{for:`login-password`},`رمز عبور`,-1),a(`div`,L,[a(`span`,R,[c(b,{name:`lock`,size:18})]),n(a(`input`,{id:`login-password`,"onUpdate:modelValue":s[2]||=e=>J.value=e,type:X.value?`text`:`password`,class:`admin-input login-page__input login-page__input--with-toggle`,placeholder:`رمز عبور`,autocomplete:`current-password`,onInput:s[3]||=e=>i(G).clearError()},null,40,z),[[g,J.value]]),a(`button`,{type:`button`,class:`login-page__toggle-password`,"aria-label":X.value?`پنهان کردن رمز`:`نمایش رمز`,onClick:s[4]||=e=>X.value=!X.value},[c(b,{name:X.value?`eye-off`:`eye`,size:18},null,8,[`name`])],8,B)])]),a(`label`,V,[n(a(`input`,{"onUpdate:modelValue":s[5]||=e=>Y.value=e,type:`checkbox`},null,512),[[x,Y.value]]),s[12]||=a(`span`,null,`مرا به خاطر بسپار`,-1)]),a(`button`,{type:`submit`,class:`admin-btn admin-btn--primary login-page__submit`,disabled:!Q.value||i(G).loading},[i(G).loading?(e(),o(`span`,U)):p(``,!0),s[13]||=l(` ورود به پنل `,-1)],8,H)],32)])]),s[14]||=u(`<div class="login-page__brand" data-v-9aef901d><div class="login-page__brand-inner" data-v-9aef901d><p class="login-page__brand-badge" data-v-9aef901d>پارچه</p><h1 class="login-page__brand-title" data-v-9aef901d>پنل مدیریت سفارش‌های عمده</h1><p class="login-page__brand-text" data-v-9aef901d> مدیریت سفارش‌ها، مشتریان، قیمت‌گذاری، چک‌ها و گزارش‌های فروش در یک محیط یکپارچه. </p></div></div>`,1)]))}}),[[`__scopeId`,`data-v-9aef901d`]]);export{W 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
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 @@
.sms-stats[data-v-e4cab5f3]{grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;margin-bottom:16px;display:grid}.sms-stat[data-v-e4cab5f3]{border-radius:16px;min-height:112px;padding:16px 18px}.sms-stat h2[data-v-e4cab5f3]{margin:0 0 10px;font-size:.95rem}.sms-stat p[data-v-e4cab5f3]{color:var(--color-text-muted,#64748b);margin:0;font-size:.8125rem}.sms-stat p span[data-v-e4cab5f3],.sms-stat strong[data-v-e4cab5f3]{font-weight:800}.sms-stat--balance[data-v-e4cab5f3]{color:#6d28d9;background:#f3e8ff}.sms-stat--today[data-v-e4cab5f3]{color:#c2410c;background:#fff7ed}.sms-stat--price[data-v-e4cab5f3]{color:#1d4ed8;background:#eff6ff}.sms-stat--price strong[data-v-e4cab5f3],.sms-stat--wallet strong[data-v-e4cab5f3]{margin-top:18px;font-size:1.35rem;display:block}.sms-stat--wallet[data-v-e4cab5f3]{color:#b45309;background:#fffbeb}.sms-manage[data-v-e4cab5f3],.sms-deposit[data-v-e4cab5f3],.sms-thresholds[data-v-e4cab5f3]{margin-bottom:16px}.sms-manage h2[data-v-e4cab5f3],.sms-deposit h2[data-v-e4cab5f3],.sms-thresholds h2[data-v-e4cab5f3]{margin:0 0 14px;font-size:1rem}.sms-manage__row[data-v-e4cab5f3],.sms-thresholds__row[data-v-e4cab5f3]{flex-wrap:wrap;align-items:end;gap:12px;display:flex}.sms-manage__tariff[data-v-e4cab5f3],.sms-manage__price[data-v-e4cab5f3],.sms-manage__charge[data-v-e4cab5f3]{align-items:center;gap:8px;display:flex}.sms-manage__price[data-v-e4cab5f3] input,.sms-manage__charge[data-v-e4cab5f3] input,.sms-manage__charge .admin-input[data-v-e4cab5f3],.sms-thresholds__row[data-v-e4cab5f3] input{width:140px}.sms-manage__tariff strong[data-v-e4cab5f3]{text-align:center;min-width:72px;font-size:1.25rem}.sms-manage__charge[data-v-e4cab5f3]{margin-right:auto}.sms-manage__hint[data-v-e4cab5f3],.sms-deposit__hint[data-v-e4cab5f3]{color:var(--color-text-muted,#64748b);margin:12px 0 0;font-size:.8125rem}.sms-block[data-v-e4cab5f3]{background:#ecfdf3;border-radius:12px;justify-content:space-between;align-items:center;gap:12px;margin-top:14px;padding:12px 14px;display:flex}.sms-block p[data-v-e4cab5f3]{color:#067647;margin:4px 0 0;font-size:.75rem}.sms-block__switch[data-v-e4cab5f3]{color:#067647;align-items:center;gap:8px;font-weight:700;display:flex}.sms-deposit__save[data-v-e4cab5f3]{width:100%;margin-top:8px}.sms-thresholds__row .admin-btn[data-v-e4cab5f3]{margin-bottom:2px}.admin-form__field>span[data-v-e4cab5f3]{font-size:.8125rem;font-weight:600}@media (width<=960px){.sms-stats[data-v-e4cab5f3]{grid-template-columns:1fr 1fr}.sms-manage__charge[data-v-e4cab5f3]{margin-right:0}}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
import{O as e,Ot as t,T as n,Z as r,c as i,d as a,g as o,j as s,l as c,m as l,r as u,t as d}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{E as f,N as p,z as m}from"./index-CXa4EMLW.js";import{t as h}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as g}from"./BaseEmptyState-BmO9aDcK.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=r([]),S=r(!0),C=m();async function w(){S.value=!0;try{d.value=await f.list()}catch(e){C.error(e instanceof p?e.message:`بارگذاری ناموفق بود`)}finally{S.value=!1}}async function T(e){try{await f.notify(e),C.success(`اعلان موجودی ارسال شد`),await w()}catch(e){C.error(e instanceof p?e.message:`ارسال ناموفق بود`)}}return n(()=>{w()}),(n,r)=>(e(),a(`div`,_,[r[1]||=i(`header`,{class:`admin-page__head`},[i(`div`,null,[i(`h1`,null,`درخواست خبر موجودی`),i(`p`,null,`مشتریانی که برای محصول ناموجود درخواست اطلاع داده‌اند`)])],-1),S.value?(e(),c(h,{key:0,variant:`table`})):d.value.length?(e(),a(`table`,v,[r[0]||=i(`thead`,null,[i(`tr`,null,[i(`th`,null,`مشتری`),i(`th`,null,`محصول`),i(`th`,null,`موجودی فعلی`),i(`th`)])],-1),i(`tbody`,null,[(e(!0),a(u,null,s(d.value,n=>(e(),a(`tr`,{key:n.id},[i(`td`,null,[l(t(n.customerName)+` `,1),i(`span`,y,t(n.customerPhone),1)]),i(`td`,null,[l(t(n.productName)+` `,1),i(`span`,b,`(`+t(n.productCode)+`)`,1)]),i(`td`,null,t(n.stock??`—`),1),i(`td`,null,[i(`button`,{type:`button`,class:`admin-btn admin-btn--primary admin-btn--sm`,onClick:e=>T(n.id)},`ارسال اعلان`,8,x)])]))),128))])])):(e(),c(g,{key:1,title:`درخواستی فعال نیست`}))]))}}),[[`__scopeId`,`data-v-61131544`]]);export{S as default};
+1
View File
@@ -0,0 +1 @@
import{O as e,Ot as t,T as n,Z as r,c as i,d as a,g as o,j as s,l as c,m as l,r as u,t as d}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{a as f,t as p}from"./http-Djf2pGxE.js";import{A as m}from"./index-CznUUXv3.js";import{t as h}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as g}from"./BaseEmptyState-D6sk-aI_.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=r([]),S=r(!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()}),(n,r)=>(e(),a(`div`,_,[r[1]||=i(`header`,{class:`admin-page__head`},[i(`div`,null,[i(`h1`,null,`درخواست خبر موجودی`),i(`p`,null,`مشتریانی که برای محصول ناموجود درخواست اطلاع داده‌اند`)])],-1),S.value?(e(),c(h,{key:0,variant:`table`})):d.value.length?(e(),a(`table`,v,[r[0]||=i(`thead`,null,[i(`tr`,null,[i(`th`,null,`مشتری`),i(`th`,null,`محصول`),i(`th`,null,`موجودی فعلی`),i(`th`)])],-1),i(`tbody`,null,[(e(!0),a(u,null,s(d.value,n=>(e(),a(`tr`,{key:n.id},[i(`td`,null,[l(t(n.customerName)+` `,1),i(`span`,y,t(n.customerPhone),1)]),i(`td`,null,[l(t(n.productName)+` `,1),i(`span`,b,`(`+t(n.productCode)+`)`,1)]),i(`td`,null,t(n.stock??`—`),1),i(`td`,null,[i(`button`,{type:`button`,class:`admin-btn admin-btn--primary admin-btn--sm`,onClick:e=>T(n.id)},`ارسال اعلان`,8,x)])]))),128))])])):(e(),c(g,{key:1,title:`درخواستی فعال نیست`}))]))}}),[[`__scopeId`,`data-v-61131544`]]);export{S as default};
+1
View File
@@ -0,0 +1 @@
import{B as e,O as t,Ot as n,T as r,V as i,Y as a,Z as o,c as s,d as c,g as l,h as u,j as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{a as _,f as v,t as y}from"./http-Djf2pGxE.js";import{T as b,V as x,j as S,y as C}from"./index-CznUUXv3.js";import{t as w}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as T}from"./BaseEmptyState-D6sk-aI_.js";import{t as E}from"./AdminSelect-DK-_otkw.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=o([]),I=o(!0),L=o([]),R=o([]),z=o(!1),B=_(),V=a({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 r(()=>{W()}),(r,a)=>(t(),c(`div`,D,[s(`header`,O,[a[5]||=s(`div`,null,[s(`h1`,null,`پیشنهاد محصول`),s(`p`,null,`پیشنهاد دستی محصول به مشتری`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:a[0]||=e=>z.value=!0},`پیشنهاد جدید`)]),I.value?(t(),f(w,{key:0,variant:`table`})):g.value.length?(t(),c(`table`,k,[a[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,[(t(!0),c(m,null,d(g.value,e=>(t(),c(`tr`,{key:e.id},[s(`td`,null,n(e.customerName),1),s(`td`,null,[p(n(e.productName)+` `,1),s(`span`,A,`(`+n(e.productCode)+`)`,1)]),s(`td`,null,n(e.note||`—`),1),s(`td`,null,n(e.isActive?`فعال`:`غیرفعال`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>K(e)},n(e.isActive?`غیرفعال`:`فعال`),9,j)])]))),128))])])):(t(),f(T,{key:1,title:`پیشنهادی ثبت نشده`})),u(x,{open:z.value,title:`پیشنهاد محصول`,onClose:a[4]||=e=>z.value=!1},{footer:e(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!V.customerId||!V.productId,onClick:G},` ثبت `,8,F)]),default:e(()=>[s(`label`,M,[a[7]||=p(`مشتری `,-1),u(E,{modelValue:V.customerId,"onUpdate:modelValue":a[1]||=e=>V.customerId=e,options:H.value},null,8,[`modelValue`,`options`])]),s(`label`,N,[a[8]||=p(`محصول `,-1),u(E,{modelValue:V.productId,"onUpdate:modelValue":a[2]||=e=>V.productId=e,options:U.value},null,8,[`modelValue`,`options`])]),s(`label`,P,[a[9]||=p(`یادداشت`,-1),i(s(`textarea`,{"onUpdate:modelValue":a[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};
-1
View File
@@ -1 +0,0 @@
import{B as e,O as t,Ot as n,T as r,V as i,Y as a,Z as o,c as s,d as c,g as l,h as u,j as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-OaxA6A2f.js";import{D as _,N as v,R as y,S as b,W as x,g as S,z as C}from"./index-CXa4EMLW.js";import{t as w}from"./AdminPageSkeleton-CTgUqaAu.js";import{t as T}from"./BaseEmptyState-BmO9aDcK.js";import{t as E}from"./AdminSelect-9UfEJQzN.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=o([]),I=o(!0),L=o([]),R=o([]),z=o(!1),B=C(),V=a({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([_.list(),S.list(),b.list()]);g.value=e,L.value=t,R.value=n}catch(e){B.error(e instanceof v?e.message:`بارگذاری ناموفق بود`)}finally{I.value=!1}}async function G(){try{await _.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 v?e.message:`ثبت ناموفق بود`)}}async function K(e){try{await _.setActive(e.id,!e.isActive),await W()}catch(e){B.error(e instanceof v?e.message:`به‌روزرسانی ناموفق بود`)}}return r(()=>{W()}),(r,a)=>(t(),c(`div`,D,[s(`header`,O,[a[5]||=s(`div`,null,[s(`h1`,null,`پیشنهاد محصول`),s(`p`,null,`پیشنهاد دستی محصول به مشتری`)],-1),s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:a[0]||=e=>z.value=!0},`پیشنهاد جدید`)]),I.value?(t(),f(w,{key:0,variant:`table`})):g.value.length?(t(),c(`table`,k,[a[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,[(t(!0),c(m,null,d(g.value,e=>(t(),c(`tr`,{key:e.id},[s(`td`,null,n(e.customerName),1),s(`td`,null,[p(n(e.productName)+` `,1),s(`span`,A,`(`+n(e.productCode)+`)`,1)]),s(`td`,null,n(e.note||`—`),1),s(`td`,null,n(e.isActive?`فعال`:`غیرفعال`),1),s(`td`,null,[s(`button`,{type:`button`,class:`link`,onClick:t=>K(e)},n(e.isActive?`غیرفعال`:`فعال`),9,j)])]))),128))])])):(t(),f(T,{key:1,title:`پیشنهادی ثبت نشده`})),u(y,{open:z.value,title:`پیشنهاد محصول`,onClose:a[4]||=e=>z.value=!1},{footer:e(()=>[s(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!V.customerId||!V.productId,onClick:G},` ثبت `,8,F)]),default:e(()=>[s(`label`,M,[a[7]||=p(`مشتری `,-1),u(E,{modelValue:V.customerId,"onUpdate:modelValue":a[1]||=e=>V.customerId=e,options:H.value},null,8,[`modelValue`,`options`])]),s(`label`,N,[a[8]||=p(`محصول `,-1),u(E,{modelValue:V.productId,"onUpdate:modelValue":a[2]||=e=>V.productId=e,options:U.value},null,8,[`modelValue`,`options`])]),s(`label`,P,[a[9]||=p(`یادداشت`,-1),i(s(`textarea`,{"onUpdate:modelValue":a[3]||=e=>V.note=e,class:`admin-input`,rows:`3`},null,512),[[x,V.note]])])]),_:1},8,[`open`])]))}}),[[`__scopeId`,`data-v-56e3e728`]]);export{I as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
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
-382
View File
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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+10 -3
View File
@@ -2,12 +2,19 @@
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/admin/favicon.ico">
<link rel="icon" type="image/x-icon" href="./favicon.ico?v=2">
<link rel="icon" type="image/png" sizes="192x192" href="./icon-192.png?v=2">
<link rel="apple-touch-icon" href="./apple-touch-icon.png?v=2">
<link rel="manifest" href="./manifest.webmanifest?v=2">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#081d39">
<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-CXa4EMLW.js"></script>
<script type="module" crossorigin src="/admin/assets/index-CznUUXv3.js"></script>
<link rel="modulepreload" crossorigin href="/admin/assets/_plugin-vue_export-helper-OaxA6A2f.js">
<link rel="stylesheet" crossorigin href="/admin/assets/index-C3X1YgAk.css">
<link rel="modulepreload" crossorigin href="/admin/assets/http-Djf2pGxE.js">
<link rel="stylesheet" crossorigin href="/admin/assets/index-BZ7aOp8K.css">
</head>
<body>
<div id="app"></div>
+25
View File
@@ -0,0 +1,25 @@
{
"name": "نادمی تکس | پنل مدیریت",
"short_name": "پنل نادمی تکس",
"description": "پنل مدیریت سفارش‌های عمده",
"lang": "fa",
"dir": "rtl",
"start_url": "./",
"display": "standalone",
"background_color": "#081d39",
"theme_color": "#081d39",
"icons": [
{
"src": "icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
Vendored
+26
View File
@@ -0,0 +1,26 @@
const CACHE_NAME = 'nademitex-admin-v1'
self.addEventListener('install', () => {
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
const url = new URL(event.request.url)
if (url.origin !== self.location.origin || url.pathname.includes('/server/api')) return
event.respondWith(
fetch(event.request)
.then((response) => {
const copy = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, copy))
return response
})
.catch(() => caches.match(event.request)),
)
})
+7 -1
View File
@@ -2,8 +2,14 @@
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<link rel="icon" href="%BASE_URL%favicon.ico">
<link rel="icon" type="image/x-icon" href="./favicon.ico?v=2">
<link rel="icon" type="image/png" sizes="192x192" href="./icon-192.png?v=2">
<link rel="apple-touch-icon" href="./apple-touch-icon.png?v=2">
<link rel="manifest" href="./manifest.webmanifest?v=2">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="theme-color" content="#081d39">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>پنل مدیریت | پارچه</title>
</head>
<body>
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"name": "نادمی تکس | پنل مدیریت",
"short_name": "پنل نادمی تکس",
"description": "پنل مدیریت سفارش‌های عمده",
"lang": "fa",
"dir": "rtl",
"start_url": "./",
"display": "standalone",
"background_color": "#081d39",
"theme_color": "#081d39",
"icons": [
{
"src": "icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
]
}
+26
View File
@@ -0,0 +1,26 @@
const CACHE_NAME = 'nademitex-admin-v1'
self.addEventListener('install', () => {
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return
const url = new URL(event.request.url)
if (url.origin !== self.location.origin || url.pathname.includes('/server/api')) return
event.respondWith(
fetch(event.request)
.then((response) => {
const copy = response.clone()
caches.open(CACHE_NAME).then((cache) => cache.put(event.request, copy))
return response
})
.catch(() => caches.match(event.request)),
)
})
+1 -1
View File
@@ -116,7 +116,7 @@
.admin-btn--primary {
background: var(--btn-primary-bg);
color: #fff;
color: var(--color-surface);
border-color: var(--btn-primary-bg);
}
+31 -31
View File
@@ -1,17 +1,17 @@
@import './fonts.css';
:root {
/* Brand — هم‌تراز اپ کاربر (mockups / main.css) */
--color-primary: #0a353c;
--color-primary-hover: #072a2f;
--color-primary-soft: #e8f4f1;
--color-primary-dark: #062428;
--color-primary-light: #e8f4f1;
--color-primary-rgb: 10, 53, 60;
--color-accent-navy: #1b223c;
--color-sidebar-bg: #0a353c;
--color-sidebar-active: #0e454e;
--color-sidebar-hover: #0e454e;
/* Brand — sampled from the Nademitex logo */
--color-primary: #091d36;
--color-primary-hover: #061426;
--color-primary-soft: #e9edf2;
--color-primary-dark: #04101f;
--color-primary-light: #e9edf2;
--color-primary-rgb: 9, 29, 54;
--color-accent-navy: #091d36;
--color-sidebar-bg: #091d36;
--color-sidebar-active: #162b46;
--color-sidebar-hover: #162b46;
--color-success: #1b8a5a;
--color-success-light: #eaf8f1;
--color-success-dark: #0d7a4d;
@@ -25,12 +25,12 @@
--color-purple-light: #ede9fe;
--color-info: #3b6fa0;
--color-info-light: #eef3f7;
--color-bg: #f6f8f7;
--color-bg: #f3f5f7;
--color-surface: #ffffff;
--color-elevated: #ffffff;
--color-border: #e5e9e8;
--color-border-light: #eef1f0;
--color-text: #121212;
--color-border: #dfe4ea;
--color-border-light: #e9edf2;
--color-text: #121821;
--color-text-muted: #6b7280;
--color-text-secondary: #6b7280;
--admin-primary: var(--color-primary);
@@ -46,8 +46,8 @@
--sidebar-width: 240px;
--header-height: 72px;
--font-family: 'IRANSans', 'Tahoma', sans-serif;
--btn-primary-bg: #0a353c;
--btn-primary-hover: #072a2f;
--btn-primary-bg: var(--color-primary);
--btn-primary-hover: var(--color-primary-hover);
--admin-desktop-min: 1280px;
--control-height: 40px;
--control-height-sm: 32px;
@@ -59,20 +59,20 @@
--space-6: 24px;
--transition-fast: 0.15s ease;
--transition-base: 0.18s ease;
--focus-ring: 0 0 0 3px rgba(10, 53, 60, 0.18);
--focus-ring: 0 0 0 3px rgba(9, 29, 54, 0.2);
}
html.dark {
--color-primary: #5eb8ad;
--color-primary-hover: #7ad0c5;
--color-primary-soft: #14353a;
--color-primary-dark: #7ad0c5;
--color-primary-light: #14353a;
--color-primary-rgb: 94, 184, 173;
--color-accent-navy: #9bb0d0;
--color-primary: #7d8695;
--color-primary-hover: #aeb6c2;
--color-primary-soft: #27303d;
--color-primary-dark: #aeb6c2;
--color-primary-light: #27303d;
--color-primary-rgb: 125, 134, 149;
--color-accent-navy: #7d8695;
--color-sidebar-bg: #0b0f14;
--color-sidebar-active: #14353a;
--color-sidebar-hover: #121821;
--color-sidebar-active: #27303d;
--color-sidebar-hover: #1a2230;
--color-success: #3dd68c;
--color-success-light: #123528;
--color-success-dark: #6ee7b7;
@@ -91,14 +91,14 @@ html.dark {
--color-elevated: #1a2230;
--color-border: #2a3444;
--color-border-light: #1a2230;
--color-text: #eef2f8;
--color-text: #ffffff;
--color-text-muted: #9aa8bc;
--color-text-secondary: #9aa8bc;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
--shadow-card: 0 1px 3px rgba(0, 0, 0, 0.4), 0 4px 16px rgba(0, 0, 0, 0.35);
--btn-primary-bg: #5eb8ad;
--btn-primary-hover: #7ad0c5;
--focus-ring: 0 0 0 3px rgba(94, 184, 173, 0.28);
--btn-primary-bg: var(--color-primary);
--btn-primary-hover: var(--color-primary-hover);
--focus-ring: 0 0 0 3px rgba(125, 134, 149, 0.32);
}
*,
+1 -1
View File
@@ -14,7 +14,7 @@ const props = withDefaults(
}>(),
{
height: 160,
color: '#0a353c',
color: '#091d36',
unit: 'میلیارد تومان',
},
)
+191 -2
View File
@@ -1,12 +1,16 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { onClickOutside, useEventListener } from '@vueuse/core'
import AdminIcon from '@/components/icons/AdminIcon.vue'
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 { formatDateTime } from '@/utils/format'
import { formatDateTime, formatNumber, toPersianDigits } from '@/utils/format'
import { resolveAdminInboxLink } from '@/utils/inboxLinks'
import { formatCardNumber, formatSheba } from '@/utils/smsFormat'
import { useAuthStore } from '@/stores/auth'
import { useThemeStore, type ThemePreference } from '@/stores/theme'
import type { AdminNotification, InternalMessage } from '@/types'
@@ -20,6 +24,9 @@ type OpenPanel = 'messages' | 'notifications' | 'theme' | null
const router = useRouter()
const auth = useAuthStore()
const theme = useThemeStore()
const toast = useToast()
const { summary: smsSummary, refresh: refreshSmsSummary } = useSmsWalletSummary()
const chargeOpen = ref(false)
const openPanel = ref<OpenPanel>(null)
const actionsRef = ref<HTMLElement | null>(null)
@@ -135,6 +142,30 @@ function selectTheme(value: ThemePreference) {
theme.setPreference(value)
closePanel()
}
const smsCreditTone = computed(() => {
const row = smsSummary.value
if (!row) return 'ok'
if (row.blocked || row.balanceToman <= row.redThreshold) return 'danger'
if (row.balanceToman <= row.yellowThreshold) return 'warn'
return 'ok'
})
function refreshSmsCredit() {
void refreshSmsSummary().catch(() => {})
}
onMounted(refreshSmsCredit)
useEventListener(window, 'focus', refreshSmsCredit)
async function copyDeposit(value: string) {
try {
await navigator.clipboard.writeText(value.replace(/\s/g, ''))
toast.success('کپی شد.')
} catch {
toast.error('کپی انجام نشد.')
}
}
</script>
<template>
@@ -151,6 +182,21 @@ function selectTheme(value: ThemePreference) {
</div>
<div ref="actionsRef" class="admin-header__left">
<div
class="sms-credit"
:class="smsSummary ? `sms-credit--${smsCreditTone}` : ''"
>
<button
type="button"
class="sms-credit__charge"
:disabled="!smsSummary"
@click="chargeOpen = true"
>
شارژ
</button>
<span>اعتبار پیامک: {{ smsSummary ? formatNumber(smsSummary.balanceRial) : '—' }} ریال</span>
<span v-if="smsSummary?.blocked">مسدود</span>
</div>
<div class="admin-header__dropdown">
<button
type="button"
@@ -336,6 +382,36 @@ function selectTheme(value: ThemePreference) {
</div>
</div>
</header>
<BaseModal :open="chargeOpen" title="شارژ اعتبار پیامک" @close="chargeOpen = false">
<div v-if="smsSummary" class="sms-charge">
<p class="sms-charge__note">
لطفا مبلغ مورد نظر خود جهت شارژ سامانه پیامکی را به شماره کارت یا حساب زیر واریز نمایید و تصویر فیش پرداختی را برای پشتیبانی بفرستید.
</p>
<div class="sms-charge__row">
<button type="button" class="sms-charge__copy" @click="copyDeposit(smsSummary.deposit.cardNumber)">
کپی
</button>
<div>
<span>شماره کارت</span>
<strong dir="ltr">{{ toPersianDigits(formatCardNumber(smsSummary.deposit.cardNumber)) }}</strong>
</div>
</div>
<div class="sms-charge__row">
<button type="button" class="sms-charge__copy sms-charge__copy--sheba" @click="copyDeposit(smsSummary.deposit.sheba)">
کپی
</button>
<div>
<span>شماره شبا</span>
<strong dir="ltr">{{ toPersianDigits(formatSheba(smsSummary.deposit.sheba)) }}</strong>
</div>
</div>
<p class="sms-charge__name">
به نام
<strong>{{ smsSummary.deposit.accountName }}</strong>
</p>
</div>
</BaseModal>
</template>
<style scoped>
@@ -725,5 +801,118 @@ function selectTheme(value: ThemePreference) {
padding-right: 0;
margin-right: 0;
}
.sms-credit {
max-width: 46vw;
}
.sms-credit span:nth-child(2) {
overflow: hidden;
text-overflow: ellipsis;
}
}
.sms-credit {
display: inline-flex;
align-items: center;
gap: 8px;
max-width: 100%;
padding: 4px 12px 4px 4px;
border-radius: 999px;
background: #e8f8ef;
color: #067647;
font-size: 0.75rem;
font-weight: 700;
white-space: nowrap;
}
.sms-credit--warn {
background: #fff7ed;
color: #c2410c;
}
.sms-credit--danger {
background: #fef2f2;
color: #b91c1c;
}
.sms-credit__charge {
border: 0;
border-radius: 999px;
padding: 4px 12px;
background: #12b76a;
color: #fff;
font: inherit;
font-weight: 800;
cursor: pointer;
}
.sms-credit--warn .sms-credit__charge {
background: #f79009;
}
.sms-credit--danger .sms-credit__charge {
background: #f04438;
}
.sms-charge__note {
margin: 0 0 14px;
padding: 12px 14px;
border-radius: 14px;
background: #eef6ff;
color: #334155;
font-size: 0.8125rem;
line-height: 1.7;
}
.sms-charge__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
padding: 12px;
border: 1px solid var(--color-border);
border-radius: 14px;
}
.sms-charge__row span {
display: block;
margin-bottom: 4px;
color: var(--color-text-muted, #64748b);
font-size: 0.75rem;
}
.sms-charge__row strong {
font-size: 1.05rem;
letter-spacing: 0.04em;
}
.sms-charge__copy {
border: 1px solid #c4b5fd;
border-radius: 999px;
padding: 4px 10px;
background: #f5f3ff;
color: #6d28d9;
font: inherit;
cursor: pointer;
}
.sms-charge__copy--sheba {
border-color: #a7f3d0;
background: #ecfdf3;
color: #067647;
}
.sms-charge__name {
margin: 4px 0 0;
text-align: left;
font-size: 0.875rem;
}
.sms-charge__name strong {
display: block;
margin-top: 4px;
font-size: 1.05rem;
}
</style>
+14 -2
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { ADMIN_NAV_ITEMS, isNavActive } from '@/utils/navigation'
import { computed } from 'vue'
import { isNavActive, navItemsForPhone } from '@/utils/navigation'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import { useAuthStore } from '@/stores/auth'
@@ -15,6 +16,8 @@ const emit = defineEmits<{
const route = useRoute()
const router = useRouter()
const auth = useAuthStore()
const navItems = computed(() => navItemsForPhone(auth.session?.phone))
const brandLogoUrl = `${import.meta.env.BASE_URL}brand-logo.png`
function navigate(path: string) {
router.push(path)
@@ -42,6 +45,7 @@ function logout() {
:aria-hidden="!open"
>
<div class="admin-drawer__brand">
<img :src="brandLogoUrl" alt="نادمی تکس" class="admin-drawer__logo" />
<strong>پارچه</strong>
<span>پنل مدیریت</span>
<button type="button" class="admin-drawer__close" aria-label="بستن منو" @click="emit('close')">
@@ -51,7 +55,7 @@ function logout() {
<nav class="admin-drawer__nav">
<button
v-for="item in ADMIN_NAV_ITEMS"
v-for="item in navItems"
:key="item.name"
type="button"
class="admin-drawer__item"
@@ -78,6 +82,14 @@ function logout() {
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.admin-drawer__logo {
display: block;
width: 92px;
height: 64px;
object-fit: contain;
margin: 0 auto 8px;
}
.admin-drawer__brand strong {
display: block;
font-size: 1.0625rem;
+14 -2
View File
@@ -1,12 +1,15 @@
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router'
import { ADMIN_NAV_ITEMS, isNavActive } from '@/utils/navigation'
import { computed } from 'vue'
import { isNavActive, navItemsForPhone } from '@/utils/navigation'
import AdminIcon from '@/components/icons/AdminIcon.vue'
import { useAuthStore } from '@/stores/auth'
const route = useRoute()
const router = useRouter()
const auth = useAuthStore()
const navItems = computed(() => navItemsForPhone(auth.session?.phone))
const brandLogoUrl = `${import.meta.env.BASE_URL}brand-logo.png`
function navigate(path: string) {
router.push(path)
@@ -21,13 +24,14 @@ function logout() {
<template>
<aside class="admin-sidebar" aria-label="ناوبری پنل مدیریت">
<div class="admin-sidebar__brand">
<img :src="brandLogoUrl" alt="نادمی تکس" class="admin-sidebar__logo" />
<strong class="admin-sidebar__title">پارچه</strong>
<span class="admin-sidebar__subtitle">پنل مدیریت</span>
</div>
<nav class="admin-sidebar__nav">
<button
v-for="item in ADMIN_NAV_ITEMS"
v-for="item in navItems"
:key="item.name"
type="button"
class="admin-sidebar__item"
@@ -64,6 +68,14 @@ function logout() {
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}
.admin-sidebar__logo {
display: block;
width: 92px;
height: 64px;
object-fit: contain;
margin: 0 auto 8px;
}
.admin-sidebar__title {
display: block;
font-size: 1.125rem;
+12
View File
@@ -0,0 +1,12 @@
import { ref } from 'vue'
import { smsWalletApi, type SmsWalletSummary } from '@/services/api'
const summary = ref<SmsWalletSummary | null>(null)
export function useSmsWalletSummary() {
async function refresh() {
summary.value = await smsWalletApi.summary()
}
return { summary, refresh }
}
+8
View File
@@ -21,3 +21,11 @@ useThemeStore(pinia)
useAuthStore(pinia).hydrate()
app.mount('#app')
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
window.addEventListener('load', () => {
void navigator.serviceWorker.register(`${import.meta.env.BASE_URL}sw.js`, {
scope: import.meta.env.BASE_URL,
})
})
}
+11
View File
@@ -1,6 +1,7 @@
import { createRouter, createWebHistory } from 'vue-router'
import AdminShell from '@/layouts/AdminShell.vue'
import { useAuthStore } from '@/stores/auth'
import { isSmsWalletOwner } from '@/utils/navigation'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@@ -151,6 +152,12 @@ const router = createRouter({
component: () => import('@/views/settings/SettingsView.vue'),
meta: { title: 'تنظیمات' },
},
{
path: 'sms-tariff',
name: 'sms-tariff',
component: () => import('@/views/sms/SmsTariffView.vue'),
meta: { title: 'تعرفه پیامک‌ها', smsWalletOwner: true },
},
{
path: 'users',
name: 'users',
@@ -185,6 +192,10 @@ router.beforeEach(async (to) => {
}
}
if (to.meta.smsWalletOwner && !isSmsWalletOwner(auth.session?.phone)) {
return { name: 'dashboard' }
}
return true
})
+43
View File
@@ -667,3 +667,46 @@ export const stockAlertsApi = {
notify: (id: string) =>
apiRequest<StockAlert>(`/admin/stock-alerts/${id}/notify`, { method: 'PATCH' }),
}
/* ── SMS wallet ───────────────────────────────────────────── */
export type SmsUsageStats = {
messages: number
parts: number
costToman: number
}
export type SmsWalletSummary = {
balanceToman: number
balanceRial: number
blocked: boolean
yellowThreshold: number
redThreshold: number
deposit: {
cardNumber: string
sheba: string
accountName: string
}
}
export type SmsWalletManage = {
balanceToman: number
pricePerPart: number
blocked: boolean
cardNumber: string
sheba: string
accountName: string
yellowThreshold: number
redThreshold: number
total: SmsUsageStats
today: SmsUsageStats
}
export const smsWalletApi = {
summary: () => apiRequest<SmsWalletSummary>('/admin/sms-wallet'),
manage: () => apiRequest<SmsWalletManage>('/admin/sms-wallet/manage'),
update: (body: Partial<Omit<SmsWalletManage, 'balanceToman' | 'total' | 'today'>>) =>
apiRequest<SmsWalletManage>('/admin/sms-wallet', { method: 'PATCH', body }),
charge: (body: { amountToman: number; note?: string }) =>
apiRequest<SmsWalletManage>('/admin/sms-wallet/charge', { body }),
}
+16
View File
@@ -24,9 +24,25 @@ export const ADMIN_NAV_ITEMS: NavItem[] = [
{ name: 'shipping', label: 'ارسال و باربری', path: '/shipping', icon: 'shipping' },
{ name: 'reports', label: 'گزارش‌ها', path: '/reports', icon: 'reports' },
{ name: 'settings', label: 'تنظیمات', path: '/settings', icon: 'settings' },
{ name: 'sms-tariff', label: 'تعرفه پیامک‌ها', path: '/sms-tariff', icon: 'sms' },
{ name: 'users', label: 'کاربران و دسترسی‌ها', path: '/users', icon: 'users' },
]
export const SMS_WALLET_OWNER_PHONE = '09128081392'
export function isSmsWalletOwner(phone?: string | null): boolean {
if (!phone) return false
let digits = phone.replace(/\D/g, '')
if (digits.startsWith('98') && digits.length === 12) digits = `0${digits.slice(2)}`
if (digits.length === 10 && digits.startsWith('9')) digits = `0${digits}`
return digits === SMS_WALLET_OWNER_PHONE
}
export function navItemsForPhone(phone?: string | null): NavItem[] {
if (isSmsWalletOwner(phone)) return ADMIN_NAV_ITEMS
return ADMIN_NAV_ITEMS.filter((item) => item.name !== 'sms-tariff')
}
export function isNavActive(path: string, currentPath: string): boolean {
if (path === '/') return currentPath === '/'
// چک‌های یک سفارش زیر منوی «چک‌ها» فعال می‌شود، نه «سفارش‌ها»
+12
View File
@@ -0,0 +1,12 @@
export function formatCardNumber(raw: string): string {
const digits = raw.replace(/\D/g, '')
return digits.replace(/(\d{4})(?=\d)/g, '$1 ').trim()
}
export function formatSheba(raw: string): string {
const compact = raw.replace(/[\s-]/g, '').toUpperCase()
const body = compact.startsWith('IR') ? compact.slice(2) : compact
if (body.length < 2) return compact
const groups = body.slice(2).replace(/(.{4})/g, '$1 ').trim()
return `IR${body.slice(0, 2)} ${groups}`.trim()
}
+16 -1
View File
@@ -16,6 +16,7 @@ const password = ref(isMockAuthEnabled() ? MOCK_ADMIN.password : '')
const remember = ref(true)
const showPassword = ref(false)
const mockAuth = isMockAuthEnabled()
const brandLogoUrl = `${import.meta.env.BASE_URL}brand-logo.png`
const isFormValid = computed(() => phone.value.trim().length > 0 && password.value.length > 0)
@@ -123,6 +124,7 @@ async function submit() {
<div class="login-page__brand">
<div class="login-page__brand-inner">
<img :src="brandLogoUrl" alt="نادمی تکس" class="login-page__brand-logo" />
<p class="login-page__brand-badge">پارچه</p>
<h1 class="login-page__brand-title">پنل مدیریت سفارش‌های عمده</h1>
<p class="login-page__brand-text">
@@ -146,7 +148,12 @@ async function submit() {
align-items: center;
justify-content: center;
padding: 48px 40px;
background: linear-gradient(160deg, #062428 0%, #0a353c 55%, #0e454e 100%);
background: linear-gradient(
160deg,
var(--color-primary-dark) 0%,
var(--color-primary) 55%,
var(--color-sidebar-active) 100%
);
color: #fff;
}
@@ -154,6 +161,14 @@ async function submit() {
max-width: 420px;
}
.login-page__brand-logo {
display: block;
width: min(220px, 70%);
height: 150px;
object-fit: contain;
margin-bottom: 28px;
}
.login-page__brand-badge {
display: inline-flex;
margin: 0 0 16px;
+445
View File
@@ -0,0 +1,445 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import AdminCurrencyInput from '@/components/ui/AdminCurrencyInput.vue'
import AdminPageSkeleton from '@/components/ui/AdminPageSkeleton.vue'
import BaseSwitch from '@/components/ui/BaseSwitch.vue'
import { useSmsWalletSummary } from '@/composables/useSmsWalletSummary'
import { useToast } from '@/composables/useToast'
import { smsWalletApi, type SmsUsageStats, type SmsWalletManage } from '@/services/api'
import { formatNumber } from '@/utils/format'
import { formatCardNumber, formatSheba } from '@/utils/smsFormat'
const toast = useToast()
const { refresh: refreshSummary } = useSmsWalletSummary()
const loading = ref(true)
const savingPrice = ref(false)
const charging = ref(false)
const savingDeposit = ref(false)
const savingThresholds = ref(false)
const toggling = ref(false)
const wallet = ref<SmsWalletManage | null>(null)
const pricePerPart = ref<number | null>(250)
const chargeAmount = ref<number | null>(null)
const chargeNote = ref('')
const cardNumber = ref('')
const sheba = ref('')
const accountName = ref('')
const yellowThreshold = ref<number | null>(10000)
const redThreshold = ref<number | null>(1000)
function apply(data: SmsWalletManage) {
wallet.value = data
pricePerPart.value = data.pricePerPart
cardNumber.value = formatCardNumber(data.cardNumber)
sheba.value = formatSheba(data.sheba)
accountName.value = data.accountName
yellowThreshold.value = data.yellowThreshold
redThreshold.value = data.redThreshold
}
onMounted(async () => {
try {
apply(await smsWalletApi.manage())
await refreshSummary().catch(() => {})
} catch (e) {
toast.error(e instanceof Error ? e.message : 'دریافت تعرفه پیامک ناموفق بود.')
} finally {
loading.value = false
}
})
async function savePrice() {
if (!pricePerPart.value || pricePerPart.value < 1 || savingPrice.value) return
savingPrice.value = true
try {
apply(await smsWalletApi.update({ pricePerPart: pricePerPart.value }))
toast.success('تعرفه پیامک ذخیره شد.')
} catch (e) {
toast.error(e instanceof Error ? e.message : 'ذخیره تعرفه ناموفق بود.')
} finally {
savingPrice.value = false
}
}
async function charge() {
if (!chargeAmount.value || chargeAmount.value < 1 || charging.value) return
charging.value = true
try {
apply(
await smsWalletApi.charge({
amountToman: chargeAmount.value,
note: chargeNote.value.trim() || undefined,
}),
)
chargeAmount.value = null
chargeNote.value = ''
await refreshSummary()
toast.success('کیف پول پیامک شارژ شد.')
} catch (e) {
toast.error(e instanceof Error ? e.message : 'شارژ کیف پول ناموفق بود.')
} finally {
charging.value = false
}
}
async function setBlocked(next: boolean) {
if (!wallet.value || toggling.value) return
const previous = wallet.value.blocked
wallet.value.blocked = next
toggling.value = true
try {
apply(await smsWalletApi.update({ blocked: next }))
await refreshSummary()
toast.success(next ? 'ارسال پیامک مسدود شد.' : 'ارسال پیامک فعال شد.')
} catch (e) {
wallet.value.blocked = previous
toast.error(e instanceof Error ? e.message : 'تغییر وضعیت مسدودسازی ناموفق بود.')
} finally {
toggling.value = false
}
}
async function saveDeposit() {
if (savingDeposit.value) return
savingDeposit.value = true
try {
apply(
await smsWalletApi.update({
cardNumber: cardNumber.value,
sheba: sheba.value,
accountName: accountName.value,
}),
)
await refreshSummary()
toast.success('اطلاعات واریز ذخیره شد.')
} catch (e) {
toast.error(e instanceof Error ? e.message : 'ذخیره اطلاعات واریز ناموفق بود.')
} finally {
savingDeposit.value = false
}
}
async function saveThresholds() {
if (yellowThreshold.value == null || redThreshold.value == null || savingThresholds.value) return
savingThresholds.value = true
try {
apply(
await smsWalletApi.update({
yellowThreshold: yellowThreshold.value,
redThreshold: redThreshold.value,
}),
)
await refreshSummary()
toast.success('آستانه‌ها ذخیره شد.')
} catch (e) {
toast.error(e instanceof Error ? e.message : 'ذخیره آستانه‌ها ناموفق بود.')
} finally {
savingThresholds.value = false
}
}
function stat(row: SmsUsageStats | undefined) {
return row ?? { messages: 0, parts: 0, costToman: 0 }
}
</script>
<template>
<div class="sms-page">
<div class="admin-page-header">
<h1 class="admin-page-header__title">تعرفه پیامک‌ها</h1>
</div>
<AdminPageSkeleton v-if="loading" variant="form" :rows="8" />
<template v-else-if="wallet">
<section class="sms-stats">
<article class="sms-stat sms-stat--balance">
<h2>مانده جاری</h2>
<p><span>{{ formatNumber(stat(wallet.total).messages) }}</span> پیامک</p>
<p><span>{{ formatNumber(stat(wallet.total).parts) }}</span> پارت</p>
<p><span>{{ formatNumber(stat(wallet.total).costToman) }}</span> تومان</p>
</article>
<article class="sms-stat sms-stat--today">
<h2>امروز</h2>
<p><span>{{ formatNumber(stat(wallet.today).messages) }}</span> پیامک</p>
<p><span>{{ formatNumber(stat(wallet.today).parts) }}</span> پارت</p>
<p><span>{{ formatNumber(stat(wallet.today).costToman) }}</span> تومان</p>
</article>
<article class="sms-stat sms-stat--price">
<h2>قیمت هر پارت</h2>
<strong>{{ formatNumber(wallet.pricePerPart) }} تومان</strong>
</article>
<article class="sms-stat sms-stat--wallet">
<h2>موجودی کیف پول</h2>
<strong>{{ formatNumber(wallet.balanceToman) }} تومان</strong>
</article>
</section>
<section class="admin-card sms-manage">
<h2>مدیریت کیف پول</h2>
<div class="sms-manage__row">
<div class="sms-manage__tariff">
<span>تعرفه پیامک</span>
<strong>{{ formatNumber(wallet.pricePerPart) }}</strong>
</div>
<div class="sms-manage__price">
<AdminCurrencyInput v-model="pricePerPart" :allow-empty="false" />
<button
type="button"
class="admin-btn admin-btn--success"
:disabled="savingPrice || !pricePerPart"
@click="savePrice"
>
{{ savingPrice ? 'در حال ذخیره…' : 'ذخیره قیمت' }}
</button>
</div>
<div class="sms-manage__charge">
<AdminCurrencyInput v-model="chargeAmount" placeholder="مبلغ (تومان)" />
<input
v-model="chargeNote"
class="admin-input"
type="text"
placeholder="توضیح (اختیاری)"
/>
<button
type="button"
class="admin-btn admin-btn--success"
:disabled="charging || !chargeAmount"
@click="charge"
>
{{ charging ? 'در حال شارژ…' : 'شارژ کیف پول' }}
</button>
</div>
</div>
<p class="sms-manage__hint">هر پیامک ۶۷ کاراکتر است. پیام ۱۴۰ کاراکتری ۳ پارت محاسبه می‌شود.</p>
<div class="sms-block">
<div>
<strong>مسدودسازی ارسال پیامک</strong>
<p>در حالت مسدود، ارسال پیامک متوقف می‌شود.</p>
</div>
<div class="sms-block__switch">
<span>{{ wallet.blocked ? 'فعال' : 'خاموش' }}</span>
<BaseSwitch
:model-value="wallet.blocked"
:loading="toggling"
active-text="فعال"
inactive-text="خاموش"
@update:model-value="setBlocked"
/>
</div>
</div>
</section>
<section class="admin-card sms-deposit">
<h2>اطلاعات واریز شارژ</h2>
<p class="sms-deposit__hint">
لطفا مبلغ مورد نظر خود جهت شارژ سامانه پیامکی را به شماره کارت یا حساب زیر واریز نمایید و تصویر فیش پرداختی را برای پشتیبانی ارسال فرمایید.
</p>
<label class="admin-form__field">
<span>شماره کارت</span>
<input v-model="cardNumber" class="admin-input" dir="ltr" inputmode="numeric" />
</label>
<label class="admin-form__field">
<span>شماره شبا</span>
<input v-model="sheba" class="admin-input" dir="ltr" />
</label>
<label class="admin-form__field">
<span>به نام</span>
<input v-model="accountName" class="admin-input" />
</label>
<button
type="button"
class="admin-btn admin-btn--success sms-deposit__save"
:disabled="savingDeposit"
@click="saveDeposit"
>
{{ savingDeposit ? 'در حال ذخیره…' : 'ذخیره اطلاعات واریز' }}
</button>
</section>
<section class="admin-card sms-thresholds">
<h2>آستانه‌های رنگ موجودی</h2>
<div class="sms-thresholds__row">
<label class="admin-form__field">
<span>آستانه زرد</span>
<AdminCurrencyInput v-model="yellowThreshold" :allow-empty="false" />
</label>
<label class="admin-form__field">
<span>آستانه قرمز</span>
<AdminCurrencyInput v-model="redThreshold" :allow-empty="false" />
</label>
<button
type="button"
class="admin-btn admin-btn--success"
:disabled="savingThresholds"
@click="saveThresholds"
>
{{ savingThresholds ? 'در حال ذخیره…' : 'ذخیره آستانه‌ها' }}
</button>
</div>
</section>
</template>
</div>
</template>
<style scoped>
.sms-stats {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.sms-stat {
border-radius: 16px;
padding: 16px 18px;
min-height: 112px;
}
.sms-stat h2 {
margin: 0 0 10px;
font-size: 0.95rem;
}
.sms-stat p {
margin: 0;
color: var(--color-text-muted, #64748b);
font-size: 0.8125rem;
}
.sms-stat p span,
.sms-stat strong {
font-weight: 800;
}
.sms-stat--balance {
background: #f3e8ff;
color: #6d28d9;
}
.sms-stat--today {
background: #fff7ed;
color: #c2410c;
}
.sms-stat--price {
background: #eff6ff;
color: #1d4ed8;
}
.sms-stat--price strong,
.sms-stat--wallet strong {
display: block;
margin-top: 18px;
font-size: 1.35rem;
}
.sms-stat--wallet {
background: #fffbeb;
color: #b45309;
}
.sms-manage,
.sms-deposit,
.sms-thresholds {
margin-bottom: 16px;
}
.sms-manage h2,
.sms-deposit h2,
.sms-thresholds h2 {
margin: 0 0 14px;
font-size: 1rem;
}
.sms-manage__row,
.sms-thresholds__row {
display: flex;
flex-wrap: wrap;
align-items: end;
gap: 12px;
}
.sms-manage__tariff,
.sms-manage__price,
.sms-manage__charge {
display: flex;
align-items: center;
gap: 8px;
}
.sms-manage__price :deep(input),
.sms-manage__charge :deep(input),
.sms-manage__charge .admin-input,
.sms-thresholds__row :deep(input) {
width: 140px;
}
.sms-manage__tariff strong {
min-width: 72px;
text-align: center;
font-size: 1.25rem;
}
.sms-manage__charge {
margin-right: auto;
}
.sms-manage__hint,
.sms-deposit__hint {
margin: 12px 0 0;
color: var(--color-text-muted, #64748b);
font-size: 0.8125rem;
}
.sms-block {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-top: 14px;
padding: 12px 14px;
border-radius: 12px;
background: #ecfdf3;
}
.sms-block p {
margin: 4px 0 0;
color: #067647;
font-size: 0.75rem;
}
.sms-block__switch {
display: flex;
align-items: center;
gap: 8px;
color: #067647;
font-weight: 700;
}
.sms-deposit__save {
width: 100%;
margin-top: 8px;
}
.sms-thresholds__row .admin-btn {
margin-bottom: 2px;
}
.admin-form__field > span {
font-size: 0.8125rem;
font-weight: 600;
}
@media (max-width: 960px) {
.sms-stats {
grid-template-columns: 1fr 1fr;
}
.sms-manage__charge {
margin-right: 0;
}
}
</style>