changes 1

This commit is contained in:
2026-10-29 15:24:44 +03:30
parent 6a52b845fc
commit 400fecf271
48 changed files with 869 additions and 360 deletions
@@ -1 +1 @@
import{A as e,ct as t,et as n,g as r,l as i}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a}from"./http-CTjL79ty.js";import{B as o}from"./index-DWlOII4H.js";import{t as s}from"./BaseSwitch-Dz4ODUhf.js";function c(e=480){return new Promise(t=>{window.setTimeout(t,e)})}function l(e){let t=n(null),r=n(null),i=a();async function o(n,a){if(t.value)return a;let o=!a;if(!o&&e.confirmOff&&!await e.confirmOff())return a;t.value=n,r.value=null;let s=a;try{return await e.onPersist(o),i.success(e.successMessage?.(o)??(o?`وضعیت فعال شد.`:`وضعیت غیرفعال شد.`)),o}catch{return r.value=n,i.error(e.errorMessage??`به‌روزرسانی وضعیت انجام نشد.`),s}finally{t.value=null}}function s(e){return t.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(n,{emit:r}){let a=n,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":n.modelValue,loading:t(f)(n.id),error:t(p)(n.id),"onUpdate:modelValue":m},null,8,[`model-value`,`loading`,`error`]))}});export{u as t}; import{A as e,ct as t,et as n,g as r,l as i}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a}from"./http-CTjL79ty.js";import{B as o}from"./index-BWgYBNME.js";import{t as s}from"./BaseSwitch-Dz4ODUhf.js";function c(e=480){return new Promise(t=>{window.setTimeout(t,e)})}function l(e){let t=n(null),r=n(null),i=a();async function o(n,a){if(t.value)return a;let o=!a;if(!o&&e.confirmOff&&!await e.confirmOff())return a;t.value=n,r.value=null;let s=a;try{return await e.onPersist(o),i.success(e.successMessage?.(o)??(o?`وضعیت فعال شد.`:`وضعیت غیرفعال شد.`)),o}catch{return r.value=n,i.error(e.errorMessage??`به‌روزرسانی وضعیت انجام نشد.`),s}finally{t.value=null}}function s(e){return t.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(n,{emit:r}){let a=n,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":n.modelValue,loading:t(f)(n.id),error:t(p)(n.id),"onUpdate:modelValue":m},null,8,[`model-value`,`loading`,`error`]))}});export{u as t};
@@ -1 +1 @@
import{A as e,Ot as t,d as n,g as r,s as i,t as a}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{c as o,r as s}from"./index-DWlOII4H.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}; import{A as e,Ot as t,d as n,g as r,s as i,t as a}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{c as o,r as s}from"./index-BWgYBNME.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 +1 @@
import{A as e,At as t,N as n,O as r,Ot as i,P as a,V as o,W as s,a as c,c as l,d as u,et as d,g as f,h as p,jt as m,l as h,r as g,s as _,t as v,u as y,w as b}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{d as x,m as S,p as C}from"./http-CTjL79ty.js";import{L as w,M as T}from"./index-DWlOII4H.js";import{n as E,r as D,t as O}from"./selectOptionSearch-BUIO0UgP.js";var k=[`id`],A=[`aria-label`,`aria-expanded`],j={class:`admin-select__trigger-content`},ee={class:`admin-select__value`},te={class:`admin-select__chevron`,"aria-hidden":`true`},M=[`aria-label`],N={key:0,class:`admin-select__search`,role:`presentation`},P=[`placeholder`],F={key:1,class:`admin-select__empty`,role:`presentation`},I=[`aria-selected`,`onClick`,`onKeydown`],L={class:`admin-select__option-label`},R=v(f({__name:`AdminSelect`,props:{modelValue:{},options:{},placeholder:{default:`انتخاب کنید`},ariaLabel:{default:`انتخاب`},block:{type:Boolean,default:!1},searchable:{type:Boolean,default:!0},searchPlaceholder:{default:`جستجو…`},id:{default:void 0}},emits:[`update:modelValue`],setup(f,{emit:v}){let R=f,z=v,B=d(!1),V=d(``),H=d(null),U=d(null),W=d(null),G=d({});T(H,()=>{B.value=!1,V.value=``},{ignore:[U]});let K=_(()=>R.options.find(e=>e.value===R.modelValue)),q=_(()=>K.value?.label??R.placeholder),J=_(()=>O(R.options,V.value));function Y(e){e.key===`ArrowDown`?(e.preventDefault(),D(U.value,`first`)):e.key===`ArrowUp`&&(e.preventDefault(),D(U.value,`last`))}function X(e,t){let n=e.currentTarget;e.key===`ArrowDown`?(e.preventDefault(),E(n,1)):e.key===`ArrowUp`?(e.preventDefault(),E(n,-1)):(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),$(t))}function Z(){let e=H.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));G.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 Q(e){e?(window.addEventListener(`resize`,Z),window.addEventListener(`scroll`,Z,!0)):(window.removeEventListener(`resize`,Z),window.removeEventListener(`scroll`,Z,!0))}async function ne(){B.value=!B.value,B.value&&(V.value=``,await b(),Z(),R.searchable&&(await b(),W.value?.focus()))}function $(e){z(`update:modelValue`,e),B.value=!1,V.value=``}return o(B,e=>{Q(e),e&&b(()=>Z())}),r(()=>{Q(!1)}),(r,o)=>(e(),u(`div`,{id:f.id,ref_key:`rootRef`,ref:H,class:i([`admin-select`,{"admin-select--open":B.value,"admin-select--block":f.block}])},[l(`button`,{type:`button`,class:`admin-select__trigger`,"aria-label":f.ariaLabel,"aria-expanded":B.value,"aria-haspopup":`listbox`,onClick:ne},[l(`span`,j,[a(r.$slots,`option-leading`,{option:K.value},void 0,!0),l(`span`,ee,m(q.value),1)]),l(`span`,te,[p(w,{name:`chevron-down`,size:16})])],8,A),(e(),h(c,{to:`body`},[B.value?(e(),u(`ul`,{key:0,ref_key:`menuRef`,ref:U,class:`admin-select__menu`,"data-select-menu":``,role:`listbox`,"aria-label":f.ariaLabel,style:t(G.value)},[f.searchable?(e(),u(`li`,N,[s(l(`input`,{ref_key:`searchInputRef`,ref:W,"onUpdate:modelValue":o[0]||=e=>V.value=e,type:`search`,class:`admin-select__search-input`,placeholder:f.searchPlaceholder,onClick:o[1]||=S(()=>{},[`stop`]),onKeydown:[o[2]||=C(S(e=>B.value=!1,[`stop`]),[`esc`]),Y]},null,40,P),[[x,V.value]])])):y(``,!0),J.value.length?y(``,!0):(e(),u(`li`,F,` موردی یافت نشد `)),(e(!0),u(g,null,n(J.value,t=>(e(),u(`li`,{key:t.value,role:`presentation`},[l(`button`,{type:`button`,class:i([`admin-select__option`,{"admin-select__option--active":t.value===f.modelValue}]),"data-select-option":``,role:`option`,"aria-selected":t.value===f.modelValue,onClick:e=>$(t.value),onKeydown:e=>X(e,t.value)},[l(`span`,L,[a(r.$slots,`option-leading`,{option:t},void 0,!0),l(`span`,null,m(t.label),1)]),t.value===f.modelValue?(e(),h(w,{key:0,name:`check`,size:16,class:`admin-select__check`})):y(``,!0)],42,I)]))),128))],12,M)):y(``,!0)]))],10,k))}}),[[`__scopeId`,`data-v-4ee349a1`]]);export{R as t}; import{A as e,At as t,N as n,O as r,Ot as i,P as a,V as o,W as s,a as c,c as l,d as u,et as d,g as f,h as p,jt as m,l as h,r as g,s as _,t as v,u as y,w as b}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{d as x,m as S,p as C}from"./http-CTjL79ty.js";import{L as w,M as T}from"./index-BWgYBNME.js";import{n as E,r as D,t as O}from"./selectOptionSearch-DNMc9NzZ.js";var k=[`id`],A=[`aria-label`,`aria-expanded`],j={class:`admin-select__trigger-content`},ee={class:`admin-select__value`},te={class:`admin-select__chevron`,"aria-hidden":`true`},M=[`aria-label`],N={key:0,class:`admin-select__search`,role:`presentation`},P=[`placeholder`],F={key:1,class:`admin-select__empty`,role:`presentation`},I=[`aria-selected`,`onClick`,`onKeydown`],L={class:`admin-select__option-label`},R=v(f({__name:`AdminSelect`,props:{modelValue:{},options:{},placeholder:{default:`انتخاب کنید`},ariaLabel:{default:`انتخاب`},block:{type:Boolean,default:!1},searchable:{type:Boolean,default:!0},searchPlaceholder:{default:`جستجو…`},id:{default:void 0}},emits:[`update:modelValue`],setup(f,{emit:v}){let R=f,z=v,B=d(!1),V=d(``),H=d(null),U=d(null),W=d(null),G=d({});T(H,()=>{B.value=!1,V.value=``},{ignore:[U]});let K=_(()=>R.options.find(e=>e.value===R.modelValue)),q=_(()=>K.value?.label??R.placeholder),J=_(()=>O(R.options,V.value));function Y(e){e.key===`ArrowDown`?(e.preventDefault(),D(U.value,`first`)):e.key===`ArrowUp`&&(e.preventDefault(),D(U.value,`last`))}function X(e,t){let n=e.currentTarget;e.key===`ArrowDown`?(e.preventDefault(),E(n,1)):e.key===`ArrowUp`?(e.preventDefault(),E(n,-1)):(e.key===`Enter`||e.key===` `)&&(e.preventDefault(),$(t))}function Z(){let e=H.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));G.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 Q(e){e?(window.addEventListener(`resize`,Z),window.addEventListener(`scroll`,Z,!0)):(window.removeEventListener(`resize`,Z),window.removeEventListener(`scroll`,Z,!0))}async function ne(){B.value=!B.value,B.value&&(V.value=``,await b(),Z(),R.searchable&&(await b(),W.value?.focus()))}function $(e){z(`update:modelValue`,e),B.value=!1,V.value=``}return o(B,e=>{Q(e),e&&b(()=>Z())}),r(()=>{Q(!1)}),(r,o)=>(e(),u(`div`,{id:f.id,ref_key:`rootRef`,ref:H,class:i([`admin-select`,{"admin-select--open":B.value,"admin-select--block":f.block}])},[l(`button`,{type:`button`,class:`admin-select__trigger`,"aria-label":f.ariaLabel,"aria-expanded":B.value,"aria-haspopup":`listbox`,onClick:ne},[l(`span`,j,[a(r.$slots,`option-leading`,{option:K.value},void 0,!0),l(`span`,ee,m(q.value),1)]),l(`span`,te,[p(w,{name:`chevron-down`,size:16})])],8,A),(e(),h(c,{to:`body`},[B.value?(e(),u(`ul`,{key:0,ref_key:`menuRef`,ref:U,class:`admin-select__menu`,"data-select-menu":``,role:`listbox`,"aria-label":f.ariaLabel,style:t(G.value)},[f.searchable?(e(),u(`li`,N,[s(l(`input`,{ref_key:`searchInputRef`,ref:W,"onUpdate:modelValue":o[0]||=e=>V.value=e,type:`search`,class:`admin-select__search-input`,placeholder:f.searchPlaceholder,onClick:o[1]||=S(()=>{},[`stop`]),onKeydown:[o[2]||=C(S(e=>B.value=!1,[`stop`]),[`esc`]),Y]},null,40,P),[[x,V.value]])])):y(``,!0),J.value.length?y(``,!0):(e(),u(`li`,F,` موردی یافت نشد `)),(e(!0),u(g,null,n(J.value,t=>(e(),u(`li`,{key:t.value,role:`presentation`},[l(`button`,{type:`button`,class:i([`admin-select__option`,{"admin-select__option--active":t.value===f.modelValue}]),"data-select-option":``,role:`option`,"aria-selected":t.value===f.modelValue,onClick:e=>$(t.value),onKeydown:e=>X(e,t.value)},[l(`span`,L,[a(r.$slots,`option-leading`,{option:t},void 0,!0),l(`span`,null,m(t.label),1)]),t.value===f.modelValue?(e(),h(w,{key:0,name:`check`,size:16,class:`admin-select__check`})):y(``,!0)],42,I)]))),128))],12,M)):y(``,!0)]))],10,k))}}),[[`__scopeId`,`data-v-4ee349a1`]]);export{R as t};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{A as e,Ot as t,P as n,c as r,d as i,g as a,h as o,jt as s,t as c,u as l}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{L as u}from"./index-DWlOII4H.js";var d={class:`base-empty__icon`,"aria-hidden":`true`},f={class:`base-empty__title`},p={key:0,class:`base-empty__description`},m={key:1,class:`base-empty__actions`},h=c(a({__name:`BaseEmptyState`,props:{icon:{default:`file`},title:{},description:{},compact:{type:Boolean}},emits:[`action`,`secondary`],setup(a){return(c,h)=>(e(),i(`div`,{class:t([`base-empty`,{"base-empty--compact":a.compact}])},[r(`div`,d,[o(u,{name:a.icon,size:a.compact?28:36},null,8,[`name`,`size`])]),r(`h3`,f,s(a.title),1),a.description?(e(),i(`p`,p,s(a.description),1)):l(``,!0),c.$slots.default||c.$slots.secondary?(e(),i(`div`,m,[n(c.$slots,`default`,{},void 0,!0),n(c.$slots,`secondary`,{},void 0,!0)])):l(``,!0)],2))}}),[[`__scopeId`,`data-v-47ea81e4`]]);export{h as t}; import{A as e,Ot as t,P as n,c as r,d as i,g as a,h as o,jt as s,t as c,u as l}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{L as u}from"./index-BWgYBNME.js";var d={class:`base-empty__icon`,"aria-hidden":`true`},f={class:`base-empty__title`},p={key:0,class:`base-empty__description`},m={key:1,class:`base-empty__actions`},h=c(a({__name:`BaseEmptyState`,props:{icon:{default:`file`},title:{},description:{},compact:{type:Boolean}},emits:[`action`,`secondary`],setup(a){return(c,h)=>(e(),i(`div`,{class:t([`base-empty`,{"base-empty--compact":a.compact}])},[r(`div`,d,[o(u,{name:a.icon,size:a.compact?28:36},null,8,[`name`,`size`])]),r(`h3`,f,s(a.title),1),a.description?(e(),i(`p`,p,s(a.description),1)):l(``,!0),c.$slots.default||c.$slots.secondary?(e(),i(`div`,m,[n(c.$slots,`default`,{},void 0,!0),n(c.$slots,`secondary`,{},void 0,!0)])):l(``,!0)],2))}}),[[`__scopeId`,`data-v-47ea81e4`]]);export{h as t};
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
@@ -1 +0,0 @@
.checks-page__order-meta[data-v-af5e25db]{align-items:center;gap:8px;display:flex}.checks-page__stats[data-v-af5e25db]{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:14px;display:grid}.checks-page__stat[data-v-af5e25db]{border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);flex-direction:column;gap:2px;padding:10px 12px;display:flex}.checks-page__stat span[data-v-af5e25db]{color:var(--color-text-muted);font-size:.75rem}.checks-page__stat strong[data-v-af5e25db]{font-size:.875rem;font-weight:700}.checks-page__list[data-v-af5e25db]{overflow:hidden}.check-row[data-v-af5e25db]{border-bottom:1px solid var(--color-border-light);grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px 12px;padding:8px 12px;display:grid}.check-row[data-v-af5e25db]:last-child{border-bottom:0}.check-row.open[data-v-af5e25db]{background:color-mix(in srgb, var(--color-primary-soft) 55%, transparent)}.check-row__main[data-v-af5e25db]{min-width:0;color:inherit;text-align:start;cursor:pointer;background:0 0;border:0;align-items:center;gap:10px;padding:4px 0;display:flex}.check-row__chevron[data-v-af5e25db]{width:14px;color:var(--color-text-muted);flex-shrink:0;font-size:.75rem}.check-row__thumb[data-v-af5e25db]{border:1px solid var(--color-border);cursor:zoom-in;background:#111;border-radius:6px;flex-shrink:0;place-items:center;width:44px;height:32px;padding:0;display:grid;overflow:hidden}.check-row__thumb img[data-v-af5e25db]{object-fit:cover;width:100%;height:100%;display:block}.check-row__thumb--empty[data-v-af5e25db]{color:var(--color-text-muted);cursor:default;font-size:.625rem}.check-row__meta[data-v-af5e25db]{flex:1;gap:2px;min-width:0;display:grid}.check-row__meta strong[data-v-af5e25db]{font-size:.875rem;font-weight:700}.check-row__meta small[data-v-af5e25db]{color:var(--color-text-muted);white-space:nowrap;text-overflow:ellipsis;font-size:.75rem;overflow:hidden}.check-row__actions[data-v-af5e25db]{flex-shrink:0;gap:6px;display:flex}.check-row__panel[data-v-af5e25db]{border-top:1px dashed var(--color-border);flex-wrap:wrap;grid-column:1/-1;align-items:flex-start;gap:12px 16px;padding:8px 0 4px 24px;display:flex}.check-row__fields[data-v-af5e25db]{flex-wrap:wrap;flex:1;gap:8px;min-width:200px;display:flex}.check-row__field[data-v-af5e25db]{background:var(--color-border-light);border-radius:999px;align-items:baseline;gap:6px;padding:4px 8px;font-size:.75rem;display:inline-flex}.check-row__field span[data-v-af5e25db]{color:var(--color-text-muted)}.check-row__field strong[data-v-af5e25db]{font-weight:600}.check-row__photos[data-v-af5e25db]{flex-shrink:0;gap:8px;display:flex}.check-row__photo[data-v-af5e25db]{border:1px solid var(--color-border);width:96px;color:inherit;cursor:zoom-in;text-align:center;background:0 0;border-radius:8px;padding:0;overflow:hidden}.check-row__photo img[data-v-af5e25db]{object-fit:cover;background:#111;width:100%;height:60px;display:block}.check-row__photo span[data-v-af5e25db]{color:var(--color-text-muted);padding:4px;font-size:.6875rem;display:block}.check-row__hint[data-v-af5e25db]{color:var(--color-text-muted);margin:0;font-size:.75rem}.checks-page__footer[data-v-af5e25db]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;margin-top:14px;display:flex}.checks-page__note[data-v-af5e25db]{color:var(--color-text-muted);margin:0;font-size:.75rem}.checks-page__history[data-v-af5e25db]{gap:8px;display:grid}.checks-page__history-item[data-v-af5e25db]{gap:6px;padding:12px;display:grid}.checks-page__history-item-head[data-v-af5e25db]{flex-wrap:wrap;justify-content:space-between;gap:12px;font-size:.875rem;display:flex}.checks-page__history-meta[data-v-af5e25db],.checks-page__history-note[data-v-af5e25db]{margin:0;font-size:.8125rem;line-height:1.5}.checks-page__history-meta[data-v-af5e25db]{color:var(--color-text-muted)}.checks-page__lightbox[data-v-af5e25db]{z-index:300000;background:#000000d6;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.checks-page__lightbox img[data-v-af5e25db]{object-fit:contain;border-radius:8px;max-width:min(900px,100%);max-height:88vh}.checks-page__lightbox-close[data-v-af5e25db]{top:14px;cursor:pointer;border:0;border-radius:8px;padding:8px 12px;position:absolute;inset-inline-end:14px}@media (width<=800px){.checks-page__stats[data-v-af5e25db]{grid-template-columns:repeat(2,minmax(0,1fr))}.check-row[data-v-af5e25db]{grid-template-columns:1fr}.check-row__actions[data-v-af5e25db]{padding-inline-start:24px}.check-row__panel[data-v-af5e25db]{padding-inline-start:0}}
+1
View File
@@ -0,0 +1 @@
.checks-page__order-meta[data-v-6ad55e99]{align-items:center;gap:8px;display:flex}.checks-page__stats[data-v-6ad55e99]{grid-template-columns:repeat(4,minmax(0,1fr));gap:8px;margin-bottom:14px;display:grid}.checks-page__stat[data-v-6ad55e99]{border:1px solid var(--color-border);border-radius:var(--radius-md);background:var(--color-surface);flex-direction:column;gap:2px;padding:10px 12px;display:flex}.checks-page__stat span[data-v-6ad55e99]{color:var(--color-text-muted);font-size:.75rem}.checks-page__stat strong[data-v-6ad55e99]{font-size:.875rem;font-weight:700}.checks-page__list[data-v-6ad55e99]{overflow:hidden}.check-row[data-v-6ad55e99]{border-bottom:1px solid var(--color-border-light);grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px 12px;padding:8px 12px;display:grid}.check-row[data-v-6ad55e99]:last-child{border-bottom:0}.check-row.open[data-v-6ad55e99]{background:color-mix(in srgb, var(--color-primary-soft) 55%, transparent)}.check-row__main[data-v-6ad55e99]{min-width:0;color:inherit;text-align:start;cursor:pointer;background:0 0;border:0;align-items:center;gap:10px;padding:4px 0;display:flex}.check-row__chevron[data-v-6ad55e99]{width:14px;color:var(--color-text-muted);flex-shrink:0;font-size:.75rem}.check-row__thumb[data-v-6ad55e99]{border:1px solid var(--color-border);cursor:zoom-in;background:#111;border-radius:6px;flex-shrink:0;place-items:center;width:44px;height:32px;padding:0;display:grid;overflow:hidden}.check-row__thumb img[data-v-6ad55e99]{object-fit:cover;width:100%;height:100%;display:block}.check-row__thumb--empty[data-v-6ad55e99]{color:var(--color-text-muted);cursor:default;font-size:.625rem}.check-row__meta[data-v-6ad55e99]{flex:1;gap:2px;min-width:0;display:grid}.check-row__meta strong[data-v-6ad55e99]{font-size:.875rem;font-weight:700}.check-row__meta small[data-v-6ad55e99]{color:var(--color-text-muted);white-space:nowrap;text-overflow:ellipsis;font-size:.75rem;overflow:hidden}.check-row__actions[data-v-6ad55e99]{flex-shrink:0;gap:6px;display:flex}.check-row__panel[data-v-6ad55e99]{border-top:1px dashed var(--color-border);flex-wrap:wrap;grid-column:1/-1;align-items:flex-start;gap:12px 16px;padding:8px 0 4px 24px;display:flex}.check-row__fields[data-v-6ad55e99]{flex-wrap:wrap;flex:1;gap:8px;min-width:200px;display:flex}.check-row__field[data-v-6ad55e99]{background:var(--color-border-light);border-radius:999px;align-items:baseline;gap:6px;padding:4px 8px;font-size:.75rem;display:inline-flex}.check-row__field span[data-v-6ad55e99]{color:var(--color-text-muted)}.check-row__field strong[data-v-6ad55e99]{font-weight:600}.check-row__photos[data-v-6ad55e99]{flex-shrink:0;gap:8px;display:flex}.check-row__photo[data-v-6ad55e99]{border:1px solid var(--color-border);width:96px;color:inherit;cursor:zoom-in;text-align:center;background:0 0;border-radius:8px;padding:0;overflow:hidden}.check-row__photo img[data-v-6ad55e99]{object-fit:cover;background:#111;width:100%;height:60px;display:block}.check-row__photo span[data-v-6ad55e99]{color:var(--color-text-muted);padding:4px;font-size:.6875rem;display:block}.check-row__hint[data-v-6ad55e99]{color:var(--color-text-muted);margin:0;font-size:.75rem}.checks-page__footer[data-v-6ad55e99]{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px;margin-top:14px;display:flex}.checks-page__note[data-v-6ad55e99]{color:var(--color-text-muted);margin:0;font-size:.75rem}.checks-page__history[data-v-6ad55e99]{gap:8px;display:grid}.checks-page__history-item[data-v-6ad55e99]{gap:6px;padding:12px;display:grid}.checks-page__history-item-head[data-v-6ad55e99]{flex-wrap:wrap;justify-content:space-between;gap:12px;font-size:.875rem;display:flex}.checks-page__history-meta[data-v-6ad55e99],.checks-page__history-note[data-v-6ad55e99]{margin:0;font-size:.8125rem;line-height:1.5}.checks-page__history-meta[data-v-6ad55e99]{color:var(--color-text-muted)}.checks-page__lightbox[data-v-6ad55e99]{z-index:300000;background:#000000d6;place-items:center;padding:20px;display:grid;position:fixed;inset:0}.checks-page__lightbox img[data-v-6ad55e99]{object-fit:contain;border-radius:8px;max-width:min(900px,100%);max-height:88vh}.checks-page__lightbox-close[data-v-6ad55e99]{top:14px;cursor:pointer;border:0;border-radius:8px;padding:8px 12px;position:absolute;inset-inline-end:14px}@media (width<=800px){.checks-page__stats[data-v-6ad55e99]{grid-template-columns:repeat(2,minmax(0,1fr))}.check-row[data-v-6ad55e99]{grid-template-columns:1fr}.check-row__actions[data-v-6ad55e99]{padding-inline-start:24px}.check-row__panel[data-v-6ad55e99]{padding-inline-start:0}}
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
@@ -1 +0,0 @@
.checks-list-page__summary[data-v-09633f1f]{color:var(--color-text-muted);flex-direction:column;align-items:flex-end;gap:4px;font-size:.8125rem;display:flex}.checks-list-page__filters[data-v-09633f1f]{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:16px;padding:12px 14px;display:flex}.checks-list-page__search[data-v-09633f1f]{border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-border-light);min-width:220px;color:var(--color-text-muted);flex:1;align-items:center;gap:10px;padding:8px 12px;display:flex}.checks-list-page__search-input[data-v-09633f1f]{color:var(--color-text);background:0 0;border:none;outline:none;flex:1;font-size:.875rem}.checks-list-page__status-select[data-v-09633f1f]{flex:0 220px;width:220px}.checks-list-page__table[data-v-09633f1f]{padding:0;overflow:hidden}.checks-list-page__order-link[data-v-09633f1f]{color:var(--color-primary);cursor:pointer;font:inherit;background:0 0;border:none;padding:0}.checks-list-page__order-link[data-v-09633f1f]:hover{text-decoration:underline}.checks-list-page__empty[data-v-09633f1f]{text-align:center;color:var(--color-text-muted);margin:0;padding:24px;font-size:.875rem}
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,3 +1,3 @@
import{A as e,D as t,N as n,Q as r,U as i,W as a,c as o,d as s,et as c,g as l,h as u,jt as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a as _,d as v,t as y}from"./http-CTjL79ty.js";import{S as b,V as x}from"./index-DWlOII4H.js";import{t as S}from"./AdminPageSkeleton-DacAHji8.js";import{t as C}from"./BaseEmptyState-B6cM4bRM.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=c([]),N=c(!0),P=c(!1),F=c(null),I=_(),L=r({code:``,label:``,sortOrder:0,benefitsText:``}),R=h(()=>F.value?`ویرایش سطح`:`سطح جدید`);function z(e){return e.split(` import{A as e,D as t,N as n,Q as r,U as i,W as a,c as o,d as s,et as c,g as l,h as u,jt as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a as _,d as v,t as y}from"./http-CTjL79ty.js";import{S as b,V as x}from"./index-BWgYBNME.js";import{t as S}from"./AdminPageSkeleton-DacAHji8.js";import{t as C}from"./BaseEmptyState-88fQ29rO.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=c([]),N=c(!0),P=c(!1),F=c(null),I=_(),L=r({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 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(` `).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 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 b.update(F.value,e):await b.create(e),P.value=!1,I.success(`ذخیره شد`),await B()}catch(e){I.error(e instanceof y?e.message:`ذخیره ناموفق بود`)}}return t(()=>{B()}),(t,r)=>(e(),s(`div`,w,[o(`header`,{class:`admin-page__head`},[r[5]||=o(`div`,null,[o(`h1`,null,`سطوح و مزایای مشتری`),o(`p`,null,`تعریف Level داخلی CRM و مزایای هر سطح (نمایش فقط در ادمین)`)],-1),o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:V},`سطح جدید`)]),N.value?(e(),f(S,{key:0,variant:`table`})):g.value.length?(e(),s(`table`,T,[r[6]||=o(`thead`,null,[o(`tr`,null,[o(`th`,null,`کد`),o(`th`,null,`عنوان`),o(`th`,null,`مزایا`),o(`th`,null,`مشتریان`),o(`th`)])],-1),o(`tbody`,null,[(e(!0),s(m,null,n(g.value,t=>(e(),s(`tr`,{key:t.id},[o(`td`,E,d(t.code),1),o(`td`,null,d(t.label),1),o(`td`,null,d(t.benefits.map(e=>e.title).join(`، `)||`—`),1),o(`td`,null,d(t.customersCount),1),o(`td`,null,[o(`button`,{type:`button`,class:`link`,onClick:e=>H(t)},`ویرایش`,8,D)])]))),128))])])):(e(),f(C,{key:1,title:`سطحی تعریف نشده`})),u(x,{open:P.value,title:R.value,onClose:r[4]||=e=>P.value=!1},{footer:i(()=>[o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!L.code.trim()||!L.label.trim(),onClick:U},` ذخیره `,8,M)]),default:i(()=>[o(`label`,O,[r[7]||=p(`کد`,-1),a(o(`input`,{"onUpdate:modelValue":r[0]||=e=>L.code=e,class:`admin-input`,dir:`ltr`},null,512),[[v,L.code]])]),o(`label`,k,[r[8]||=p(`عنوان`,-1),a(o(`input`,{"onUpdate:modelValue":r[1]||=e=>L.label=e,class:`admin-input`},null,512),[[v,L.label]])]),o(`label`,A,[r[9]||=p(`ترتیب`,-1),a(o(`input`,{"onUpdate:modelValue":r[2]||=e=>L.sortOrder=e,type:`number`,class:`admin-input`},null,512),[[v,L.sortOrder,void 0,{number:!0}]])]),o(`label`,j,[r[10]||=p(` مزایا (هر خط یک مورد) `,-1),a(o(`textarea`,{"onUpdate:modelValue":r[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}; `)}),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 y?e.message:`ذخیره ناموفق بود`)}}return t(()=>{B()}),(t,r)=>(e(),s(`div`,w,[o(`header`,{class:`admin-page__head`},[r[5]||=o(`div`,null,[o(`h1`,null,`سطوح و مزایای مشتری`),o(`p`,null,`تعریف Level داخلی CRM و مزایای هر سطح (نمایش فقط در ادمین)`)],-1),o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:V},`سطح جدید`)]),N.value?(e(),f(S,{key:0,variant:`table`})):g.value.length?(e(),s(`table`,T,[r[6]||=o(`thead`,null,[o(`tr`,null,[o(`th`,null,`کد`),o(`th`,null,`عنوان`),o(`th`,null,`مزایا`),o(`th`,null,`مشتریان`),o(`th`)])],-1),o(`tbody`,null,[(e(!0),s(m,null,n(g.value,t=>(e(),s(`tr`,{key:t.id},[o(`td`,E,d(t.code),1),o(`td`,null,d(t.label),1),o(`td`,null,d(t.benefits.map(e=>e.title).join(`، `)||`—`),1),o(`td`,null,d(t.customersCount),1),o(`td`,null,[o(`button`,{type:`button`,class:`link`,onClick:e=>H(t)},`ویرایش`,8,D)])]))),128))])])):(e(),f(C,{key:1,title:`سطحی تعریف نشده`})),u(x,{open:P.value,title:R.value,onClose:r[4]||=e=>P.value=!1},{footer:i(()=>[o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!L.code.trim()||!L.label.trim(),onClick:U},` ذخیره `,8,M)]),default:i(()=>[o(`label`,O,[r[7]||=p(`کد`,-1),a(o(`input`,{"onUpdate:modelValue":r[0]||=e=>L.code=e,class:`admin-input`,dir:`ltr`},null,512),[[v,L.code]])]),o(`label`,k,[r[8]||=p(`عنوان`,-1),a(o(`input`,{"onUpdate:modelValue":r[1]||=e=>L.label=e,class:`admin-input`},null,512),[[v,L.label]])]),o(`label`,A,[r[9]||=p(`ترتیب`,-1),a(o(`input`,{"onUpdate:modelValue":r[2]||=e=>L.sortOrder=e,type:`number`,class:`admin-input`},null,512),[[v,L.sortOrder,void 0,{number:!0}]])]),o(`label`,j,[r[10]||=p(` مزایا (هر خط یک مورد) `,-1),a(o(`textarea`,{"onUpdate:modelValue":r[3]||=e=>L.benefitsText=e,class:`admin-input`,rows:`4`},null,512),[[v,L.benefitsText]])])]),_:1},8,[`open`,`title`])]))}}),[[`__scopeId`,`data-v-44c7f808`]]);export{N as default};
@@ -1 +1 @@
import{A as e,W as t,c as n,ct as r,d as i,et as a,g as o,h as s,jt as c,m as l,s as u,t as d,u as f}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a as p,d as m,l as h,m as g,u as _}from"./http-CTjL79ty.js";import{F as v,I as y,L as b,P as x,R as S,z as C}from"./index-DWlOII4H.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(o({__name:`LoginView`,setup(o){let d=C(),U=S(),W=x(),G=p(),K=a(y()?v.phone:``),q=a(y()?v.password:``),J=a(!0),Y=a(!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(a,o)=>(e(),i(`div`,w,[n(`div`,T,[n(`div`,E,[n(`div`,D,[o[8]||=n(`h2`,{class:`login-page__title`},`ورود به پنل مدیریت`,-1),o[9]||=n(`p`,{class:`login-page__subtitle`},`برای ادامه، اطلاعات حساب کاربری خود را وارد کنید.`,-1),r(X)?(e(),i(`p`,O,[o[6]||=l(` حالت موک فعال است — `,-1),n(`span`,k,c(r(v).phone),1),o[7]||=l(` / `,-1),n(`span`,A,c(r(v).password),1)])):f(``,!0)]),n(`form`,{class:`admin-form login-page__form`,onSubmit:g($,[`prevent`])},[r(W).error?(e(),i(`p`,j,c(r(W).error),1)):f(``,!0),n(`div`,M,[o[10]||=n(`label`,{for:`login-phone`},`شماره موبایل`,-1),n(`div`,N,[n(`span`,P,[s(b,{name:`phone`,size:18})]),t(n(`input`,{id:`login-phone`,"onUpdate:modelValue":o[0]||=e=>K.value=e,type:`tel`,class:`admin-input login-page__input`,placeholder:`۰۹۱۲۱۱۱۱۲۲۲۲`,autocomplete:`username`,inputmode:`tel`,dir:`ltr`,onInput:o[1]||=e=>r(W).clearError()},null,544),[[m,K.value]])])]),n(`div`,F,[o[11]||=n(`label`,{for:`login-password`},`رمز عبور`,-1),n(`div`,I,[n(`span`,L,[s(b,{name:`lock`,size:18})]),t(n(`input`,{id:`login-password`,"onUpdate:modelValue":o[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:o[3]||=e=>r(W).clearError()},null,40,R),[[_,q.value]]),n(`button`,{type:`button`,class:`login-page__toggle-password`,"aria-label":Y.value?`پنهان کردن رمز`:`نمایش رمز`,onClick:o[4]||=e=>Y.value=!Y.value},[s(b,{name:Y.value?`eye-off`:`eye`,size:18},null,8,[`name`])],8,z)])]),n(`label`,B,[t(n(`input`,{"onUpdate:modelValue":o[5]||=e=>J.value=e,type:`checkbox`},null,512),[[h,J.value]]),o[12]||=n(`span`,null,`مرا به خاطر بسپار`,-1)]),n(`button`,{type:`submit`,class:`admin-btn admin-btn--primary login-page__submit`,disabled:!Z.value||r(W).loading},[r(W).loading?(e(),i(`span`,H)):f(``,!0),o[13]||=l(` ورود به پنل `,-1)],8,V)],32)])]),n(`div`,{class:`login-page__brand`},[n(`div`,{class:`login-page__brand-inner`},[n(`img`,{src:`/admin/brand-logo.png`,alt:`نادمی تکس`,class:`login-page__brand-logo`}),o[14]||=n(`p`,{class:`login-page__brand-badge`},`پارچه`,-1),o[15]||=n(`h1`,{class:`login-page__brand-title`},`پنل مدیریت سفارش‌های عمده`,-1),o[16]||=n(`p`,{class:`login-page__brand-text`},` مدیریت سفارش‌ها، مشتریان، قیمت‌گذاری، چک‌ها و گزارش‌های فروش در یک محیط یکپارچه. `,-1)])])]))}}),[[`__scopeId`,`data-v-e708878f`]]);export{U as default}; import{A as e,W as t,c as n,ct as r,d as i,et as a,g as o,h as s,jt as c,m as l,s as u,t as d,u as f}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a as p,d as m,l as h,m as g,u as _}from"./http-CTjL79ty.js";import{F as v,I as y,L as b,P as x,R as S,z as C}from"./index-BWgYBNME.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(o({__name:`LoginView`,setup(o){let d=C(),U=S(),W=x(),G=p(),K=a(y()?v.phone:``),q=a(y()?v.password:``),J=a(!0),Y=a(!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(a,o)=>(e(),i(`div`,w,[n(`div`,T,[n(`div`,E,[n(`div`,D,[o[8]||=n(`h2`,{class:`login-page__title`},`ورود به پنل مدیریت`,-1),o[9]||=n(`p`,{class:`login-page__subtitle`},`برای ادامه، اطلاعات حساب کاربری خود را وارد کنید.`,-1),r(X)?(e(),i(`p`,O,[o[6]||=l(` حالت موک فعال است — `,-1),n(`span`,k,c(r(v).phone),1),o[7]||=l(` / `,-1),n(`span`,A,c(r(v).password),1)])):f(``,!0)]),n(`form`,{class:`admin-form login-page__form`,onSubmit:g($,[`prevent`])},[r(W).error?(e(),i(`p`,j,c(r(W).error),1)):f(``,!0),n(`div`,M,[o[10]||=n(`label`,{for:`login-phone`},`شماره موبایل`,-1),n(`div`,N,[n(`span`,P,[s(b,{name:`phone`,size:18})]),t(n(`input`,{id:`login-phone`,"onUpdate:modelValue":o[0]||=e=>K.value=e,type:`tel`,class:`admin-input login-page__input`,placeholder:`۰۹۱۲۱۱۱۱۲۲۲۲`,autocomplete:`username`,inputmode:`tel`,dir:`ltr`,onInput:o[1]||=e=>r(W).clearError()},null,544),[[m,K.value]])])]),n(`div`,F,[o[11]||=n(`label`,{for:`login-password`},`رمز عبور`,-1),n(`div`,I,[n(`span`,L,[s(b,{name:`lock`,size:18})]),t(n(`input`,{id:`login-password`,"onUpdate:modelValue":o[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:o[3]||=e=>r(W).clearError()},null,40,R),[[_,q.value]]),n(`button`,{type:`button`,class:`login-page__toggle-password`,"aria-label":Y.value?`پنهان کردن رمز`:`نمایش رمز`,onClick:o[4]||=e=>Y.value=!Y.value},[s(b,{name:Y.value?`eye-off`:`eye`,size:18},null,8,[`name`])],8,z)])]),n(`label`,B,[t(n(`input`,{"onUpdate:modelValue":o[5]||=e=>J.value=e,type:`checkbox`},null,512),[[h,J.value]]),o[12]||=n(`span`,null,`مرا به خاطر بسپار`,-1)]),n(`button`,{type:`submit`,class:`admin-btn admin-btn--primary login-page__submit`,disabled:!Z.value||r(W).loading},[r(W).loading?(e(),i(`span`,H)):f(``,!0),o[13]||=l(` ورود به پنل `,-1)],8,V)],32)])]),n(`div`,{class:`login-page__brand`},[n(`div`,{class:`login-page__brand-inner`},[n(`img`,{src:`/admin/brand-logo.png`,alt:`نادمی تکس`,class:`login-page__brand-logo`}),o[14]||=n(`p`,{class:`login-page__brand-badge`},`پارچه`,-1),o[15]||=n(`h1`,{class:`login-page__brand-title`},`پنل مدیریت سفارش‌های عمده`,-1),o[16]||=n(`p`,{class:`login-page__brand-text`},` مدیریت سفارش‌ها، مشتریان، قیمت‌گذاری، چک‌ها و گزارش‌های فروش در یک محیط یکپارچه. `,-1)])])]))}}),[[`__scopeId`,`data-v-e708878f`]]);export{U as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.products-page__filters[data-v-87b603ef]{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:16px;padding:12px 14px;display:flex}.products-page__search[data-v-87b603ef]{min-width:200px;color:var(--color-text-muted);flex:1;align-items:center;gap:10px;display:flex}.products-page__search-input[data-v-87b603ef]{background:0 0;border:none;outline:none;flex:1;font-size:.875rem}.products-page__catalog-filter[data-v-87b603ef]{width:min(260px,100%)}.admin-form__hint[data-v-87b603ef]{color:var(--color-text-muted);margin:6px 0 0;font-size:.75rem;line-height:1.5}.products-page__table[data-v-87b603ef]{overflow:hidden}.product-thumb[data-v-87b603ef]{background:#e8eceb 50%/cover no-repeat;border-radius:8px;width:44px;height:36px;display:block}.product-thumb--empty[data-v-87b603ef]{background:#eef1f0}.color-variants__hint[data-v-87b603ef]{color:var(--color-text-muted);margin:0 0 10px;font-size:.75rem;line-height:1.6}.general-images[data-v-87b603ef],.color-variants[data-v-87b603ef]{flex-direction:column;gap:10px;display:flex}.product-media-row[data-v-87b603ef]{border:1px solid var(--color-border);background:var(--color-elevated,#fff);border-radius:14px;grid-template-columns:1fr auto;align-items:center;gap:12px 14px;padding:12px 14px;display:grid;box-shadow:0 1px 2px #0f172a0a}.product-media-row--color[data-v-87b603ef]{grid-template-columns:auto 1fr auto;align-items:center}.product-media-row[data-v-87b603ef] .image-picker{min-width:0}.product-media-row__input[data-v-87b603ef]{min-width:0;height:44px}.product-media-row__delete[data-v-87b603ef]{width:40px;height:40px;color:var(--color-text-muted);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:12px;flex-shrink:0;justify-content:center;align-items:center;padding:0;transition:color .15s,background .15s,border-color .15s;display:inline-flex}.product-media-row__delete[data-v-87b603ef]:hover{color:#c53030;background:#c5303014;border-color:#c530302e}.product-media-row__delete[data-v-87b603ef]:focus-visible{outline:2px solid var(--color-primary,#2d6a4f);outline-offset:2px}.product-media-add[data-v-87b603ef]{justify-content:center;align-self:stretch}@media (width<=520px){.product-media-row--color[data-v-87b603ef]{grid-template-rows:auto auto;grid-template-columns:1fr auto}.product-media-row--color[data-v-87b603ef] .image-picker{grid-column:1/-1}.product-media-row--color .product-media-row__delete[data-v-87b603ef]{grid-area:2/2}.product-media-row--color .product-media-row__input[data-v-87b603ef]{grid-area:2/1}}
-1
View File
@@ -1 +0,0 @@
.products-page__filters[data-v-83a6f3f4]{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:16px;padding:12px 14px;display:flex}.products-page__search[data-v-83a6f3f4]{min-width:200px;color:var(--color-text-muted);flex:1;align-items:center;gap:10px;display:flex}.products-page__search-input[data-v-83a6f3f4]{background:0 0;border:none;outline:none;flex:1;font-size:.875rem}.products-page__catalog-filter[data-v-83a6f3f4]{width:min(260px,100%)}.admin-form__hint[data-v-83a6f3f4]{color:var(--color-text-muted);margin:6px 0 0;font-size:.75rem;line-height:1.5}.products-page__table[data-v-83a6f3f4]{overflow:hidden}.product-thumb[data-v-83a6f3f4]{background:#e8eceb 50%/cover no-repeat;border-radius:8px;width:44px;height:36px;display:block}.product-thumb--empty[data-v-83a6f3f4]{background:#eef1f0}.color-variants__hint[data-v-83a6f3f4]{color:var(--color-text-muted);margin:0 0 10px;font-size:.75rem;line-height:1.6}.general-images[data-v-83a6f3f4],.color-variants[data-v-83a6f3f4]{flex-direction:column;gap:10px;display:flex}.product-media-row[data-v-83a6f3f4]{border:1px solid var(--color-border);background:var(--color-elevated,#fff);border-radius:14px;grid-template-columns:1fr auto;align-items:center;gap:12px 14px;padding:12px 14px;display:grid;box-shadow:0 1px 2px #0f172a0a}.product-media-row--color[data-v-83a6f3f4]{grid-template-columns:auto 1fr auto;align-items:center}.product-media-row[data-v-83a6f3f4] .image-picker{min-width:0}.product-media-row__input[data-v-83a6f3f4]{min-width:0;height:44px}.product-media-row__delete[data-v-83a6f3f4]{width:40px;height:40px;color:var(--color-text-muted);cursor:pointer;background:0 0;border:1px solid #0000;border-radius:12px;flex-shrink:0;justify-content:center;align-items:center;padding:0;transition:color .15s,background .15s,border-color .15s;display:inline-flex}.product-media-row__delete[data-v-83a6f3f4]:hover{color:#c53030;background:#c5303014;border-color:#c530302e}.product-media-row__delete[data-v-83a6f3f4]:focus-visible{outline:2px solid var(--color-primary,#2d6a4f);outline-offset:2px}.product-media-add[data-v-83a6f3f4]{justify-content:center;align-self:stretch}@media (width<=520px){.product-media-row--color[data-v-83a6f3f4]{grid-template-rows:auto auto;grid-template-columns:1fr auto}.product-media-row--color[data-v-83a6f3f4] .image-picker{grid-column:1/-1}.product-media-row--color .product-media-row__delete[data-v-83a6f3f4]{grid-area:2/2}.product-media-row--color .product-media-row__input[data-v-83a6f3f4]{grid-area:2/1}}
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 @@
import{A as e,D as t,N as n,Q as r,U as i,W as a,c as o,d as s,et as c,g as l,h as u,jt as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a as _,d as v,t as y}from"./http-CTjL79ty.js";import{E as b,V as x,j as S,y as C}from"./index-DWlOII4H.js";import{t as w}from"./AdminPageSkeleton-DacAHji8.js";import{t as T}from"./BaseEmptyState-B6cM4bRM.js";import{t as E}from"./AdminSelect-CWMyO8tk.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=c([]),I=c(!0),L=c([]),R=c([]),z=c(!1),B=_(),V=r({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 t(()=>{W()}),(t,r)=>(e(),s(`div`,D,[o(`header`,O,[r[5]||=o(`div`,null,[o(`h1`,null,`پیشنهاد محصول`),o(`p`,null,`پیشنهاد دستی محصول به مشتری`)],-1),o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:r[0]||=e=>z.value=!0},`پیشنهاد جدید`)]),I.value?(e(),f(w,{key:0,variant:`table`})):g.value.length?(e(),s(`table`,k,[r[6]||=o(`thead`,null,[o(`tr`,null,[o(`th`,null,`مشتری`),o(`th`,null,`محصول`),o(`th`,null,`یادداشت`),o(`th`,null,`وضعیت`),o(`th`)])],-1),o(`tbody`,null,[(e(!0),s(m,null,n(g.value,t=>(e(),s(`tr`,{key:t.id},[o(`td`,null,d(t.customerName),1),o(`td`,null,[p(d(t.productName)+` `,1),o(`span`,A,`(`+d(t.productCode)+`)`,1)]),o(`td`,null,d(t.note||`—`),1),o(`td`,null,d(t.isActive?`فعال`:`غیرفعال`),1),o(`td`,null,[o(`button`,{type:`button`,class:`link`,onClick:e=>K(t)},d(t.isActive?`غیرفعال`:`فعال`),9,j)])]))),128))])])):(e(),f(T,{key:1,title:`پیشنهادی ثبت نشده`})),u(x,{open:z.value,title:`پیشنهاد محصول`,onClose:r[4]||=e=>z.value=!1},{footer:i(()=>[o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!V.customerId||!V.productId,onClick:G},` ثبت `,8,F)]),default:i(()=>[o(`label`,M,[r[7]||=p(`مشتری `,-1),u(E,{modelValue:V.customerId,"onUpdate:modelValue":r[1]||=e=>V.customerId=e,options:H.value},null,8,[`modelValue`,`options`])]),o(`label`,N,[r[8]||=p(`محصول `,-1),u(E,{modelValue:V.productId,"onUpdate:modelValue":r[2]||=e=>V.productId=e,options:U.value},null,8,[`modelValue`,`options`])]),o(`label`,P,[r[9]||=p(`یادداشت`,-1),a(o(`textarea`,{"onUpdate:modelValue":r[3]||=e=>V.note=e,class:`admin-input`,rows:`3`},null,512),[[v,V.note]])])]),_:1},8,[`open`])]))}}),[[`__scopeId`,`data-v-56e3e728`]]);export{I as default}; import{A as e,D as t,N as n,Q as r,U as i,W as a,c as o,d as s,et as c,g as l,h as u,jt as d,l as f,m as p,r as m,s as h,t as g}from"./_plugin-vue_export-helper-vx-p5QAT.js";import{a as _,d as v,t as y}from"./http-CTjL79ty.js";import{E as b,V as x,j as S,y as C}from"./index-BWgYBNME.js";import{t as w}from"./AdminPageSkeleton-DacAHji8.js";import{t as T}from"./BaseEmptyState-88fQ29rO.js";import{t as E}from"./AdminSelect-hgn_nYC2.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=c([]),I=c(!0),L=c([]),R=c([]),z=c(!1),B=_(),V=r({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 t(()=>{W()}),(t,r)=>(e(),s(`div`,D,[o(`header`,O,[r[5]||=o(`div`,null,[o(`h1`,null,`پیشنهاد محصول`),o(`p`,null,`پیشنهاد دستی محصول به مشتری`)],-1),o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,onClick:r[0]||=e=>z.value=!0},`پیشنهاد جدید`)]),I.value?(e(),f(w,{key:0,variant:`table`})):g.value.length?(e(),s(`table`,k,[r[6]||=o(`thead`,null,[o(`tr`,null,[o(`th`,null,`مشتری`),o(`th`,null,`محصول`),o(`th`,null,`یادداشت`),o(`th`,null,`وضعیت`),o(`th`)])],-1),o(`tbody`,null,[(e(!0),s(m,null,n(g.value,t=>(e(),s(`tr`,{key:t.id},[o(`td`,null,d(t.customerName),1),o(`td`,null,[p(d(t.productName)+` `,1),o(`span`,A,`(`+d(t.productCode)+`)`,1)]),o(`td`,null,d(t.note||`—`),1),o(`td`,null,d(t.isActive?`فعال`:`غیرفعال`),1),o(`td`,null,[o(`button`,{type:`button`,class:`link`,onClick:e=>K(t)},d(t.isActive?`غیرفعال`:`فعال`),9,j)])]))),128))])])):(e(),f(T,{key:1,title:`پیشنهادی ثبت نشده`})),u(x,{open:z.value,title:`پیشنهاد محصول`,onClose:r[4]||=e=>z.value=!1},{footer:i(()=>[o(`button`,{type:`button`,class:`admin-btn admin-btn--primary`,disabled:!V.customerId||!V.productId,onClick:G},` ثبت `,8,F)]),default:i(()=>[o(`label`,M,[r[7]||=p(`مشتری `,-1),u(E,{modelValue:V.customerId,"onUpdate:modelValue":r[1]||=e=>V.customerId=e,options:H.value},null,8,[`modelValue`,`options`])]),o(`label`,N,[r[8]||=p(`محصول `,-1),u(E,{modelValue:V.productId,"onUpdate:modelValue":r[2]||=e=>V.productId=e,options:U.value},null,8,[`modelValue`,`options`])]),o(`label`,P,[r[9]||=p(`یادداشت`,-1),a(o(`textarea`,{"onUpdate:modelValue":r[3]||=e=>V.note=e,class:`admin-input`,rows:`3`},null,512),[[v,V.note]])])]),_:1},8,[`open`])]))}}),[[`__scopeId`,`data-v-56e3e728`]]);export{I as default};
File diff suppressed because one or more lines are too long
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{l as e}from"./index-DWlOII4H.js";function t(t){return e(t).toLowerCase().replace(/ي/g,`ی`).replace(/ك/g,`ک`).normalize(`NFKC`).trim()}function n(e,n){let r=t(n);return!r||t(e).includes(r)}function r(e,t){let r=t.trim();return r?e.filter(e=>n(e.label,r)):e}function i(e,t){if(!e)return;let n=e.querySelectorAll(`[data-select-option]`);(t===`first`?n[0]:n[n.length-1])?.focus()}function a(e,t){let n=e.closest(`[data-select-menu]`);if(!n)return;let r=[...n.querySelectorAll(`[data-select-option]`)],i=r.indexOf(e);i<0||r[i+t]?.focus()}export{a as n,i as r,r as t}; import{l as e}from"./index-BWgYBNME.js";function t(t){return e(t).toLowerCase().replace(/ي/g,`ی`).replace(/ك/g,`ک`).normalize(`NFKC`).trim()}function n(e,n){let r=t(n);return!r||t(e).includes(r)}function r(e,t){let r=t.trim();return r?e.filter(e=>n(e.label,r)):e}function i(e,t){if(!e)return;let n=e.querySelectorAll(`[data-select-option]`);(t===`first`?n[0]:n[n.length-1])?.focus()}function a(e,t){let n=e.closest(`[data-select-menu]`);if(!n)return;let r=[...n.querySelectorAll(`[data-select-option]`)],i=r.indexOf(e);i<0||r[i+t]?.focus()}export{a as n,i as r,r as t};
+2 -2
View File
@@ -11,10 +11,10 @@
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<title>پنل مدیریت | پارچه</title> <title>پنل مدیریت | پارچه</title>
<script type="module" crossorigin src="/admin/assets/index-DWlOII4H.js"></script> <script type="module" crossorigin src="/admin/assets/index-BWgYBNME.js"></script>
<link rel="modulepreload" crossorigin href="/admin/assets/_plugin-vue_export-helper-vx-p5QAT.js"> <link rel="modulepreload" crossorigin href="/admin/assets/_plugin-vue_export-helper-vx-p5QAT.js">
<link rel="modulepreload" crossorigin href="/admin/assets/http-CTjL79ty.js"> <link rel="modulepreload" crossorigin href="/admin/assets/http-CTjL79ty.js">
<link rel="stylesheet" crossorigin href="/admin/assets/index-Cc10RyA4.css"> <link rel="stylesheet" crossorigin href="/admin/assets/index-BsKtmxy-.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+14
View File
@@ -193,6 +193,19 @@ export function useOrdersPage() {
showDetailModal.value = false showDetailModal.value = false
} }
function dismissOrderDetail() {
const returnToChecks = route.query.from === 'checks'
showDetailModal.value = false
showDetailPanel.value = false
if (!returnToChecks) return
const previous = window.history.state?.back
if (typeof previous === 'string') {
void router.back()
return
}
void router.replace({ name: 'checks' })
}
function settlementLabel(method?: string | null) { function settlementLabel(method?: string | null) {
return settlementMethodLabel(method) return settlementMethodLabel(method)
} }
@@ -321,6 +334,7 @@ export function useOrdersPage() {
openOrderDetail, openOrderDetail,
closeDetailPanel, closeDetailPanel,
closeDetailModal, closeDetailModal,
dismissOrderDetail,
settlementLabel, settlementLabel,
changeStatus, changeStatus,
statusSaving, statusSaving,
+2
View File
@@ -251,6 +251,8 @@ export interface Product {
subtitle?: string subtitle?: string
specs?: string specs?: string
rollMeters?: number rollMeters?: number
widthCm?: number | null
weightGrams?: number | null
isNew?: boolean isNew?: boolean
colors?: string[] colors?: string[]
colorImages?: Array<string | null> colorImages?: Array<string | null>
+1 -1
View File
@@ -392,7 +392,7 @@ async function finalApproveOrder() {
<template v-else-if="order?.checkRassDate"> <template v-else-if="order?.checkRassDate">
راس: {{ formatDate(order.checkRassDate) }} راس: {{ formatDate(order.checkRassDate) }}
</template> </template>
<template v-else>راس بر اساس تاریخ ثبت سفارش محاسبه می‌شود.</template> <template v-else>راس نسبت به تاریخ صدور فاکتور ثابت است.</template>
</p> </p>
<div class="admin-action-group"> <div class="admin-action-group">
<button <button
+669 -171
View File
@@ -5,20 +5,52 @@ import AdminIcon from '@/components/icons/AdminIcon.vue'
import AdminPageSkeleton from '@/components/ui/AdminPageSkeleton.vue' import AdminPageSkeleton from '@/components/ui/AdminPageSkeleton.vue'
import AdminSelect from '@/components/ui/AdminSelect.vue' import AdminSelect from '@/components/ui/AdminSelect.vue'
import StatusBadge from '@/components/ui/StatusBadge.vue' import StatusBadge from '@/components/ui/StatusBadge.vue'
import { checksApi, ordersApi } from '@/services/api' import { checksApi, ordersApi, type ApiOrder } from '@/services/api'
import { ApiError } from '@/services/http' import { ApiError } from '@/services/http'
import { formatCurrency, formatDateTime } from '@/utils/format' import { formatCurrency, formatDate, formatMeterage, formatNumber, toPersianDigits } from '@/utils/format'
import { useToast } from '@/composables/useToast' import { useToast } from '@/composables/useToast'
import type { Check } from '@/types' import type { Check } from '@/types'
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
type GoodsLine = {
productName: string
color: string
meterage: number
}
type OrderCheckMeta = {
orderNumber: string
customerName: string
invoiceAmount: number | null
rassDate: string | null
rassDays: number | null
goods: GoodsLine[]
}
type CheckGroup = {
orderId: string
customerName: string
orderNumber: string
invoiceAmount: number | null
rassDate: string | null
rassDays: number | null
goodsLabel: string
totalCheckAmount: number
hasPending: boolean
latestAt: number
checks: Check[]
}
const checkList = ref<Check[]>([]) const checkList = ref<Check[]>([])
const orderMeta = ref(new Map<string, OrderCheckMeta>())
const loading = ref(true) const loading = ref(true)
const searchQuery = ref('') const searchQuery = ref('')
const statusFilter = ref<'all' | Check['status']>('all') const statusFilter = ref<'all' | Check['status']>('all')
const busyId = ref<string | null>(null) const busyId = ref<string | null>(null)
const previewImage = ref<string | null>(null)
const openGroups = ref<Record<string, boolean>>({})
const statusFilterOptions = [ const statusFilterOptions = [
{ value: 'all', label: 'همه وضعیت‌ها' }, { value: 'all', label: 'همه وضعیت‌ها' },
@@ -27,57 +59,189 @@ const statusFilterOptions = [
{ value: 'rejected', label: 'رد شده' }, { value: 'rejected', label: 'رد شده' },
] ]
const filteredChecks = computed(() => { const invoiceCount = computed(() => new Set(checkList.value.map((check) => check.orderId)).size)
let list = checkList.value
if (statusFilter.value !== 'all') {
list = list.filter((c) => c.status === statusFilter.value)
}
const q = searchQuery.value.trim()
if (q) {
list = list.filter(
(c) =>
(c.orderNumber ?? '').includes(q) ||
(c.customerName ?? '').includes(q) ||
(c.bankName ?? '').includes(q) ||
(c.sayadId ?? '').includes(q),
)
}
return list
})
const pendingCount = computed( const pendingCount = computed(
() => checkList.value.filter((c) => c.status === 'pending_review').length, () => checkList.value.filter((check) => check.status === 'pending_review').length,
) )
const groups = computed(() => buildGroups())
function metaFromOrder(order: ApiOrder): OrderCheckMeta {
return {
orderNumber: order.orderNumber,
customerName: order.customerName,
invoiceAmount: order.finalAmount ?? order.totalAmount ?? null,
rassDate: order.checkRassDate ?? null,
rassDays: order.checkRassDays ?? null,
goods: (order.items ?? []).map((item) => ({
productName: item.productName,
color: item.color,
meterage: item.confirmedMeterage ?? item.requestedMeterage,
})),
}
}
function goodsLabel(goods: GoodsLine[]): string {
if (!goods.length) return '—'
const shown = goods.slice(0, 2).map((item) => {
const name = [item.productName, item.color].filter(Boolean).join(' ')
return item.meterage ? `${name} (${formatMeterage(item.meterage)})` : name
})
const rest = goods.length - shown.length
if (rest > 0) return `${shown.join(' · ')} و ${toPersianDigits(rest)} قلم دیگر`
return shown.join(' · ')
}
function matchesQuery(check: Check, query: string): boolean {
if (!query) return true
const meta = orderMeta.value.get(check.orderId)
const goods = (meta?.goods ?? []).map((item) => `${item.productName} ${item.color}`).join(' ')
const haystack = [
check.orderNumber,
meta?.orderNumber,
check.customerName,
meta?.customerName,
check.bankName,
check.sayadId,
goods,
]
.filter(Boolean)
.join(' ')
.toLowerCase()
return haystack.includes(query.toLowerCase())
}
function buildGroups(): CheckGroup[] {
const byOrder = new Map<string, Check[]>()
for (const check of checkList.value) {
const list = byOrder.get(check.orderId) ?? []
list.push(check)
byOrder.set(check.orderId, list)
}
const query = searchQuery.value.trim()
const result: CheckGroup[] = []
for (const [orderId, checks] of byOrder) {
const visible = checks.some((check) => {
if (statusFilter.value !== 'all' && check.status !== statusFilter.value) return false
return matchesQuery(check, query)
})
if (!visible) continue
const sorted = [...checks].sort((a, b) => a.row - b.row)
const meta = orderMeta.value.get(orderId)
const first = sorted[0]!
const latestAt = Math.max(
0,
...sorted.map((check) => (check.createdAt ? new Date(check.createdAt).getTime() : 0)),
)
result.push({
orderId,
customerName: first.customerName || meta?.customerName || '',
orderNumber: first.orderNumber || meta?.orderNumber || '',
invoiceAmount: meta?.invoiceAmount ?? null,
rassDate: meta?.rassDate ?? null,
rassDays: meta?.rassDays ?? null,
goodsLabel: goodsLabel(meta?.goods ?? []),
totalCheckAmount: sorted.reduce((sum, check) => sum + check.amount, 0),
hasPending: sorted.some((check) => check.status === 'pending_review'),
latestAt,
checks: sorted,
})
}
result.sort((a, b) => {
if (a.hasPending !== b.hasPending) return a.hasPending ? -1 : 1
return b.latestAt - a.latestAt
})
return result
}
function rassLabel(group: CheckGroup): string {
if (group.rassDate) return formatDate(group.rassDate)
if (group.rassDays != null) return `${formatNumber(group.rassDays)} روز`
return '—'
}
function invoiceLabel(group: CheckGroup): string {
return group.invoiceAmount != null ? formatCurrency(group.invoiceAmount) : '—'
}
function isGroupOpen(orderId: string) {
return openGroups.value[orderId] === true
}
function toggleGroup(orderId: string) {
openGroups.value = {
...openGroups.value,
[orderId]: !openGroups.value[orderId],
}
}
function groupBadgeStatus(group: CheckGroup) {
if (group.checks.some((check) => check.status === 'pending_review')) return 'pending_check_review'
if (group.checks.some((check) => check.status === 'rejected')) return 'needs_correction'
return 'final_approval'
}
function checkBadgeStatus(status: Check['status']) { function checkBadgeStatus(status: Check['status']) {
if (status === 'approved') return 'final_approval' if (status === 'approved') return 'final_approval'
if (status === 'rejected') return 'needs_correction' if (status === 'rejected') return 'needs_correction'
return 'pending_check_review' return 'pending_check_review'
} }
async function loadChecksFromOrders() { function checkImage(check: Check): string | null {
const orders = await ordersApi.list() return check.frontImage || check.backImage || null
checkList.value = orders.flatMap((order) => }
(order.checks ?? []).map((check) => ({
...check, function openOrderDetail(orderId: string) {
orderNumber: order.orderNumber, router.push({ name: 'orders', query: { order: orderId, from: 'checks' } })
customerName: order.customerName,
orderStatus: order.status,
settlementType: order.settlementMethod ?? order.settlementType ?? null,
})),
)
} }
async function loadChecks() { async function loadChecks() {
loading.value = true loading.value = true
try { try {
checkList.value = await checksApi.list() const [checksResult, ordersResult] = await Promise.allSettled([
} catch { checksApi.list(),
try { ordersApi.list(),
await loadChecksFromOrders() ])
} catch (err) {
toast.error(err instanceof ApiError ? err.message : 'بارگذاری چک‌ها انجام نشد.') const orders = ordersResult.status === 'fulfilled' ? ordersResult.value : []
const meta = new Map<string, OrderCheckMeta>()
for (const order of orders) meta.set(order.id, metaFromOrder(order))
orderMeta.value = meta
if (checksResult.status === 'fulfilled') {
checkList.value = checksResult.value.map((check) => {
const info = meta.get(check.orderId)
if (!info) return check
return {
...check,
orderNumber: check.orderNumber || info.orderNumber,
customerName: check.customerName || info.customerName,
}
})
return
} }
if (ordersResult.status === 'fulfilled') {
checkList.value = orders.flatMap((order) =>
(order.checks ?? []).map((check) => ({
...check,
orderNumber: order.orderNumber,
customerName: order.customerName,
orderStatus: order.status,
settlementType: order.settlementMethod ?? order.settlementType ?? null,
})),
)
return
}
const err = checksResult.reason
toast.error(err instanceof ApiError ? err.message : 'بارگذاری چک‌ها انجام نشد.')
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -87,15 +251,11 @@ onMounted(() => {
void loadChecks() void loadChecks()
}) })
function openOrderChecks(check: Check) {
router.push(`/orders/${check.orderId}/checks`)
}
async function updateCheckStatus(check: Check, status: Check['status']) { async function updateCheckStatus(check: Check, status: Check['status']) {
busyId.value = check.id busyId.value = check.id
try { try {
const updated = await checksApi.updateStatus(check.id, { status }) const updated = await checksApi.updateStatus(check.id, { status })
const index = checkList.value.findIndex((c) => c.id === check.id) const index = checkList.value.findIndex((item) => item.id === check.id)
if (index !== -1) { if (index !== -1) {
checkList.value[index] = { checkList.value[index] = {
...checkList.value[index]!, ...checkList.value[index]!,
@@ -122,8 +282,9 @@ async function updateCheckStatus(check: Check, status: Check['status']) {
<h1 class="admin-page-header__title">چک‌ها</h1> <h1 class="admin-page-header__title">چک‌ها</h1>
</div> </div>
<div class="checks-list-page__summary"> <div class="checks-list-page__summary">
<span>{{ checkList.length }} چک</span> <span>{{ toPersianDigits(invoiceCount) }} فاکتور</span>
<span v-if="pendingCount">{{ pendingCount }} در انتظار بررسی</span> <span>{{ toPersianDigits(checkList.length) }} چک</span>
<span v-if="pendingCount">{{ toPersianDigits(pendingCount) }} در انتظار بررسی</span>
</div> </div>
</div> </div>
@@ -134,7 +295,7 @@ async function updateCheckStatus(check: Check, status: Check['status']) {
v-model="searchQuery" v-model="searchQuery"
type="search" type="search"
class="checks-list-page__search-input" class="checks-list-page__search-input"
placeholder="جستجو در شماره سفارش، مشتری، بانک، صیاد..." placeholder="جستجو در شماره فاکتور، مشتری، جنس، بانک، صیاد..."
/> />
</div> </div>
<AdminSelect <AdminSelect
@@ -147,126 +308,143 @@ async function updateCheckStatus(check: Check, status: Check['status']) {
<AdminPageSkeleton v-if="loading" variant="table" /> <AdminPageSkeleton v-if="loading" variant="table" />
<template v-else> <template v-else>
<div class="checks-list-page__table admin-card desktop-only"> <div v-if="groups.length" class="check-groups">
<div class="admin-table-wrap"> <article
<table class="admin-table"> v-for="group in groups"
<thead> :key="group.orderId"
<tr> class="check-group admin-card"
<th>شماره سفارش</th> :class="{ 'is-open': isGroupOpen(group.orderId) }"
<th>مشتری</th> >
<th>ردیف</th>
<th>مبلغ</th>
<th>بانک</th>
<th>شناسه صیادی</th>
<th>سررسید</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
<tr v-for="check in filteredChecks" :key="check.id">
<td>
<button
type="button"
class="checks-list-page__order-link"
@click="openOrderChecks(check)"
>
{{ check.orderNumber || check.orderId }}
</button>
</td>
<td>{{ check.customerName || '—' }}</td>
<td>{{ check.row }}</td>
<td>{{ formatCurrency(check.amount) }}</td>
<td>{{ check.bankName || '—' }}</td>
<td dir="ltr">{{ check.sayadId || '—' }}</td>
<td>{{ check.dueDate.slice(0, 10) }}</td>
<td><StatusBadge :status="checkBadgeStatus(check.status)" /></td>
<td>
<div class="admin-action-group">
<template v-if="check.status === 'pending_review'">
<button
type="button"
class="admin-btn admin-btn--outline-success admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'approved')"
>
تأیید
</button>
<button
type="button"
class="admin-btn admin-btn--outline-danger admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'rejected')"
>
رد
</button>
</template>
<button
type="button"
class="admin-btn admin-btn--outline admin-btn--xs"
@click="openOrderChecks(check)"
>
جزئیات سفارش
</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<p v-if="!filteredChecks.length" class="checks-list-page__empty">
چکی با این فیلتر یافت نشد.
</p>
</div>
<div class="compact-only admin-card-list">
<article v-for="check in filteredChecks" :key="check.id" class="admin-card-item">
<div class="admin-card-item__header">
<p class="admin-card-item__title">
{{ check.orderNumber || check.orderId }} — چک {{ check.row }}
</p>
<StatusBadge :status="checkBadgeStatus(check.status)" />
</div>
<div class="admin-card-item__meta">
<span>{{ check.customerName || '—' }}</span>
<span>{{ formatCurrency(check.amount) }}</span>
<span>{{ check.bankName || '—' }}</span>
<span>سررسید {{ check.dueDate.slice(0, 10) }}</span>
<span v-if="check.createdAt">ثبت {{ formatDateTime(check.createdAt) }}</span>
</div>
<div class="admin-card-item__actions">
<template v-if="check.status === 'pending_review'">
<button
type="button"
class="admin-btn admin-btn--outline-success admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'approved')"
>
تأیید
</button>
<button
type="button"
class="admin-btn admin-btn--outline-danger admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'rejected')"
>
رد
</button>
</template>
<button <button
type="button" type="button"
class="admin-btn admin-btn--outline admin-btn--xs" class="check-group__detail"
@click="openOrderChecks(check)" aria-label="جزئیات سفارش"
title="جزئیات سفارش"
@click.stop="openOrderDetail(group.orderId)"
> >
جزئیات سفارش <AdminIcon name="eye" :size="18" />
</button> </button>
</div> <button
</article> type="button"
<p v-if="!filteredChecks.length" class="checks-list-page__empty"> class="check-group__summary"
چکی با این فیلتر یافت نشد. :aria-expanded="isGroupOpen(group.orderId)"
</p> @click="toggleGroup(group.orderId)"
</div> >
<AdminIcon
class="check-group__chevron"
:name="isGroupOpen(group.orderId) ? 'chevron-down' : 'chevron-left'"
:size="16"
/>
<strong class="check-group__summary-name">{{ group.customerName || '—' }}</strong>
<span class="check-group__summary-invoice">{{ group.orderNumber || '—' }}</span>
<span class="check-group__summary-meta">{{ toPersianDigits(group.checks.length) }} چک</span>
<span class="check-group__summary-meta">{{ formatCurrency(group.totalCheckAmount) }}</span>
<span class="check-group__summary-meta">راس {{ rassLabel(group) }}</span>
<StatusBadge :status="groupBadgeStatus(group)" />
</button>
<div v-if="isGroupOpen(group.orderId)" class="check-group__body">
<header class="check-group__head">
<div class="check-group__col">
<div class="check-group__field">
<span>نام مشتری</span>
<strong>{{ group.customerName || '—' }}</strong>
</div>
<div class="check-group__field">
<span>شماره فاکتور</span>
<strong class="check-group__ltr">{{ group.orderNumber || '—' }}</strong>
</div>
<div class="check-group__field">
<span>جنس سفارش</span>
<strong>{{ group.goodsLabel }}</strong>
</div>
</div>
<div class="check-group__col">
<div class="check-group__field">
<span>مبلغ فاکتور</span>
<strong>{{ invoiceLabel(group) }}</strong>
</div>
<div class="check-group__field">
<span>مبلغ مجموع چک‌ها</span>
<strong>{{ formatCurrency(group.totalCheckAmount) }}</strong>
</div>
<div class="check-group__field">
<span>راس چک‌ها</span>
<strong>{{ rassLabel(group) }}</strong>
</div>
</div>
</header>
<div class="check-lines">
<div class="check-line check-line--head" aria-hidden="true">
<span>ردیف</span>
<span>مبلغ</span>
<span>سررسید</span>
<span>عکس</span>
<span>وضعیت</span>
<span>عملیات</span>
</div>
<div v-for="check in group.checks" :key="check.id" class="check-line">
<span class="check-line__row">{{ toPersianDigits(check.row) }}</span>
<span class="check-line__amount">
<strong>{{ formatCurrency(check.amount) }}</strong>
<small v-if="check.bankName">{{ check.bankName }}</small>
</span>
<span class="check-line__due">
<strong>{{ formatDate(check.dueDate) }}</strong>
<small v-if="check.sayadId" class="check-line__sayad">{{ check.sayadId }}</small>
</span>
<button
v-if="checkImage(check)"
type="button"
class="check-line__photo"
aria-label="عکس چک"
@click="previewImage = checkImage(check)"
>
<img :src="checkImage(check)!" alt="" />
</button>
<span v-else class="check-line__photo check-line__photo--empty" aria-hidden="true"></span>
<div class="check-line__foot">
<span class="check-line__status">
<StatusBadge :status="checkBadgeStatus(check.status)" />
</span>
<div class="check-line__actions">
<template v-if="check.status === 'pending_review'">
<button
type="button"
class="admin-btn admin-btn--outline-success admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'approved')"
>
تأیید
</button>
<button
type="button"
class="admin-btn admin-btn--outline-danger admin-btn--xs"
:disabled="busyId === check.id"
@click="updateCheckStatus(check, 'rejected')"
>
رد
</button>
</template>
</div>
</div>
</div>
</div>
</div>
</article>
</div>
<p v-else class="checks-list-page__empty admin-card">چکی با این فیلتر یافت نشد.</p>
</template> </template>
<Teleport to="body">
<div v-if="previewImage" class="checks-list-page__lightbox" @click.self="previewImage = null">
<button type="button" class="checks-list-page__lightbox-close" @click="previewImage = null">
بستن
</button>
<img :src="previewImage" alt="عکس چک" />
</div>
</Teleport>
</div> </div>
</template> </template>
@@ -316,22 +494,236 @@ async function updateCheckStatus(check: Check, status: Check['status']) {
width: 220px; width: 220px;
} }
.checks-list-page__table { .check-groups {
padding: 0; display: flex;
flex-direction: column;
gap: 14px;
}
.check-group {
position: relative;
overflow: hidden;
container-type: inline-size;
container-name: check-group;
}
.check-group__head {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: 12px 28px;
padding: 14px 16px;
border-bottom: 1px solid var(--color-border);
background: color-mix(in srgb, var(--color-primary-soft) 70%, var(--color-surface));
}
.check-group__col {
display: grid;
gap: 8px;
min-width: 0;
}
.check-group__field {
display: grid;
grid-template-columns: 8.25rem minmax(0, 1fr);
gap: 8px;
align-items: start;
font-size: 0.8125rem;
}
.check-group__field > span {
color: var(--color-text-muted);
padding-top: 2px;
}
.check-group__field strong {
min-width: 0;
font-weight: 700;
line-height: 1.5;
overflow-wrap: anywhere;
}
.check-group__ltr {
direction: ltr;
unicode-bidi: isolate;
text-align: right;
}
.check-group__summary {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px 16px;
width: 100%;
min-height: 52px;
padding: 10px 14px 10px 52px;
border: 0;
background: transparent;
color: inherit;
font: inherit;
text-align: start;
cursor: pointer;
}
.check-group.is-open .check-group__summary {
border-bottom: 1px solid var(--color-border);
background: color-mix(in srgb, var(--color-primary-soft) 45%, var(--color-surface));
}
.check-group__chevron {
flex-shrink: 0;
color: var(--color-text-muted);
}
.check-group__summary-name {
font-size: 0.9375rem;
font-weight: 700;
}
.check-group__summary-invoice {
direction: ltr;
unicode-bidi: isolate;
font-size: 0.8125rem;
font-weight: 700;
}
.check-group__summary-meta {
color: var(--color-text-muted);
font-size: 0.8125rem;
}
.check-group__detail {
position: absolute;
top: 10px;
left: 10px;
z-index: 1;
width: 32px;
height: 32px;
display: grid;
place-items: center;
border: 1px solid var(--color-border);
border-radius: 8px;
background: var(--color-surface);
color: var(--color-primary);
cursor: pointer;
}
.check-group__detail:hover {
border-color: var(--color-primary);
}
.check-lines {
display: grid;
grid-template-columns: 3rem repeat(5, minmax(0, 1fr));
column-gap: 16px;
}
.check-line {
display: grid;
grid-template-columns: subgrid;
grid-column: 1 / -1;
align-items: center;
padding: 10px 16px;
border-bottom: 1px solid var(--color-border-light);
}
.check-line:last-child {
border-bottom: 0;
}
.check-line--head {
padding-top: 8px;
padding-bottom: 8px;
font-size: 0.75rem;
color: var(--color-text-muted);
background: var(--color-border-light);
}
.check-line--head > span {
text-align: start;
}
.check-line__row {
font-weight: 700;
text-align: center;
}
.check-line__amount,
.check-line__due {
display: grid;
gap: 2px;
min-width: 0;
overflow: hidden; overflow: hidden;
} }
.checks-list-page__order-link { .check-line__amount strong,
border: none; .check-line__due strong {
background: transparent; font-size: 0.875rem;
color: var(--color-primary); font-weight: 700;
cursor: pointer;
padding: 0;
font: inherit;
} }
.checks-list-page__order-link:hover { .check-line__amount small,
text-decoration: underline; .check-line__due small {
color: var(--color-text-muted);
font-size: 0.75rem;
}
.check-line__sayad {
display: block;
direction: ltr;
unicode-bidi: isolate;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.check-line__photo {
width: 48px;
height: 34px;
justify-self: start;
border-radius: 6px;
border: 1px solid var(--color-border);
overflow: hidden;
padding: 0;
background: var(--color-border-light);
cursor: zoom-in;
}
.check-line__photo img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.check-line__photo--empty {
cursor: default;
}
.check-line__foot {
display: contents;
}
.check-line__actions {
display: flex;
flex-wrap: nowrap;
gap: 6px;
justify-content: flex-start;
}
.check-line__actions .admin-btn {
flex: 0 0 auto;
}
.check-line__status {
justify-self: start;
}
.check-line__status :deep(.admin-badge) {
max-width: 100%;
white-space: normal;
line-height: 1.35;
text-align: center;
} }
.checks-list-page__empty { .checks-list-page__empty {
@@ -341,4 +733,110 @@ async function updateCheckStatus(check: Check, status: Check['status']) {
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 0.875rem; font-size: 0.875rem;
} }
.checks-list-page__lightbox {
position: fixed;
inset: 0;
z-index: 300000;
display: grid;
place-items: center;
background: rgba(0, 0, 0, 0.84);
padding: 20px;
}
.checks-list-page__lightbox img {
max-width: min(900px, 100%);
max-height: 88vh;
object-fit: contain;
border-radius: 8px;
}
.checks-list-page__lightbox-close {
position: absolute;
top: 14px;
inset-inline-end: 14px;
border: 0;
border-radius: 8px;
padding: 8px 12px;
cursor: pointer;
}
@container check-group (max-width: 760px) {
.check-lines {
display: flex;
flex-direction: column;
}
.check-line--head {
display: none;
}
.check-line:not(.check-line--head) {
grid-column: auto;
grid-template-columns: 1.75rem minmax(0, 1fr) 48px;
grid-template-areas:
'row amount photo'
'due due photo'
'foot foot foot';
row-gap: 6px;
column-gap: 10px;
align-items: center;
padding-inline: 16px;
}
.check-line__foot {
display: flex;
grid-area: foot;
align-items: center;
justify-content: space-between;
gap: 8px;
min-width: 0;
padding-top: 2px;
}
.check-line__row {
grid-area: row;
text-align: start;
}
.check-line__amount {
grid-area: amount;
}
.check-line__due {
grid-area: due;
}
.check-line__photo {
grid-area: photo;
justify-self: end;
align-self: center;
}
.check-line__actions {
margin-inline-start: auto;
}
}
@container check-group (max-width: 640px) {
.check-group__head {
grid-template-columns: minmax(0, 1fr);
gap: 10px;
}
}
@container check-group (max-width: 420px) {
.check-group__summary {
padding: 10px 12px 10px 48px;
}
.check-line {
padding-inline: 12px;
}
.check-group__field {
grid-template-columns: 6.25rem minmax(0, 1fr);
gap: 6px;
}
}
</style> </style>
@@ -25,6 +25,7 @@ const {
openOrderDetail, openOrderDetail,
closeDetailPanel, closeDetailPanel,
closeDetailModal, closeDetailModal,
dismissOrderDetail,
settlementLabel, settlementLabel,
goPricing, goPricing,
goChecks, goChecks,
@@ -214,7 +215,7 @@ function handleFinalApprove() {
type="button" type="button"
class="admin-detail-panel__back" class="admin-detail-panel__back"
aria-label="بازگشت به لیست" aria-label="بازگشت به لیست"
@click="closeDetailPanel" @click="dismissOrderDetail"
> >
<AdminIcon name="chevron-back" :size="20" /> <AdminIcon name="chevron-back" :size="20" />
</button> </button>
@@ -240,7 +241,7 @@ function handleFinalApprove() {
:open="isCompactLayout && !isMobile && showDetailModal && !!selectedOrder" :open="isCompactLayout && !isMobile && showDetailModal && !!selectedOrder"
title="جزئیات سفارش" title="جزئیات سفارش"
size="xl" size="xl"
@close="closeDetailModal" @close="dismissOrderDetail"
> >
<template v-if="selectedOrder"> <template v-if="selectedOrder">
<OrderDetailContent <OrderDetailContent
@@ -23,6 +23,7 @@ const {
showDetailModal, showDetailModal,
openOrderDetail, openOrderDetail,
closeDetailModal, closeDetailModal,
dismissOrderDetail,
settlementLabel, settlementLabel,
goPricing, goPricing,
goChecks, goChecks,
@@ -303,7 +304,7 @@ function runMenuAction(action: 'detail' | 'pricing' | 'checks' | 'cash' | 'final
:open="isDesktopLayout && showDetailModal && !!selectedOrder" :open="isDesktopLayout && showDetailModal && !!selectedOrder"
title="جزئیات سفارش" title="جزئیات سفارش"
size="xl" size="xl"
@close="closeDetailModal" @close="dismissOrderDetail"
> >
<template v-if="selectedOrder"> <template v-if="selectedOrder">
<OrderDetailContent <OrderDetailContent
+142 -149
View File
@@ -14,7 +14,6 @@ import { useToast } from '@/composables/useToast'
import { confirm } from '@/composables/useConfirm' import { confirm } from '@/composables/useConfirm'
import { formatCurrency, formatNumber } from '@/utils/format' import { formatCurrency, formatNumber } from '@/utils/format'
import type { Company, Product } from '@/types' import type { Company, Product } from '@/types'
import AdminCurrencyInput from '@/components/ui/AdminCurrencyInput.vue'
import { toCssBackgroundUrl } from '@/utils/catalogImage' import { toCssBackgroundUrl } from '@/utils/catalogImage'
import { import {
PAYMENT_RECIPIENT_COMPANY_SLUG, PAYMENT_RECIPIENT_COMPANY_SLUG,
@@ -64,18 +63,15 @@ const companyFormOptions = computed(() => {
const emptyForm = () => ({ const emptyForm = () => ({
companyId: '', companyId: '',
name: '', name: '',
color: '',
productCode: '', productCode: '',
minMeterage: 100, widthCm: null as number | null,
orderStep: 50, weightGrams: null as number | null,
price: null as number | null, inStock: true,
internalStock: 0,
isActive: true, isActive: true,
usageCategory: 'other', usageCategory: 'other',
season: 'all_season', season: 'all_season',
composition: '', composition: '',
weaveType: '', weaveType: '',
subtitle: '',
specs: '', specs: '',
rollMeters: 50, rollMeters: 50,
isNew: false, isNew: false,
@@ -91,7 +87,7 @@ const form = reactive(emptyForm())
const isModalOpen = computed(() => modalMode.value !== null) const isModalOpen = computed(() => modalMode.value !== null)
const modalTitle = computed(() => (modalMode.value === 'edit' ? 'ویرایش محصول' : 'افزودن محصول')) const modalTitle = computed(() => (modalMode.value === 'edit' ? 'ویرایش محصول' : 'افزودن محصول'))
const isFormValid = computed(() => form.name.trim() && form.minMeterage > 0) const isFormValid = computed(() => form.name.trim().length > 0 && form.rollMeters > 0)
const hasActiveFilters = computed( const hasActiveFilters = computed(
() => searchQuery.value.trim().length > 0 || companyFilter.value !== 'all', () => searchQuery.value.trim().length > 0 || companyFilter.value !== 'all',
) )
@@ -167,18 +163,15 @@ async function openEditModal(product: Product) {
} }
form.companyId = productCompanyIdForForm(product.companyId, companies.value) form.companyId = productCompanyIdForForm(product.companyId, companies.value)
form.name = product.name form.name = product.name
form.color = product.color
form.productCode = product.productCode form.productCode = product.productCode
form.minMeterage = product.minMeterage form.widthCm = product.widthCm ?? null
form.orderStep = product.orderStep form.weightGrams = product.weightGrams ?? null
form.price = product.price form.inStock = product.internalStock == null || product.internalStock > 0
form.internalStock = product.internalStock
form.isActive = product.isActive form.isActive = product.isActive
form.usageCategory = (product as { usageCategory?: string }).usageCategory ?? 'other' form.usageCategory = (product as { usageCategory?: string }).usageCategory ?? 'other'
form.season = (product as { season?: string }).season ?? 'all_season' form.season = (product as { season?: string }).season ?? 'all_season'
form.composition = (product as { composition?: string }).composition ?? '' form.composition = (product as { composition?: string }).composition ?? ''
form.weaveType = (product as { weaveType?: string }).weaveType ?? '' form.weaveType = (product as { weaveType?: string }).weaveType ?? ''
form.subtitle = (product as { subtitle?: string }).subtitle ?? ''
form.specs = (product as { specs?: string }).specs ?? '' form.specs = (product as { specs?: string }).specs ?? ''
form.rollMeters = (product as { rollMeters?: number }).rollMeters ?? 50 form.rollMeters = (product as { rollMeters?: number }).rollMeters ?? 50
form.isNew = (product as { isNew?: boolean }).isNew ?? false form.isNew = (product as { isNew?: boolean }).isNew ?? false
@@ -246,10 +239,7 @@ function buildProductMedia() {
form.colorRows.forEach((row, index) => { form.colorRows.forEach((row, index) => {
const imageUrl = row.imageUrl.trim() || undefined const imageUrl = row.imageUrl.trim() || undefined
const name = const name = row.name.trim() || (imageUrl ? `رنگ ${index + 1}` : '')
row.name.trim() ||
(index === 0 ? form.color.trim() : '') ||
(imageUrl ? `رنگ ${index + 1}` : '')
if (!name && !imageUrl) return if (!name && !imageUrl) return
if (!name) return if (!name) return
colorEntries.push({ colorEntries.push({
@@ -270,7 +260,7 @@ function closeModal() {
async function submitProduct() { async function submitProduct() {
if (!isFormValid.value) { if (!isFormValid.value) {
formError.value = 'نام محصول و حداقل متراژ الزامی هستند.' formError.value = 'نام محصول و متراژ هر طاقه الزامی هستند.'
return return
} }
@@ -284,32 +274,39 @@ async function submitProduct() {
} }
const { colorEntries, imageUrls } = buildProductMedia() const { colorEntries, imageUrls } = buildProductMedia()
const rollMeters = form.rollMeters
const catalogPayload = { const catalogPayload = {
usageCategory: form.usageCategory, usageCategory: form.usageCategory,
season: form.season, season: form.season,
composition: form.composition.trim() || null, composition: form.composition.trim() || null,
weaveType: form.weaveType.trim() || null, weaveType: form.weaveType.trim() || null,
subtitle: form.subtitle.trim() || null,
specs: form.specs.trim() || undefined, specs: form.specs.trim() || undefined,
rollMeters: form.rollMeters, rollMeters,
widthCm: form.widthCm,
weightGrams: form.weightGrams,
isNew: form.isNew, isNew: form.isNew,
colorsJson: colorEntries, colorsJson: colorEntries,
imageUrlsJson: imageUrls, imageUrlsJson: imageUrls,
} }
const productCode = form.productCode.trim() || `PRD-${Date.now()}` const productCode = form.productCode.trim() || `PRD-${Date.now()}`
const colorLabel = colorEntries[0]?.name || '—'
const meteragePayload = {
minMeterage: rollMeters,
orderStep: rollMeters,
orderMultiplier: rollMeters,
}
const stockPayload = { internalStock: form.inStock ? null : 0 }
saving.value = true saving.value = true
try { try {
if (modalMode.value === 'edit' && editingId.value) { if (modalMode.value === 'edit' && editingId.value) {
await productsApi.update(editingId.value, { await productsApi.update(editingId.value, {
companyId: companyIdPayload, companyId: companyIdPayload,
name: form.name.trim(), name: form.name.trim(),
color: form.color.trim() || colorEntries[0]?.name || '—', color: colorLabel,
code: productCode, code: productCode,
productCode, productCode,
minMeterage: form.minMeterage, ...meteragePayload,
orderStep: form.orderStep, ...stockPayload,
price: form.price,
internalStock: form.internalStock,
isActive: form.isActive, isActive: form.isActive,
...catalogPayload, ...catalogPayload,
}) })
@@ -321,13 +318,11 @@ async function submitProduct() {
const created = await productsApi.create({ const created = await productsApi.create({
companyId: companyIdPayload, companyId: companyIdPayload,
name: form.name.trim(), name: form.name.trim(),
color: form.color.trim() || colorEntries[0]?.name || '—', color: colorLabel,
code: productCode, code: productCode,
productCode, productCode,
minMeterage: form.minMeterage, ...meteragePayload,
orderStep: form.orderStep, ...stockPayload,
price: form.price,
internalStock: form.internalStock,
isActive: form.isActive, isActive: form.isActive,
...catalogPayload, ...catalogPayload,
}) })
@@ -524,120 +519,9 @@ function clearFilters() {
<p v-if="formError" class="admin-form__error" role="alert">{{ formError }}</p> <p v-if="formError" class="admin-form__error" role="alert">{{ formError }}</p>
<div class="admin-form__field"> <div class="admin-form__field">
<label>شرکت مرتبط با محصول</label> <label>عکس اسلایدی محصول</label>
<AdminSelect
v-model="form.companyId"
block
aria-label="انتخاب شرکت مرتبط با محصول"
placeholder="نادمی تکس (پیش‌فرض)"
:options="companyFormOptions"
/>
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-name">نام محصول <span class="admin-form__required">*</span></label>
<input id="product-name" v-model="form.name" type="text" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="product-color">رنگ / نوع</label>
<input id="product-color" v-model="form.color" type="text" class="admin-input" />
</div>
</div>
<div class="admin-form__field">
<label for="product-code">کد محصول</label>
<input id="product-code" v-model="form.productCode" type="text" class="admin-input" dir="ltr" />
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-min">حداقل متراژ</label>
<input id="product-min" v-model.number="form.minMeterage" type="number" min="1" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="product-step">ضریب سفارش</label>
<input id="product-step" v-model.number="form.orderStep" type="number" min="1" class="admin-input" />
</div>
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-price">قیمت هر متر (تومان)</label>
<AdminCurrencyInput id="product-price" v-model="form.price" placeholder="مثلاً ۲۵۰٬۰۰۰" />
</div>
<div class="admin-form__field">
<label for="product-stock">موجودی داخلی (متر)</label>
<input id="product-stock" v-model.number="form.internalStock" type="number" min="0" class="admin-input" />
</div>
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-usage">کاربرد</label>
<AdminSelect
id="product-usage"
v-model="form.usageCategory"
block
aria-label="کاربرد محصول"
placeholder="انتخاب کاربرد"
:options="usageOptions"
/>
</div>
<div class="admin-form__field">
<label for="product-season">فصل</label>
<AdminSelect
id="product-season"
v-model="form.season"
block
aria-label="فصل محصول"
placeholder="انتخاب فصل"
:options="seasonOptions"
/>
</div>
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-composition">جنس / ترکیب</label>
<input id="product-composition" v-model="form.composition" type="text" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="product-weave">نوع بافت</label>
<input id="product-weave" v-model="form.weaveType" type="text" class="admin-input" />
</div>
</div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-subtitle">زیرعنوان</label>
<input id="product-subtitle" v-model="form.subtitle" type="text" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="product-roll">متراژ طاقه</label>
<input id="product-roll" v-model.number="form.rollMeters" type="number" min="1" class="admin-input" />
</div>
</div>
<div class="admin-form__field">
<label for="product-specs">مشخصات محصول</label>
<p class="admin-form__hint">
متن آزاد؛ در بخش «مشاهده همه مشخصات» صفحه محصول در اپ نمایش داده می‌شود.
</p>
<textarea
id="product-specs"
v-model="form.specs"
class="admin-form__textarea"
rows="5"
placeholder="مثلاً جنس، عرض، وزن و نکات نگهداری — هر خط یک بند"
/>
</div>
<div class="admin-form__field">
<label>تصاویر عمومی محصول</label>
<p class="color-variants__hint"> <p class="color-variants__hint">
فقط برای گالری اصلی در اپ کاربر (بدون وابستگی به رنگ). با «ثبت تغییرات» ثبت می‌شود. تصاویر گالری اصلی در اپ کاربر. با «ثبت محصول» یا «ثبت تغییرات» ذخیره می‌شود.
</p> </p>
<div class="general-images"> <div class="general-images">
<div <div
@@ -645,7 +529,7 @@ function clearFilters() {
:key="`general-${gIdx}`" :key="`general-${gIdx}`"
class="product-media-row" class="product-media-row"
role="group" role="group"
:aria-label="`تصویر عمومی ${gIdx + 1}`" :aria-label="`تصویر اسلاید ${gIdx + 1}`"
> >
<AdminImagePicker <AdminImagePicker
compact compact
@@ -664,15 +548,75 @@ function clearFilters() {
</button> </button>
</div> </div>
<button type="button" class="admin-btn admin-btn--outline product-media-add" @click="addGeneralImageSlot"> <button type="button" class="admin-btn admin-btn--outline product-media-add" @click="addGeneralImageSlot">
+ افزودن تصویر عمومی + افزودن تصویر اسلاید
</button> </button>
</div> </div>
</div> </div>
<div class="admin-form__field"> <div class="admin-form__field">
<label>رنگ‌بندی‌ها</label> <label for="product-name">نام محصول <span class="admin-form__required">*</span></label>
<input id="product-name" v-model="form.name" type="text" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="product-code">کد محصول</label>
<input id="product-code" v-model="form.productCode" type="text" class="admin-input" dir="ltr" />
</div>
<div class="admin-form__field">
<label for="product-width">عرض (سانتی‌متر)</label>
<input
id="product-width"
v-model.number="form.widthCm"
type="number"
min="0"
step="0.1"
class="admin-input"
dir="ltr"
/>
</div>
<div class="admin-form__field">
<label for="product-weight">وزن (گرم)</label>
<input
id="product-weight"
v-model.number="form.weightGrams"
type="number"
min="0"
step="1"
class="admin-input"
dir="ltr"
/>
</div>
<div class="admin-form__field">
<label for="product-composition">جنس</label>
<input id="product-composition" v-model="form.composition" type="text" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="product-weave">نوع بافت</label>
<input id="product-weave" v-model="form.weaveType" type="text" class="admin-input" />
</div>
<div class="admin-form__field">
<label for="product-specs">توضیح اضافه و تکمیلی برای مشخصه فنی</label>
<p class="admin-form__hint">
در بخش «مشاهده همه مشخصات» صفحه محصول در اپ نمایش داده می‌شود.
</p>
<textarea
id="product-specs"
v-model="form.specs"
class="admin-form__textarea"
rows="5"
placeholder="نکات تکمیلی مشخصات فنی — هر خط یک بند"
/>
</div>
<div class="admin-form__field">
<label>رنگ‌بندی</label>
<p class="color-variants__hint"> <p class="color-variants__hint">
برای هر رنگ یک نام و یک تصویر بگذارید. تصویر رنگ در بخش رنگ‌بندی اپ نمایش داده می‌شود، نه در گالری عمومی. برای هر رنگ یک نام و یک تصویر بگذارید. تصویر رنگ در بخش رنگ‌بندی اپ نمایش داده می‌شود.
</p> </p>
<div class="color-variants"> <div class="color-variants">
<div <div
@@ -711,12 +655,61 @@ function clearFilters() {
</div> </div>
</div> </div>
<div class="admin-form__row">
<div class="admin-form__field">
<label for="product-usage">دسته‌بندی</label>
<AdminSelect
id="product-usage"
v-model="form.usageCategory"
block
aria-label="دسته‌بندی محصول"
placeholder="انتخاب دسته‌بندی"
:options="usageOptions"
/>
</div>
<div class="admin-form__field">
<label for="product-season">فصل</label>
<AdminSelect
id="product-season"
v-model="form.season"
block
aria-label="فصل محصول"
placeholder="انتخاب فصل"
:options="seasonOptions"
/>
</div>
</div>
<div class="admin-form__field">
<label for="product-roll">متراژ هر طاقه <span class="admin-form__required">*</span></label>
<p class="admin-form__hint">
حداقل متراژ و ضریب سفارش به‌صورت خودکار برابر با همین مقدار تنظیم می‌شود.
</p>
<input id="product-roll" v-model.number="form.rollMeters" type="number" min="1" class="admin-input" />
</div>
<div class="admin-form__field">
<BaseSwitch v-model="form.inStock" label="موجود در انبار" />
<p class="admin-form__hint">در صورت اتمام موجودی، خاموش کنید؛ نیازی به ثبت متراژ دقیق نیست.</p>
</div>
<div class="admin-form__field">
<label>شرکت مرتبط با محصول</label>
<AdminSelect
v-model="form.companyId"
block
aria-label="انتخاب شرکت مرتبط با محصول"
placeholder="نادمی تکس (پیش‌فرض)"
:options="companyFormOptions"
/>
</div>
<div class="admin-form__field"> <div class="admin-form__field">
<BaseSwitch v-model="form.isNew" label="محصول جدید" /> <BaseSwitch v-model="form.isNew" label="محصول جدید" />
</div> </div>
<div class="admin-form__field"> <div class="admin-form__field">
<BaseSwitch v-model="form.isActive" label="وضعیت" /> <BaseSwitch v-model="form.isActive" label="فعال در فروشگاه" />
</div> </div>
</form> </form>