Polish customer app UI and streamline CRM overlays.

Replace standalone customer create and meeting report pages with overlays, add TimeInput, remove opaque input backgrounds app-wide, improve desktop layouts/skeletons, and refresh shopping, customer, and reminder screens.
This commit is contained in:
2026-09-15 21:41:48 +03:30
parent d2c11e5d62
commit bbb3a15d1c
101 changed files with 2910 additions and 896 deletions
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{A as e,Et as t,L as n,M as r,O as i,P as a,R as o,St as s,Y as c,Z as l,_ as ee,j as u,mt as te,n as d,rt as f,t as p,tt as m,z as h}from"./BasicIcon-C6boaehV.js";import{i as ne,n as re}from"./format-BHJptHdF.js";import{t as ie}from"./catalog-s7nVUdlj.js";import{d as ae,n as g}from"./index-B6MlTGUp.js";import{t as oe}from"./PageHeader-CDQMUtCp.js";import{t as se}from"./useRequirePricedOrder-zO2gkEXW.js";var _=h({__name:`IconSuccessCircle`,props:{size:{default:24},strokeWidth:{default:1.5},color:{default:null}},setup(e){return(t,n)=>(c(),r(p,{icon:`vuesax-linear:tick-circle`,size:e.size,"stroke-width":e.strokeWidth,color:e.color},null,8,[`size`,`stroke-width`,`color`]))}}),ce={class:`page cash-mobile-page mobile-only`},le={class:`cash-page__scroll page__scroll`},ue={class:`cash-page__success cash-page__success--done`},v={class:`cash-page__success-title`},y={class:`cash-page__success-subtitle`},b={class:`accounts-panel`},x={class:`accounts-panel__title`},S={class:`bank-card__head`},C={class:`bank-card__name`},w={class:`bank-card__icon`,"aria-hidden":`true`},T={class:`bank-card__field`},E={class:`bank-card__value`,dir:`ltr`},D={class:`bank-card__field`},O={class:`bank-card__value`,dir:`ltr`},k={class:`cash-page__footer`},A={class:`desktop-checkout-shell cash-desktop-page desktop-only`},j={class:`desktop-page__head`},M={class:`desktop-page__subtitle`},N={class:`cash-page__success cash-page__success--done`},de={class:`cash-page__success-title`},P={class:`accounts-panel`},F={class:`accounts-panel__title`},I={class:`desktop-grid-2`},L={class:`bank-card__head`},R={class:`bank-card__name`},z={class:`bank-card__icon`,"aria-hidden":`true`},B={class:`bank-card__field`},V={class:`bank-card__value`,dir:`ltr`},H={class:`bank-card__field`},fe={class:`bank-card__value`,dir:`ltr`},pe={class:`cash-desktop-page__actions`},U=d(h({__name:`CashSettlementView`,setup(r){let d=ee(),p=ae(),h=ie(),U=te([]),{orderId:W,order:G}=se(),K=e(()=>G.value?.companyName??``);m(W,async e=>{e&&!p.getOrderById(e)&&await p.fetchOrder(e)},{immediate:!0}),m(()=>G.value?.id,async e=>{if(!e||!G.value){U.value=[];return}h.companies.length||await h.loadCompanies(),U.value=await p.getBankAccountsForOrder(G.value)},{immediate:!0});let q=e(()=>G.value?.finalAmount?re(G.value.finalAmount):`—`),J=e(()=>{let e=G.value?.cashInvoiceExpiresAt;return e?`مهلت پرداخت این فاکتور تا ${ne(e)} است.`:`مبلغ فاکتور را به یکی از حساب‌های زیر واریز و رسید را ثبت کنید.`}),Y=e(()=>{let e=K.value.replace(/^شرکت\s+/u,``);return e?`شماره حساب‌های واریز — ${e}`:`شماره حساب‌های واریز`});function X(e){return e.replace(/\D/g,``).replace(/(\d{4})(?=\d)/g,`$1 `).trim()}function Z(e){return e.replace(/\s/g,``).toUpperCase().replace(/(.{4})/g,`$1 `).trim()}function Q(){d.replace(`/orders/${W.value}?track=1`)}function $(){d.replace(`/orders/${W.value}?track=1&receipt=1`)}return(e,r)=>(c(),a(i,null,[u(`div`,ce,[o(oe,{title:`تسویه نقدی`,"show-back":``,centered:``,onBack:r[0]||=e=>s(d).push(`/orders/${s(W)}`)}),u(`div`,le,[u(`div`,ue,[o(_,{size:56}),u(`p`,v,`مبلغ قابل پرداخت `+t(q.value),1),u(`p`,y,t(J.value),1)]),u(`section`,b,[u(`h2`,x,t(Y.value),1),(c(!0),a(i,null,l(U.value,e=>(c(),a(`article`,{key:e.id,class:`bank-card`},[u(`div`,S,[u(`span`,C,t(e.bankName),1),u(`span`,w,t(e.bankName.charAt(0)),1)]),u(`div`,T,[r[1]||=u(`span`,{class:`bank-card__label`},`شماره کارت`,-1),u(`strong`,E,t(X(e.cardNumber)),1)]),u(`div`,D,[r[2]||=u(`span`,{class:`bank-card__label`},`شماره شبا`,-1),u(`strong`,O,t(Z(e.iban)),1)])]))),128))])]),u(`div`,k,[o(g,{variant:`outline`,block:``,onClick:Q},{default:f(()=>[...r[3]||=[n(`مشاهده سفارش`,-1)]]),_:1}),o(g,{block:``,class:`cash-page__submit`,onClick:$},{default:f(()=>[...r[4]||=[n(` ثبت رسید پرداخت `,-1)]]),_:1})])]),u(`div`,A,[u(`header`,j,[r[5]||=u(`h1`,{class:`desktop-page__title`},`تسویه نقدی`,-1),u(`p`,M,t(J.value),1)]),u(`div`,N,[o(_,{size:56}),u(`p`,de,`مبلغ قابل پرداخت `+t(q.value),1)]),u(`section`,P,[u(`h2`,F,t(Y.value),1),u(`div`,I,[(c(!0),a(i,null,l(U.value,e=>(c(),a(`article`,{key:e.id,class:`bank-card`},[u(`div`,L,[u(`span`,R,t(e.bankName),1),u(`span`,z,t(e.bankName.charAt(0)),1)]),u(`div`,B,[r[6]||=u(`span`,{class:`bank-card__label`},`شماره کارت`,-1),u(`strong`,V,t(X(e.cardNumber)),1)]),u(`div`,H,[r[7]||=u(`span`,{class:`bank-card__label`},`شماره شبا`,-1),u(`strong`,fe,t(Z(e.iban)),1)])]))),128))])]),u(`div`,pe,[o(g,{variant:`outline`,class:`desktop-btn-compact`,onClick:Q},{default:f(()=>[...r[8]||=[n(` مشاهده سفارش `,-1)]]),_:1}),o(g,{class:`desktop-btn-compact`,onClick:$},{default:f(()=>[...r[9]||=[n(` ثبت رسید پرداخت `,-1)]]),_:1})])])],64))}}),[[`__scopeId`,`data-v-c299caf6`]]);export{U as default}; import{A as e,Et as t,L as n,M as r,O as i,P as a,R as o,St as s,Y as c,Z as l,_ as ee,j as u,mt as te,n as d,rt as f,t as p,tt as m,z as h}from"./BasicIcon-C6boaehV.js";import{i as ne,n as re}from"./format-BHJptHdF.js";import{t as ie}from"./catalog-s7nVUdlj.js";import{d as ae,n as g}from"./index-DzzUgmYT.js";import{t as oe}from"./PageHeader-BFnKSFNh.js";import{t as se}from"./useRequirePricedOrder-CxEozNXk.js";var _=h({__name:`IconSuccessCircle`,props:{size:{default:24},strokeWidth:{default:1.5},color:{default:null}},setup(e){return(t,n)=>(c(),r(p,{icon:`vuesax-linear:tick-circle`,size:e.size,"stroke-width":e.strokeWidth,color:e.color},null,8,[`size`,`stroke-width`,`color`]))}}),ce={class:`page cash-mobile-page mobile-only`},le={class:`cash-page__scroll page__scroll`},ue={class:`cash-page__success cash-page__success--done`},v={class:`cash-page__success-title`},y={class:`cash-page__success-subtitle`},b={class:`accounts-panel`},x={class:`accounts-panel__title`},S={class:`bank-card__head`},C={class:`bank-card__name`},w={class:`bank-card__icon`,"aria-hidden":`true`},T={class:`bank-card__field`},E={class:`bank-card__value`,dir:`ltr`},D={class:`bank-card__field`},O={class:`bank-card__value`,dir:`ltr`},k={class:`cash-page__footer`},A={class:`desktop-checkout-shell cash-desktop-page desktop-only`},j={class:`desktop-page__head`},M={class:`desktop-page__subtitle`},N={class:`cash-page__success cash-page__success--done`},de={class:`cash-page__success-title`},P={class:`accounts-panel`},F={class:`accounts-panel__title`},I={class:`desktop-grid-2`},L={class:`bank-card__head`},R={class:`bank-card__name`},z={class:`bank-card__icon`,"aria-hidden":`true`},B={class:`bank-card__field`},V={class:`bank-card__value`,dir:`ltr`},H={class:`bank-card__field`},fe={class:`bank-card__value`,dir:`ltr`},pe={class:`cash-desktop-page__actions`},U=d(h({__name:`CashSettlementView`,setup(r){let d=ee(),p=ae(),h=ie(),U=te([]),{orderId:W,order:G}=se(),K=e(()=>G.value?.companyName??``);m(W,async e=>{e&&!p.getOrderById(e)&&await p.fetchOrder(e)},{immediate:!0}),m(()=>G.value?.id,async e=>{if(!e||!G.value){U.value=[];return}h.companies.length||await h.loadCompanies(),U.value=await p.getBankAccountsForOrder(G.value)},{immediate:!0});let q=e(()=>G.value?.finalAmount?re(G.value.finalAmount):`—`),J=e(()=>{let e=G.value?.cashInvoiceExpiresAt;return e?`مهلت پرداخت این فاکتور تا ${ne(e)} است.`:`مبلغ فاکتور را به یکی از حساب‌های زیر واریز و رسید را ثبت کنید.`}),Y=e(()=>{let e=K.value.replace(/^شرکت\s+/u,``);return e?`شماره حساب‌های واریز — ${e}`:`شماره حساب‌های واریز`});function X(e){return e.replace(/\D/g,``).replace(/(\d{4})(?=\d)/g,`$1 `).trim()}function Z(e){return e.replace(/\s/g,``).toUpperCase().replace(/(.{4})/g,`$1 `).trim()}function Q(){d.replace(`/orders/${W.value}?track=1`)}function $(){d.replace(`/orders/${W.value}?track=1&receipt=1`)}return(e,r)=>(c(),a(i,null,[u(`div`,ce,[o(oe,{title:`تسویه نقدی`,"show-back":``,centered:``,onBack:r[0]||=e=>s(d).push(`/orders/${s(W)}`)}),u(`div`,le,[u(`div`,ue,[o(_,{size:56}),u(`p`,v,`مبلغ قابل پرداخت `+t(q.value),1),u(`p`,y,t(J.value),1)]),u(`section`,b,[u(`h2`,x,t(Y.value),1),(c(!0),a(i,null,l(U.value,e=>(c(),a(`article`,{key:e.id,class:`bank-card`},[u(`div`,S,[u(`span`,C,t(e.bankName),1),u(`span`,w,t(e.bankName.charAt(0)),1)]),u(`div`,T,[r[1]||=u(`span`,{class:`bank-card__label`},`شماره کارت`,-1),u(`strong`,E,t(X(e.cardNumber)),1)]),u(`div`,D,[r[2]||=u(`span`,{class:`bank-card__label`},`شماره شبا`,-1),u(`strong`,O,t(Z(e.iban)),1)])]))),128))])]),u(`div`,k,[o(g,{variant:`outline`,block:``,onClick:Q},{default:f(()=>[...r[3]||=[n(`مشاهده سفارش`,-1)]]),_:1}),o(g,{block:``,class:`cash-page__submit`,onClick:$},{default:f(()=>[...r[4]||=[n(` ثبت رسید پرداخت `,-1)]]),_:1})])]),u(`div`,A,[u(`header`,j,[r[5]||=u(`h1`,{class:`desktop-page__title`},`تسویه نقدی`,-1),u(`p`,M,t(J.value),1)]),u(`div`,N,[o(_,{size:56}),u(`p`,de,`مبلغ قابل پرداخت `+t(q.value),1)]),u(`section`,P,[u(`h2`,F,t(Y.value),1),u(`div`,I,[(c(!0),a(i,null,l(U.value,e=>(c(),a(`article`,{key:e.id,class:`bank-card`},[u(`div`,L,[u(`span`,R,t(e.bankName),1),u(`span`,z,t(e.bankName.charAt(0)),1)]),u(`div`,B,[r[6]||=u(`span`,{class:`bank-card__label`},`شماره کارت`,-1),u(`strong`,V,t(X(e.cardNumber)),1)]),u(`div`,H,[r[7]||=u(`span`,{class:`bank-card__label`},`شماره شبا`,-1),u(`strong`,fe,t(Z(e.iban)),1)])]))),128))])]),u(`div`,pe,[o(g,{variant:`outline`,class:`desktop-btn-compact`,onClick:Q},{default:f(()=>[...r[8]||=[n(` مشاهده سفارش `,-1)]]),_:1}),o(g,{class:`desktop-btn-compact`,onClick:$},{default:f(()=>[...r[9]||=[n(` ثبت رسید پرداخت `,-1)]]),_:1})])])],64))}}),[[`__scopeId`,`data-v-c299caf6`]]);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
@@ -1 +1 @@
import{A as e,Et as t,M as n,O as r,P as i,R as a,St as o,Y as s,Z as c,_ as l,g as u,j as d,mt as f,n as p,q as m,tt as h,z as g}from"./BasicIcon-C6boaehV.js";import{t as _}from"./catalog-s7nVUdlj.js";import{b as v}from"./index-B6MlTGUp.js";import{t as y}from"./EmptyState-C_QQeA_h.js";import{t as b}from"./PageSkeleton-uO-jhbNm.js";import{t as x}from"./PageHeader-CDQMUtCp.js";import{t as S}from"./SearchField-Dx2PcshV.js";var C={class:`page catalog-list-page`},w={class:`catalog-list-page__desk-head desktop-only`},T={class:`page__scroll page__scroll--with-nav catalog-list-page__scroll`},E={key:1,class:`catalog-list`},D=[`onClick`],O={class:`catalog-card__icon`},k={class:`catalog-card__info`},A={class:`catalog-card__count`},j=p(g({__name:`CatalogListView`,setup(p){let g=u(),j=l(),M=_(),N=f(!0),P=e(()=>g.params.companyId),F=e(()=>M.getCompany(P.value)),I=f(``),L=e(()=>M.fetchCatalogs(P.value,I.value));async function R(){N.value=!0,M.getCompany(P.value)||await M.loadCompanies(),await M.loadCatalogs(P.value),N.value=!1}m(R),h(P,R);function z(e){j.push({name:`catalog-detail`,params:{catalogId:e}})}function B(e){let t=M.getCatalog(e);return t?.productCount==null?M.fetchProducts(e).length:t.productCount}return(e,l)=>(s(),i(`div`,C,[a(x,{class:`mobile-only`,title:F.value?.name??`کالیته‌ها`,subtitle:`کالیته مورد نظر را انتخاب کنید`,"show-back":``,centered:``,onBack:l[0]||=e=>o(j).push(`/companies`)},null,8,[`title`]),d(`header`,w,[d(`h1`,null,t(F.value?.name??`کالیته‌ها`),1),l[2]||=d(`p`,null,`کالیته مورد نظر را انتخاب کنید`,-1)]),a(S,{modelValue:I.value,"onUpdate:modelValue":l[1]||=e=>I.value=e,placeholder:`جستجو بر اساس کد یا نام کالیته...`,class:`catalog-list-page__search`},null,8,[`modelValue`]),d(`div`,T,[N.value?(s(),n(b,{key:0,variant:`cards`,rows:5})):L.value.length>0?(s(),i(`div`,E,[(s(!0),i(r,null,c(L.value,e=>(s(),i(`button`,{key:e.id,type:`button`,class:`catalog-card`,onClick:t=>z(e.id)},[d(`span`,O,[a(v,{size:22})]),d(`div`,k,[d(`h3`,null,`کالیته کد `+t(e.code),1),d(`p`,null,t(e.name),1)]),d(`span`,A,t(B(e.id))+` محصول`,1)],8,D))),128))])):(s(),n(y,{key:2,variant:I.value?`search`:`catalogs`,message:I.value?`با این جستجو کالیته‌ای پیدا نشد`:`برای این شرکت هنوز کالیته‌ای ثبت نشده است`},null,8,[`variant`,`message`]))])]))}}),[[`__scopeId`,`data-v-53c183e1`]]);export{j as default}; import{A as e,Et as t,M as n,O as r,P as i,R as a,St as o,Y as s,Z as c,_ as l,g as u,j as d,mt as f,n as p,q as m,tt as h,z as g}from"./BasicIcon-C6boaehV.js";import{t as _}from"./catalog-s7nVUdlj.js";import{b as v}from"./index-DzzUgmYT.js";import{t as y}from"./EmptyState-C_QQeA_h.js";import{t as b}from"./PageSkeleton-uO-jhbNm.js";import{t as x}from"./PageHeader-BFnKSFNh.js";import{t as S}from"./SearchField-Dx2PcshV.js";var C={class:`page catalog-list-page`},w={class:`catalog-list-page__desk-head desktop-only`},T={class:`page__scroll page__scroll--with-nav catalog-list-page__scroll`},E={key:1,class:`catalog-list`},D=[`onClick`],O={class:`catalog-card__icon`},k={class:`catalog-card__info`},A={class:`catalog-card__count`},j=p(g({__name:`CatalogListView`,setup(p){let g=u(),j=l(),M=_(),N=f(!0),P=e(()=>g.params.companyId),F=e(()=>M.getCompany(P.value)),I=f(``),L=e(()=>M.fetchCatalogs(P.value,I.value));async function R(){N.value=!0,M.getCompany(P.value)||await M.loadCompanies(),await M.loadCatalogs(P.value),N.value=!1}m(R),h(P,R);function z(e){j.push({name:`catalog-detail`,params:{catalogId:e}})}function B(e){let t=M.getCatalog(e);return t?.productCount==null?M.fetchProducts(e).length:t.productCount}return(e,l)=>(s(),i(`div`,C,[a(x,{class:`mobile-only`,title:F.value?.name??`کالیته‌ها`,subtitle:`کالیته مورد نظر را انتخاب کنید`,"show-back":``,centered:``,onBack:l[0]||=e=>o(j).push(`/companies`)},null,8,[`title`]),d(`header`,w,[d(`h1`,null,t(F.value?.name??`کالیته‌ها`),1),l[2]||=d(`p`,null,`کالیته مورد نظر را انتخاب کنید`,-1)]),a(S,{modelValue:I.value,"onUpdate:modelValue":l[1]||=e=>I.value=e,placeholder:`جستجو بر اساس کد یا نام کالیته...`,class:`catalog-list-page__search`},null,8,[`modelValue`]),d(`div`,T,[N.value?(s(),n(b,{key:0,variant:`cards`,rows:5})):L.value.length>0?(s(),i(`div`,E,[(s(!0),i(r,null,c(L.value,e=>(s(),i(`button`,{key:e.id,type:`button`,class:`catalog-card`,onClick:t=>z(e.id)},[d(`span`,O,[a(v,{size:22})]),d(`div`,k,[d(`h3`,null,`کالیته کد `+t(e.code),1),d(`p`,null,t(e.name),1)]),d(`span`,A,t(B(e.id))+` محصول`,1)],8,D))),128))])):(s(),n(y,{key:2,variant:I.value?`search`:`catalogs`,message:I.value?`با این جستجو کالیته‌ای پیدا نشد`:`برای این شرکت هنوز کالیته‌ای ثبت نشده است`},null,8,[`variant`,`message`]))])]))}}),[[`__scopeId`,`data-v-53c183e1`]]);export{j as default};
@@ -1 +1 @@
import{A as e,Ct as t,Et as n,L as r,M as i,O as a,P as o,R as s,St as c,Y as l,Z as u,_ as d,g as f,it as p,j as m,mt as h,n as g,q as _,w as v,z as y}from"./BasicIcon-C6boaehV.js";import{o as b}from"./format-BHJptHdF.js";import{t as x}from"./PageSkeleton-uO-jhbNm.js";import{t as S}from"./crm-CtglbPsN.js";import{t as C}from"./PageHeader-CDQMUtCp.js";import{t as w}from"./catalogRequests-D1kBtGqL.js";var T={class:`page`},E={class:`page__scroll page__scroll--with-nav body`},D={class:`hint`},O={key:1,class:`error`},k=[`value`,`onInput`],A={class:`note`},j={class:`summary`},M=[`disabled`],N=g(y({__name:`CatalogRequestView`,setup(g){let y=f(),N=d(),P=S(),F=w(),I=h(!0),L=h(!1),R=h(null),z=h(``),B=h([]),V=h({}),H=e(()=>String(y.query.productId??``)),U=e(()=>String(y.query.catalogId??``)),W=e(()=>B.value.filter(e=>(V.value[e.id]??0)>0).map(e=>({productId:e.id,productName:e.name,productCode:e.code,quantity:V.value[e.id]??0})));_(async()=>{I.value=!0,R.value=null;try{let e=await P.loadBrowseProducts(U.value?{catalogId:U.value}:void 0),t=H.value;B.value=t?[...e].sort((e,n)=>Number(n.id===t)-Number(e.id===t)):e,t&&(V.value={...V.value,[t]:1})}catch(e){R.value=e instanceof Error?e.message:`بارگذاری محصولات ناموفق بود.`}finally{I.value=!1}});function G(e,t){V.value={...V.value,[e]:Math.max(0,Math.floor(t)||0)}}async function K(){L.value=!0,R.value=null;try{let e=await F.create({items:W.value,note:z.value});if(!e.success){R.value=e.message;return}N.push(`/catalog-requests`)}finally{L.value=!1}}return(e,d)=>(l(),o(`div`,T,[s(C,{title:`درخواست کالیته`,"show-back":``,centered:``,onBack:d[0]||=e=>c(N).back()}),m(`div`,E,[m(`p`,D,n(H.value?`این محصول برای درخواست کالیته انتخاب شده است. در صورت نیاز اقلام دیگر را هم اضافه کنید.`:`محصولات و تعداد اقلام موردنظر را مشخص کنید.`),1),I.value?(l(),i(x,{key:0,variant:`form`})):R.value?(l(),o(`p`,O,n(R.value),1)):(l(),o(a,{key:2},[(l(!0),o(a,null,u(B.value,e=>(l(),o(`article`,{key:e.id,class:t([`row`,{"row--selected":H.value===e.id}])},[m(`div`,null,[m(`strong`,null,n(e.name),1),m(`span`,null,`کد `+n(e.code),1)]),m(`input`,{class:`qty`,type:`number`,min:`0`,value:V.value[e.id]??0,onInput:t=>G(e.id,Number(t.target.value))},null,40,k)],2))),128)),m(`label`,A,[d[3]||=r(` توضیحات `,-1),p(m(`textarea`,{"onUpdate:modelValue":d[1]||=e=>z.value=e,rows:`3`,placeholder:`آدرس یا توضیحات اختیاری`},null,512),[[v,z.value]])]),m(`p`,j,`اقلام انتخاب‌شده: `+n(c(b)(W.value.length)),1),m(`button`,{type:`button`,class:`submit`,disabled:L.value,onClick:K},n(L.value?`در حال ثبت...`:`ثبت درخواست کالیته`),9,M),m(`button`,{type:`button`,class:`linkish`,onClick:d[2]||=e=>c(N).push(`/catalog-requests`)},` وضعیت درخواست‌های قبلی `)],64))])]))}}),[[`__scopeId`,`data-v-6bde0457`]]);export{N as default}; import{A as e,Ct as t,Et as n,L as r,M as i,O as a,P as o,R as s,St as c,Y as l,Z as u,_ as d,g as f,it as p,j as m,mt as h,n as g,q as _,w as v,z as y}from"./BasicIcon-C6boaehV.js";import{o as b}from"./format-BHJptHdF.js";import{t as x}from"./PageSkeleton-uO-jhbNm.js";import{t as S}from"./crm-CtglbPsN.js";import{t as C}from"./PageHeader-BFnKSFNh.js";import{t as w}from"./catalogRequests-D1kBtGqL.js";var T={class:`page`},E={class:`page__scroll page__scroll--with-nav body`},D={class:`hint`},O={key:1,class:`error`},k=[`value`,`onInput`],A={class:`note`},j={class:`summary`},M=[`disabled`],N=g(y({__name:`CatalogRequestView`,setup(g){let y=f(),N=d(),P=S(),F=w(),I=h(!0),L=h(!1),R=h(null),z=h(``),B=h([]),V=h({}),H=e(()=>String(y.query.productId??``)),U=e(()=>String(y.query.catalogId??``)),W=e(()=>B.value.filter(e=>(V.value[e.id]??0)>0).map(e=>({productId:e.id,productName:e.name,productCode:e.code,quantity:V.value[e.id]??0})));_(async()=>{I.value=!0,R.value=null;try{let e=await P.loadBrowseProducts(U.value?{catalogId:U.value}:void 0),t=H.value;B.value=t?[...e].sort((e,n)=>Number(n.id===t)-Number(e.id===t)):e,t&&(V.value={...V.value,[t]:1})}catch(e){R.value=e instanceof Error?e.message:`بارگذاری محصولات ناموفق بود.`}finally{I.value=!1}});function G(e,t){V.value={...V.value,[e]:Math.max(0,Math.floor(t)||0)}}async function K(){L.value=!0,R.value=null;try{let e=await F.create({items:W.value,note:z.value});if(!e.success){R.value=e.message;return}N.push(`/catalog-requests`)}finally{L.value=!1}}return(e,d)=>(l(),o(`div`,T,[s(C,{title:`درخواست کالیته`,"show-back":``,centered:``,onBack:d[0]||=e=>c(N).back()}),m(`div`,E,[m(`p`,D,n(H.value?`این محصول برای درخواست کالیته انتخاب شده است. در صورت نیاز اقلام دیگر را هم اضافه کنید.`:`محصولات و تعداد اقلام موردنظر را مشخص کنید.`),1),I.value?(l(),i(x,{key:0,variant:`form`})):R.value?(l(),o(`p`,O,n(R.value),1)):(l(),o(a,{key:2},[(l(!0),o(a,null,u(B.value,e=>(l(),o(`article`,{key:e.id,class:t([`row`,{"row--selected":H.value===e.id}])},[m(`div`,null,[m(`strong`,null,n(e.name),1),m(`span`,null,`کد `+n(e.code),1)]),m(`input`,{class:`qty`,type:`number`,min:`0`,value:V.value[e.id]??0,onInput:t=>G(e.id,Number(t.target.value))},null,40,k)],2))),128)),m(`label`,A,[d[3]||=r(` توضیحات `,-1),p(m(`textarea`,{"onUpdate:modelValue":d[1]||=e=>z.value=e,rows:`3`,placeholder:`آدرس یا توضیحات اختیاری`},null,512),[[v,z.value]])]),m(`p`,j,`اقلام انتخاب‌شده: `+n(c(b)(W.value.length)),1),m(`button`,{type:`button`,class:`submit`,disabled:L.value,onClick:K},n(L.value?`در حال ثبت...`:`ثبت درخواست کالیته`),9,M),m(`button`,{type:`button`,class:`linkish`,onClick:d[2]||=e=>c(N).push(`/catalog-requests`)},` وضعیت درخواست‌های قبلی `)],64))])]))}}),[[`__scopeId`,`data-v-6bde0457`]]);export{N as default};
@@ -1 +1 @@
import{Et as e,N as t,O as n,P as r,R as i,St as a,Y as o,Z as s,_ as c,j as l,n as u,q as d,z as f}from"./BasicIcon-C6boaehV.js";import{o as p}from"./format-BHJptHdF.js";import{t as m}from"./PageHeader-CDQMUtCp.js";import{t as h}from"./catalogRequests-D1kBtGqL.js";var g={class:`page`},_={class:`page__scroll page__scroll--with-nav body`},v={key:0,class:`empty`},y={class:`card__top`},b={class:`badge`},x={key:0,class:`note`},S=u(f({__name:`CatalogRequestsView`,setup(u){let f=c(),S=h();return d(()=>S.hydrate()),(c,u)=>(o(),r(`div`,g,[i(m,{title:`وضعیت کالیته‌ها`,"show-back":``,centered:``,onBack:u[0]||=e=>a(f).push(`/catalogs`)}),l(`div`,_,[l(`button`,{type:`button`,class:`new-btn`,onClick:u[1]||=e=>a(f).push(`/catalog-request`)},` درخواست کالیته جدید `),a(S).sorted.length?t(``,!0):(o(),r(`p`,v,`هنوز درخواستی ثبت نکرده‌اید.`)),(o(!0),r(n,null,s(a(S).sorted,i=>(o(),r(`article`,{key:i.id,class:`card`},[l(`div`,y,[l(`strong`,null,`درخواست `+e(i.id.slice(-6)),1),l(`span`,b,e(a(S).statusLabel(i.status)),1)]),l(`p`,null,e(i.createdAt.slice(0,10)),1),l(`p`,null,e(a(p)(i.items.length))+` قلم`,1),l(`ul`,null,[(o(!0),r(n,null,s(i.items,t=>(o(),r(`li`,{key:t.productId},e(t.productName)+` × `+e(a(p)(t.quantity)),1))),128))]),i.note?(o(),r(`p`,x,e(i.note),1)):t(``,!0)]))),128))])]))}}),[[`__scopeId`,`data-v-fc04181d`]]);export{S as default}; import{Et as e,N as t,O as n,P as r,R as i,St as a,Y as o,Z as s,_ as c,j as l,n as u,q as d,z as f}from"./BasicIcon-C6boaehV.js";import{o as p}from"./format-BHJptHdF.js";import{t as m}from"./PageHeader-BFnKSFNh.js";import{t as h}from"./catalogRequests-D1kBtGqL.js";var g={class:`page`},_={class:`page__scroll page__scroll--with-nav body`},v={key:0,class:`empty`},y={class:`card__top`},b={class:`badge`},x={key:0,class:`note`},S=u(f({__name:`CatalogRequestsView`,setup(u){let f=c(),S=h();return d(()=>S.hydrate()),(c,u)=>(o(),r(`div`,g,[i(m,{title:`وضعیت کالیته‌ها`,"show-back":``,centered:``,onBack:u[0]||=e=>a(f).push(`/catalogs`)}),l(`div`,_,[l(`button`,{type:`button`,class:`new-btn`,onClick:u[1]||=e=>a(f).push(`/catalog-request`)},` درخواست کالیته جدید `),a(S).sorted.length?t(``,!0):(o(),r(`p`,v,`هنوز درخواستی ثبت نکرده‌اید.`)),(o(!0),r(n,null,s(a(S).sorted,i=>(o(),r(`article`,{key:i.id,class:`card`},[l(`div`,y,[l(`strong`,null,`درخواست `+e(i.id.slice(-6)),1),l(`span`,b,e(a(S).statusLabel(i.status)),1)]),l(`p`,null,e(i.createdAt.slice(0,10)),1),l(`p`,null,e(a(p)(i.items.length))+` قلم`,1),l(`ul`,null,[(o(!0),r(n,null,s(i.items,t=>(o(),r(`li`,{key:t.productId},e(t.productName)+` × `+e(a(p)(t.quantity)),1))),128))]),i.note?(o(),r(`p`,x,e(i.note),1)):t(``,!0)]))),128))])]))}}),[[`__scopeId`,`data-v-fc04181d`]]);export{S as default};
@@ -1 +1 @@
import{A as e,Ct as t,Et as n,M as r,N as i,O as a,P as o,R as s,St as c,Tt as l,Y as u,Z as d,_ as f,g as p,j as m,mt as h,n as g,q as _,z as v}from"./BasicIcon-C6boaehV.js";import{o as y}from"./format-BHJptHdF.js";import{t as b}from"./catalog-s7nVUdlj.js";import{i as x}from"./index-B6MlTGUp.js";import{t as S}from"./IconChevronBack-DORV2H0q.js";import{t as C}from"./PageSkeleton-uO-jhbNm.js";import{t as ee}from"./crm-CtglbPsN.js";import{t as te}from"./useGoBack-CgNp0uP9.js";import{a as w,n as T}from"./catalogGallery-DMNB35I1.js";import{t as E}from"./SearchField-Dx2PcshV.js";import{t as D}from"./IconSearch-DOnq8ybc.js";var O={class:`page calite-hub`},k={class:`calite-hub__nav`},A={class:`calite-hub__nav-end`},j=[`aria-label`,`aria-pressed`],M={class:`page__scroll page__scroll--with-nav calite-hub__scroll`},N={class:`calite-hub__chips`,role:`tablist`,"aria-label":`مرتب‌سازی کالیته‌ها`},P=[`onClick`],F={key:2,class:`calite-hub__empty`},I={key:3,class:`calite-hub__list`},L=[`onClick`],R={key:0,class:`calite-card__badge`},z={class:`calite-card__copy`,dir:`rtl`},ne={class:`mr-4 text-white`},B={class:`text-lg font-bold`},V={class:`text-md`},H={class:`text-xs mt-4`},U=g(v({__name:`CatalogsHubView`,setup(g){let v=p(),U=f(),{goBack:W}=te(`/`),G=b(),K=ee(),q=h([]),J=h([]),Y=h(!0),X=h(!1),Z=h(``),Q=h(`all`),re=[{id:`all`,label:`همه`},{id:`newest`,label:`جدیدترین`},{id:`popular`,label:`محبوب‌ترین`},{id:`season`,label:`بر اساس فصل`}],ie=e(()=>T(q.value,J.value)),$=e(()=>w(ie.value,Q.value,Z.value));_(async()=>{try{let e=await G.loadCompanies(),[t,n]=await Promise.all([Promise.all(e.map(e=>G.loadCatalogs(e.id))),K.loadBrowseProducts().catch(()=>[])]);q.value=t.flat(),J.value=n;let r=String(v.query.id??``);r&&q.value.some(e=>e.id===r)&&U.replace({name:`catalog-detail`,params:{catalogId:r}})}catch{q.value=[]}finally{Y.value=!1}});function ae(e){U.push({name:`catalog-detail`,params:{catalogId:e}})}function oe(){X.value=!X.value,X.value||(Z.value=``)}function se(e){let t=e.season===`winter`?`مناسب زمستان`:e.season===`summer`?`مناسب تابستان`:`مناسب چهار فصل`;return`${y(e.patternCount)} طرح • ${t}`}return(e,f)=>(u(),o(`div`,O,[m(`header`,k,[m(`button`,{type:`button`,class:`icon-btn mobile-only`,"aria-label":`بازگشت`,onClick:f[0]||=(...e)=>c(W)&&c(W)(...e)},[s(S,{size:20})]),f[2]||=m(`h1`,null,`کالیته‌ها`,-1),m(`div`,A,[s(x,{variant:`inline`,class:`mobile-only`}),m(`button`,{type:`button`,class:`icon-btn`,"aria-label":X.value?`بستن جستجو`:`جستجو`,"aria-pressed":X.value,onClick:oe},[s(D,{size:20})],8,j)])]),m(`div`,M,[f[4]||=m(`p`,{class:`calite-hub__lead`},`مجموعه‌ای از بهترین پارچه‌ها، در یک نگاه`,-1),X.value?(u(),r(E,{key:0,"model-value":Z.value,placeholder:`جستجوی نام کالیته...`,"onUpdate:modelValue":f[1]||=e=>Z.value=e},null,8,[`model-value`])):i(``,!0),m(`div`,N,[(u(),o(a,null,d(re,e=>m(`button`,{key:e.id,type:`button`,class:t([`calite-chip`,{"is-on":Q.value===e.id}]),onClick:t=>Q.value=e.id},n(e.label),11,P)),64))]),Y.value?(u(),r(C,{key:1,variant:`cards`,rows:4})):$.value.length?(u(),o(`div`,I,[(u(!0),o(a,null,d($.value,e=>(u(),o(`button`,{key:e.id,type:`button`,class:`calite-card`,dir:`ltr`,onClick:t=>ae(e.id)},[m(`span`,{class:`calite-card__photo`,style:l(e.imageUrl?{backgroundImage:`url(${e.imageUrl})`}:void 0)},null,4),e.badge?(u(),o(`span`,R,n(e.badge===`new`?`جدید`:`پرفروش`),1)):i(``,!0),m(`div`,z,[f[3]||=m(`svg`,{class:`calite-card__arrow`,width:`12`,height:`16`,viewBox:`0 0 8 12`,fill:`none`,"aria-hidden":`true`},[m(`path`,{d:`M1.5 1.5 6 6l-4.5 4.5`,stroke:`#fff`,"stroke-width":`1.4`,"stroke-linecap":`round`,"stroke-linejoin":`round`})],-1),m(`div`,ne,[m(`div`,B,n(e.title),1),m(`div`,V,n(e.subtitle),1),m(`div`,H,n(se(e)),1)])])],8,L))),128))])):(u(),o(`p`,F,`کالیته‌ای یافت نشد.`))])]))}}),[[`__scopeId`,`data-v-a6e04708`]]);export{U as default}; import{A as e,Ct as t,Et as n,M as r,N as i,O as a,P as o,R as s,St as c,Tt as l,Y as u,Z as d,_ as f,g as p,j as m,mt as h,n as g,q as _,z as v}from"./BasicIcon-C6boaehV.js";import{o as y}from"./format-BHJptHdF.js";import{t as b}from"./catalog-s7nVUdlj.js";import{i as x}from"./index-DzzUgmYT.js";import{t as S}from"./IconChevronBack-DORV2H0q.js";import{t as C}from"./PageSkeleton-uO-jhbNm.js";import{t as ee}from"./crm-CtglbPsN.js";import{t as te}from"./useGoBack-CgNp0uP9.js";import{a as w,n as T}from"./catalogGallery-DMNB35I1.js";import{t as E}from"./SearchField-Dx2PcshV.js";import{t as D}from"./IconSearch-DOnq8ybc.js";var O={class:`page calite-hub`},k={class:`calite-hub__nav`},A={class:`calite-hub__nav-end`},j=[`aria-label`,`aria-pressed`],M={class:`page__scroll page__scroll--with-nav calite-hub__scroll`},N={class:`calite-hub__chips`,role:`tablist`,"aria-label":`مرتب‌سازی کالیته‌ها`},P=[`onClick`],F={key:2,class:`calite-hub__empty`},I={key:3,class:`calite-hub__list`},L=[`onClick`],R={key:0,class:`calite-card__badge`},z={class:`calite-card__copy`,dir:`rtl`},ne={class:`mr-4 text-white`},B={class:`text-lg font-bold`},V={class:`text-md`},H={class:`text-xs mt-4`},U=g(v({__name:`CatalogsHubView`,setup(g){let v=p(),U=f(),{goBack:W}=te(`/`),G=b(),K=ee(),q=h([]),J=h([]),Y=h(!0),X=h(!1),Z=h(``),Q=h(`all`),re=[{id:`all`,label:`همه`},{id:`newest`,label:`جدیدترین`},{id:`popular`,label:`محبوب‌ترین`},{id:`season`,label:`بر اساس فصل`}],ie=e(()=>T(q.value,J.value)),$=e(()=>w(ie.value,Q.value,Z.value));_(async()=>{try{let e=await G.loadCompanies(),[t,n]=await Promise.all([Promise.all(e.map(e=>G.loadCatalogs(e.id))),K.loadBrowseProducts().catch(()=>[])]);q.value=t.flat(),J.value=n;let r=String(v.query.id??``);r&&q.value.some(e=>e.id===r)&&U.replace({name:`catalog-detail`,params:{catalogId:r}})}catch{q.value=[]}finally{Y.value=!1}});function ae(e){U.push({name:`catalog-detail`,params:{catalogId:e}})}function oe(){X.value=!X.value,X.value||(Z.value=``)}function se(e){let t=e.season===`winter`?`مناسب زمستان`:e.season===`summer`?`مناسب تابستان`:`مناسب چهار فصل`;return`${y(e.patternCount)} طرح • ${t}`}return(e,f)=>(u(),o(`div`,O,[m(`header`,k,[m(`button`,{type:`button`,class:`icon-btn mobile-only`,"aria-label":`بازگشت`,onClick:f[0]||=(...e)=>c(W)&&c(W)(...e)},[s(S,{size:20})]),f[2]||=m(`h1`,null,`کالیته‌ها`,-1),m(`div`,A,[s(x,{variant:`inline`,class:`mobile-only`}),m(`button`,{type:`button`,class:`icon-btn`,"aria-label":X.value?`بستن جستجو`:`جستجو`,"aria-pressed":X.value,onClick:oe},[s(D,{size:20})],8,j)])]),m(`div`,M,[f[4]||=m(`p`,{class:`calite-hub__lead`},`مجموعه‌ای از بهترین پارچه‌ها، در یک نگاه`,-1),X.value?(u(),r(E,{key:0,"model-value":Z.value,placeholder:`جستجوی نام کالیته...`,"onUpdate:modelValue":f[1]||=e=>Z.value=e},null,8,[`model-value`])):i(``,!0),m(`div`,N,[(u(),o(a,null,d(re,e=>m(`button`,{key:e.id,type:`button`,class:t([`calite-chip`,{"is-on":Q.value===e.id}]),onClick:t=>Q.value=e.id},n(e.label),11,P)),64))]),Y.value?(u(),r(C,{key:1,variant:`cards`,rows:4})):$.value.length?(u(),o(`div`,I,[(u(!0),o(a,null,d($.value,e=>(u(),o(`button`,{key:e.id,type:`button`,class:`calite-card`,dir:`ltr`,onClick:t=>ae(e.id)},[m(`span`,{class:`calite-card__photo`,style:l(e.imageUrl?{backgroundImage:`url(${e.imageUrl})`}:void 0)},null,4),e.badge?(u(),o(`span`,R,n(e.badge===`new`?`جدید`:`پرفروش`),1)):i(``,!0),m(`div`,z,[f[3]||=m(`svg`,{class:`calite-card__arrow`,width:`12`,height:`16`,viewBox:`0 0 8 12`,fill:`none`,"aria-hidden":`true`},[m(`path`,{d:`M1.5 1.5 6 6l-4.5 4.5`,stroke:`#fff`,"stroke-width":`1.4`,"stroke-linecap":`round`,"stroke-linejoin":`round`})],-1),m(`div`,ne,[m(`div`,B,n(e.title),1),m(`div`,V,n(e.subtitle),1),m(`div`,H,n(se(e)),1)])])],8,L))),128))])):(u(),o(`p`,F,`کالیته‌ای یافت نشد.`))])]))}}),[[`__scopeId`,`data-v-a6e04708`]]);export{U as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{A as e,Et as t,P as n,R as r,St as i,Y as a,_ as o,j as s,n as c,tt as l,z as u}from"./BasicIcon-C6boaehV.js";import{n as d}from"./format-BHJptHdF.js";import{d as f}from"./index-B6MlTGUp.js";import{t as p}from"./IconEditPen-DLV3_yq2.js";import{t as m}from"./PageHeader-CDQMUtCp.js";import{t as h}from"./useRequirePricedOrder-zO2gkEXW.js";import{t as g}from"./IconCheckBook-DwIUojJc.js";var _={class:`page check-mode-page`},v={class:`page__scroll check-mode-page__scroll`},y={class:`check-mode-invoice`},b={class:`check-mode-invoice__value`},x={class:`mode-options`},S={class:`mode-card__icon`},C={class:`mode-card__icon`},w=c(u({__name:`CheckModeSelectView`,setup(c){let u=o(),w=f(),{orderId:T,order:E}=h(),D=e(()=>E.value?.finalAmount?d(E.value.finalAmount):`—`);l(T,async e=>{e&&(w.getOrderById(e)||await w.fetchOrder(e),w.draftOrderId!==e&&w.startCheckDraft(e))},{immediate:!0});function O(e){w.setCheckMode(e),u.push(`/orders/${T.value}/check-form`)}return(e,o)=>(a(),n(`div`,_,[r(m,{title:`تسویه چکی`,subtitle:`بر اساس مبلغ فاکتور، نحوه ثبت چک را انتخاب کنید`,"show-back":``,centered:``,onBack:o[0]||=e=>i(u).push(`/orders/${i(T)}`)}),s(`div`,v,[s(`div`,y,[o[3]||=s(`span`,{class:`check-mode-invoice__label`},`مبلغ فاکتور`,-1),s(`strong`,b,t(D.value),1)]),s(`div`,x,[s(`button`,{type:`button`,class:`mode-card`,onClick:o[1]||=e=>O(`full`)},[s(`span`,S,[r(g,{size:36})]),o[4]||=s(`h3`,{class:`mode-card__title`},`ثبت کامل (پیشنهادی)`,-1),o[5]||=s(`p`,{class:`mode-card__desc`},`وارد کردن همه اطلاعات چک`,-1)]),s(`button`,{type:`button`,class:`mode-card`,onClick:o[2]||=e=>O(`brief`)},[s(`span`,C,[r(p,{size:36})]),o[6]||=s(`h3`,{class:`mode-card__title`},`ثبت مختصر`,-1),o[7]||=s(`p`,{class:`mode-card__desc`},`وارد کردن حداقل اطلاعات`,-1)])])])]))}}),[[`__scopeId`,`data-v-680065b0`]]);export{w as default}; import{A as e,Et as t,P as n,R as r,St as i,Y as a,_ as o,j as s,n as c,tt as l,z as u}from"./BasicIcon-C6boaehV.js";import{n as d}from"./format-BHJptHdF.js";import{d as f}from"./index-DzzUgmYT.js";import{t as p}from"./IconEditPen-DLV3_yq2.js";import{t as m}from"./PageHeader-BFnKSFNh.js";import{t as h}from"./useRequirePricedOrder-CxEozNXk.js";import{t as g}from"./IconCheckBook-DwIUojJc.js";var _={class:`page check-mode-page`},v={class:`page__scroll check-mode-page__scroll`},y={class:`check-mode-invoice`},b={class:`check-mode-invoice__value`},x={class:`mode-options`},S={class:`mode-card__icon`},C={class:`mode-card__icon`},w=c(u({__name:`CheckModeSelectView`,setup(c){let u=o(),w=f(),{orderId:T,order:E}=h(),D=e(()=>E.value?.finalAmount?d(E.value.finalAmount):`—`);l(T,async e=>{e&&(w.getOrderById(e)||await w.fetchOrder(e),w.draftOrderId!==e&&w.startCheckDraft(e))},{immediate:!0});function O(e){w.setCheckMode(e),u.push(`/orders/${T.value}/check-form`)}return(e,o)=>(a(),n(`div`,_,[r(m,{title:`تسویه چکی`,subtitle:`بر اساس مبلغ فاکتور، نحوه ثبت چک را انتخاب کنید`,"show-back":``,centered:``,onBack:o[0]||=e=>i(u).push(`/orders/${i(T)}`)}),s(`div`,v,[s(`div`,y,[o[3]||=s(`span`,{class:`check-mode-invoice__label`},`مبلغ فاکتور`,-1),s(`strong`,b,t(D.value),1)]),s(`div`,x,[s(`button`,{type:`button`,class:`mode-card`,onClick:o[1]||=e=>O(`full`)},[s(`span`,S,[r(g,{size:36})]),o[4]||=s(`h3`,{class:`mode-card__title`},`ثبت کامل (پیشنهادی)`,-1),o[5]||=s(`p`,{class:`mode-card__desc`},`وارد کردن همه اطلاعات چک`,-1)]),s(`button`,{type:`button`,class:`mode-card`,onClick:o[2]||=e=>O(`brief`)},[s(`span`,C,[r(p,{size:36})]),o[6]||=s(`h3`,{class:`mode-card__title`},`ثبت مختصر`,-1),o[7]||=s(`p`,{class:`mode-card__desc`},`وارد کردن حداقل اطلاعات`,-1)])])])]))}}),[[`__scopeId`,`data-v-680065b0`]]);export{w as default};
@@ -1 +1 @@
import{A as e,Ct as t,Et as n,L as r,M as i,O as a,P as o,R as s,St as c,Y as l,Z as u,_ as d,et as f,j as p,mt as m,n as h,q as g,z as _}from"./BasicIcon-C6boaehV.js";import{t as v}from"./catalog-s7nVUdlj.js";import{T as y,o as b}from"./index-B6MlTGUp.js";import{t as x}from"./IconChevronBack-DORV2H0q.js";import{n as S,t as C}from"./IconFamBaftLogo-BaP40Dff.js";import{t as w}from"./PageSkeleton-uO-jhbNm.js";import{t as T}from"./useGoBack-CgNp0uP9.js";var E={class:`company-select-page`},D={class:`company-select-page__header mobile-only`},O={class:`company-select-page__nav`},k={class:`company-select-page__list`},A=[`onClick`],j={class:`company-brand-card__logo`},M={class:`company-brand-card__name`},N={class:`company-brand-card__desc`},P={class:`company-brand-card__cta desktop-only`},F=h(_({__name:`MobileCompanySelect`,props:{companies:{},cardVariant:{type:Function},companyDescription:{type:Function},companyLogo:{type:Function}},emits:[`select`,`back`],setup(e){return(c,d)=>(l(),o(`div`,E,[p(`header`,D,[p(`div`,O,[p(`button`,{type:`button`,class:`nav-btn`,"aria-label":`بازگشت`,onClick:d[0]||=e=>c.$emit(`back`)},[s(x,{size:20})]),d[1]||=p(`h1`,{class:`company-select-page__title`},`انتخاب شرکت`,-1),d[2]||=p(`span`,{class:`nav-btn nav-btn--spacer`,"aria-hidden":`true`},null,-1)]),d[3]||=p(`p`,{class:`company-select-page__subtitle`},`شرکت مورد نظر خود را انتخاب کنید`,-1)]),d[5]||=p(`header`,{class:`company-select-page__desk-head desktop-only`},[p(`h1`,null,`انتخاب شرکت`),p(`p`,null,`شرکت موردنظر را انتخاب کنید تا کاتالوگ و سفارش‌گذاری شروع شود`)],-1),p(`div`,k,[(l(!0),o(a,null,u(e.companies,a=>(l(),o(`button`,{key:a.id,type:`button`,class:t([`company-brand-card`,`company-brand-card--${e.cardVariant(a.slug)}`]),onClick:e=>c.$emit(`select`,a.id)},[p(`span`,j,[(l(),i(f(e.companyLogo(a.slug)),{size:72}))]),p(`h2`,M,n(a.name),1),p(`p`,N,n(e.companyDescription(a)),1),p(`span`,P,[d[4]||=r(` ورود به کاتالوگ‌ها `,-1),s(b,{size:16})])],10,A))),128))])]))}}),[[`__scopeId`,`data-v-336d01ae`]]),I={key:0,class:`page page__scroll`},L=_({__name:`CompanySelectView`,setup(t){let n=d(),{goBack:r}=T(`/`),a=v(),u=y(),f=m(!0),p=e(()=>a.getActiveCompanies()),h={par:{description:`تولید و عرضه انواع پارچه`,logo:S},"fam-baft":{description:`تولید و عرضه انواع پارچه`,logo:C}};g(async()=>{await a.loadCompanies(),f.value=!1});function _(e){let t=a.getCompany(e);t&&(u.selectCompany(t),n.push(`/companies/${e}/catalogs`))}function b(e){return e===`par`?`par`:`fam-baft`}function x(e){return h[e.slug]?.description??e.description}function E(e){return h[e]?.logo??S}return(e,t)=>f.value?(l(),o(`div`,I,[s(w,{variant:`cards`})])):(l(),i(F,{key:1,companies:p.value,"card-variant":b,"company-description":x,"company-logo":E,onSelect:_,onBack:c(r)},null,8,[`companies`,`onBack`]))}});export{L as default}; import{A as e,Ct as t,Et as n,L as r,M as i,O as a,P as o,R as s,St as c,Y as l,Z as u,_ as d,et as f,j as p,mt as m,n as h,q as g,z as _}from"./BasicIcon-C6boaehV.js";import{t as v}from"./catalog-s7nVUdlj.js";import{T as y,o as b}from"./index-DzzUgmYT.js";import{t as x}from"./IconChevronBack-DORV2H0q.js";import{n as S,t as C}from"./IconFamBaftLogo-BaP40Dff.js";import{t as w}from"./PageSkeleton-uO-jhbNm.js";import{t as T}from"./useGoBack-CgNp0uP9.js";var E={class:`company-select-page`},D={class:`company-select-page__header mobile-only`},O={class:`company-select-page__nav`},k={class:`company-select-page__list`},A=[`onClick`],j={class:`company-brand-card__logo`},M={class:`company-brand-card__name`},N={class:`company-brand-card__desc`},P={class:`company-brand-card__cta desktop-only`},F=h(_({__name:`MobileCompanySelect`,props:{companies:{},cardVariant:{type:Function},companyDescription:{type:Function},companyLogo:{type:Function}},emits:[`select`,`back`],setup(e){return(c,d)=>(l(),o(`div`,E,[p(`header`,D,[p(`div`,O,[p(`button`,{type:`button`,class:`nav-btn`,"aria-label":`بازگشت`,onClick:d[0]||=e=>c.$emit(`back`)},[s(x,{size:20})]),d[1]||=p(`h1`,{class:`company-select-page__title`},`انتخاب شرکت`,-1),d[2]||=p(`span`,{class:`nav-btn nav-btn--spacer`,"aria-hidden":`true`},null,-1)]),d[3]||=p(`p`,{class:`company-select-page__subtitle`},`شرکت مورد نظر خود را انتخاب کنید`,-1)]),d[5]||=p(`header`,{class:`company-select-page__desk-head desktop-only`},[p(`h1`,null,`انتخاب شرکت`),p(`p`,null,`شرکت موردنظر را انتخاب کنید تا کاتالوگ و سفارش‌گذاری شروع شود`)],-1),p(`div`,k,[(l(!0),o(a,null,u(e.companies,a=>(l(),o(`button`,{key:a.id,type:`button`,class:t([`company-brand-card`,`company-brand-card--${e.cardVariant(a.slug)}`]),onClick:e=>c.$emit(`select`,a.id)},[p(`span`,j,[(l(),i(f(e.companyLogo(a.slug)),{size:72}))]),p(`h2`,M,n(a.name),1),p(`p`,N,n(e.companyDescription(a)),1),p(`span`,P,[d[4]||=r(` ورود به کاتالوگ‌ها `,-1),s(b,{size:16})])],10,A))),128))])]))}}),[[`__scopeId`,`data-v-336d01ae`]]),I={key:0,class:`page page__scroll`},L=_({__name:`CompanySelectView`,setup(t){let n=d(),{goBack:r}=T(`/`),a=v(),u=y(),f=m(!0),p=e(()=>a.getActiveCompanies()),h={par:{description:`تولید و عرضه انواع پارچه`,logo:S},"fam-baft":{description:`تولید و عرضه انواع پارچه`,logo:C}};g(async()=>{await a.loadCompanies(),f.value=!1});function _(e){let t=a.getCompany(e);t&&(u.selectCompany(t),n.push(`/companies/${e}/catalogs`))}function b(e){return e===`par`?`par`:`fam-baft`}function x(e){return h[e.slug]?.description??e.description}function E(e){return h[e]?.logo??S}return(e,t)=>f.value?(l(),o(`div`,I,[s(w,{variant:`cards`})])):(l(),i(F,{key:1,companies:p.value,"card-variant":b,"company-description":x,"company-logo":E,onSelect:_,onBack:c(r)},null,8,[`companies`,`onBack`]))}});export{L as default};
@@ -1 +1 @@
import{L as e,P as t,R as n,St as r,Y as i,_ as a,j as o,mt as s,n as c,rt as l,z as u}from"./BasicIcon-C6boaehV.js";import{t as d}from"./http-BC1hNfZ4.js";import{c as f}from"./format-BHJptHdF.js";import{a as p}from"./validation-BD7oi-2I.js";import{c as m,n as h}from"./index-B6MlTGUp.js";import{t as g}from"./FormErrorBanner-CjdBfIQT.js";import{n as _,r as v,t as y}from"./formErrors-DglFjYkZ.js";import{t as b}from"./crm-CtglbPsN.js";import{t as x}from"./useGoBack-CgNp0uP9.js";import{t as S}from"./PageHeader-CDQMUtCp.js";import{t as C}from"./BaseInput-qoEXOHCQ.js";var w={class:`page customer-create-page`},T={class:`page__scroll customer-create-scroll`},E={class:`form-card`},D={class:`footer`},O=c(u({__name:`CustomerCreateView`,setup(c){let u=a(),O=m(),k=b(),{goBack:A}=x(`/customers`),j=s(``),M=s(``),N=s(``),P=s(``),F=s({}),I=s(``),L=s(!1);function R(e){F.value=_(F.value,e),v(F.value)||(I.value=``)}async function z(){if(F.value={},I.value=``,!O.isAgent){I.value=`فقط واسطه می‌تواند مشتری به لیست خود اضافه کند.`;return}let e=f(N.value),t={};if(j.value.trim()||(t.firstName=`نام الزامی است.`),M.value.trim()||(t.lastName=`نام خانوادگی الزامی است.`),p(e)||(t.phone=`شماره موبایل معتبر نیست.`),v(t)){F.value=t,I.value=y;return}L.value=!0;try{let t=await k.registerCustomer({firstName:j.value.trim(),lastName:M.value.trim(),phone:e,businessName:P.value.trim()||void 0});if(t?.id){await u.replace({path:`/customer`,query:{id:t.id}});return}await u.replace(`/customers`)}catch(e){I.value=e instanceof d||e instanceof Error?e.message:`ثبت مشتری ناموفق بود.`}finally{L.value=!1}}return(a,s)=>(i(),t(`div`,w,[n(S,{title:`افزودن مشتری`,"show-back":``,centered:``,onBack:r(A)},null,8,[`onBack`]),o(`div`,T,[s[7]||=o(`p`,{class:`intro`},` مشتری به لیست شما اضافه می‌شود و بعد از ورود با همین شماره، سفارش‌هایش به حساب شما وصل می‌ماند. `,-1),n(g,{message:I.value},null,8,[`message`]),o(`div`,E,[n(C,{modelValue:j.value,"onUpdate:modelValue":[s[0]||=e=>j.value=e,s[1]||=e=>R(`firstName`)],label:`نام`,placeholder:`نام مشتری`,error:F.value.firstName},null,8,[`modelValue`,`error`]),n(C,{modelValue:M.value,"onUpdate:modelValue":[s[2]||=e=>M.value=e,s[3]||=e=>R(`lastName`)],label:`نام خانوادگی`,placeholder:`نام خانوادگی`,error:F.value.lastName},null,8,[`modelValue`,`error`]),n(C,{modelValue:N.value,"onUpdate:modelValue":[s[4]||=e=>N.value=e,s[5]||=e=>R(`phone`)],label:`شماره موبایل`,type:`tel`,inputmode:`tel`,dir:`ltr`,placeholder:`۰۹۱۲۳۴۵۶۷۸۹`,error:F.value.phone},null,8,[`modelValue`,`error`]),n(C,{modelValue:P.value,"onUpdate:modelValue":s[6]||=e=>P.value=e,label:`نام فروشگاه یا برند (اختیاری)`,placeholder:`مثلاً بوتیک رز`},null,8,[`modelValue`])])]),o(`footer`,D,[n(h,{block:``,loading:L.value,disabled:L.value,onClick:z},{default:l(()=>[...s[8]||=[e(` ثبت مشتری `,-1)]]),_:1},8,[`loading`,`disabled`])])]))}}),[[`__scopeId`,`data-v-f29dd241`]]);export{O as default}; import{L as e,P as t,R as n,St as r,Y as i,_ as a,j as o,mt as s,n as c,rt as l,z as u}from"./BasicIcon-C6boaehV.js";import{t as d}from"./http-BC1hNfZ4.js";import{c as f}from"./format-BHJptHdF.js";import{a as p}from"./validation-BD7oi-2I.js";import{c as m,n as h}from"./index-DzzUgmYT.js";import{t as g}from"./FormErrorBanner-CjdBfIQT.js";import{n as _,r as v,t as y}from"./formErrors-DglFjYkZ.js";import{t as b}from"./crm-CtglbPsN.js";import{t as x}from"./useGoBack-CgNp0uP9.js";import{t as S}from"./PageHeader-BFnKSFNh.js";import{t as C}from"./BaseInput-qoEXOHCQ.js";var w={class:`page customer-create-page`},T={class:`page__scroll customer-create-scroll`},E={class:`form-card`},D={class:`footer`},O=c(u({__name:`CustomerCreateView`,setup(c){let u=a(),O=m(),k=b(),{goBack:A}=x(`/customers`),j=s(``),M=s(``),N=s(``),P=s(``),F=s({}),I=s(``),L=s(!1);function R(e){F.value=_(F.value,e),v(F.value)||(I.value=``)}async function z(){if(F.value={},I.value=``,!O.isAgent){I.value=`فقط واسطه می‌تواند مشتری به لیست خود اضافه کند.`;return}let e=f(N.value),t={};if(j.value.trim()||(t.firstName=`نام الزامی است.`),M.value.trim()||(t.lastName=`نام خانوادگی الزامی است.`),p(e)||(t.phone=`شماره موبایل معتبر نیست.`),v(t)){F.value=t,I.value=y;return}L.value=!0;try{let t=await k.registerCustomer({firstName:j.value.trim(),lastName:M.value.trim(),phone:e,businessName:P.value.trim()||void 0});if(t?.id){await u.replace({path:`/customer`,query:{id:t.id}});return}await u.replace(`/customers`)}catch(e){I.value=e instanceof d||e instanceof Error?e.message:`ثبت مشتری ناموفق بود.`}finally{L.value=!1}}return(a,s)=>(i(),t(`div`,w,[n(S,{title:`افزودن مشتری`,"show-back":``,centered:``,onBack:r(A)},null,8,[`onBack`]),o(`div`,T,[s[7]||=o(`p`,{class:`intro`},` مشتری به لیست شما اضافه می‌شود و بعد از ورود با همین شماره، سفارش‌هایش به حساب شما وصل می‌ماند. `,-1),n(g,{message:I.value},null,8,[`message`]),o(`div`,E,[n(C,{modelValue:j.value,"onUpdate:modelValue":[s[0]||=e=>j.value=e,s[1]||=e=>R(`firstName`)],label:`نام`,placeholder:`نام مشتری`,error:F.value.firstName},null,8,[`modelValue`,`error`]),n(C,{modelValue:M.value,"onUpdate:modelValue":[s[2]||=e=>M.value=e,s[3]||=e=>R(`lastName`)],label:`نام خانوادگی`,placeholder:`نام خانوادگی`,error:F.value.lastName},null,8,[`modelValue`,`error`]),n(C,{modelValue:N.value,"onUpdate:modelValue":[s[4]||=e=>N.value=e,s[5]||=e=>R(`phone`)],label:`شماره موبایل`,type:`tel`,inputmode:`tel`,dir:`ltr`,placeholder:`۰۹۱۲۳۴۵۶۷۸۹`,error:F.value.phone},null,8,[`modelValue`,`error`]),n(C,{modelValue:P.value,"onUpdate:modelValue":s[6]||=e=>P.value=e,label:`نام فروشگاه یا برند (اختیاری)`,placeholder:`مثلاً بوتیک رز`},null,8,[`modelValue`])])]),o(`footer`,D,[n(h,{block:``,loading:L.value,disabled:L.value,onClick:z},{default:l(()=>[...s[8]||=[e(` ثبت مشتری `,-1)]]),_:1},8,[`loading`,`disabled`])])]))}}),[[`__scopeId`,`data-v-f29dd241`]]);export{O as default};
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{A as e,Ct as t,Et as n,M as r,N as i,O as a,P as o,R as s,St as c,Tt as l,Y as u,Z as d,_ as f,g as p,j as m,mt as h,n as g,q as _,rt as v,tt as y,z as b}from"./BasicIcon-C6boaehV.js";import{o as x}from"./format-BHJptHdF.js";import{o as ee,x as S,y as C}from"./index-B6MlTGUp.js";import{t as w}from"./IconFilter-D9NdLZ7w.js";import{t as T}from"./PageSkeleton-uO-jhbNm.js";import{t as E}from"./crm-CtglbPsN.js";import{t as D}from"./PageHeader-CDQMUtCp.js";import{t as O}from"./SearchField-Dx2PcshV.js";var k={class:`page customers-page`},A={class:`page__scroll page__scroll--with-nav customers-scroll`},j={class:`toolbar`},M={class:`summary-card`},N={class:`summary-card__icon`,"aria-hidden":`true`},P={class:`summary-card__cols`},F={class:`tabs`,role:`tablist`},I=[`aria-selected`,`onClick`],L={class:`tab__text`},te={key:1},R=[`onClick`],z={class:`customer-card__body`},B={class:`customer-card__top`},V=g(b({__name:`CustomersView`,setup(g){let b=p(),V=f(),H=E(),U=h(String(b.query.q??``)),W=e(()=>String(b.query.status??`all`)),G=h([]),K=h(!0),q=h(null),J=e(()=>G.value),Y=e(()=>G.value.filter(e=>e.status===`active`).length),X=e(()=>G.value.reduce((e,t)=>e+t.totalPurchaseToman,0)),Z=[{id:`all`,label:`همه`},{id:`active`,label:`فعال`},{id:`inactive`,label:`غیرفعال`},{id:`new`,label:`جدید`}];async function Q(){K.value=!0,q.value=null;try{G.value=await H.loadCustomers({q:U.value||void 0,status:W.value===`all`?void 0:W.value})}catch(e){q.value=e instanceof Error?e.message:`بارگذاری مشتریان ناموفق بود.`,G.value=[]}finally{K.value=!1}}_(Q),y([W,U],Q);function $(e){V.replace({path:`/customers`,query:{...e===`all`?{}:{status:e},...U.value?{q:U.value}:{}}})}function ne(e){U.value=e,V.replace({path:`/customers`,query:{...W.value===`all`?{}:{status:W.value},...e?{q:e}:{}}})}function re(e){return e===`active`?`فعال`:e===`inactive`?`غیرفعال`:`جدید`}function ie(){V.push(`/customers/new`)}function ae(){let e=[`all`,`active`,`inactive`,`new`],t=e.indexOf(W.value);$(e[(t<0?0:t+1)%e.length]??`all`)}return(e,f)=>(u(),o(`div`,k,[s(D,{title:`مشتریان من`,"show-back":``,centered:``,onBack:f[0]||=e=>c(V).back()},{trailing:v(()=>[m(`button`,{type:`button`,class:`nav-add-btn`,"aria-label":`ثبت مشتری`,onClick:ie},[s(C,{size:22})])]),_:1}),m(`div`,A,[m(`div`,j,[s(O,{"model-value":U.value,placeholder:`جستجو بر اساس نام یا موبایل...`,"onUpdate:modelValue":ne},null,8,[`model-value`]),m(`button`,{type:`button`,class:`filter-btn`,"aria-label":`فیلتر وضعیت`,onClick:ae},[s(w,{size:18})])]),m(`section`,M,[m(`div`,N,[s(S,{size:22,color:`#fff`})]),m(`div`,P,[m(`div`,null,[f[1]||=m(`span`,null,`تعداد مشتری فعال`,-1),m(`strong`,null,n(c(x)(Y.value)),1)]),f[4]||=m(`span`,{class:`summary-card__divider`,"aria-hidden":`true`},null,-1),m(`div`,null,[f[2]||=m(`span`,null,`مجموع خرید (ریال)`,-1),m(`strong`,null,n(c(x)(X.value)),1),f[3]||=m(`em`,null,`تومان`,-1)])])]),m(`div`,F,[(u(),o(a,null,d(Z,e=>m(`button`,{key:e.id,type:`button`,role:`tab`,class:t([`tab`,{"tab--active":W.value===e.id}]),"aria-selected":W.value===e.id,onClick:t=>$(e.id)},[m(`span`,L,n(e.label),1)],10,I)),64))]),K.value?(u(),r(T,{key:0,variant:`list`})):q.value?(u(),o(`p`,te,n(q.value),1)):i(``,!0),(u(!0),o(a,null,d(J.value,e=>(u(),o(`button`,{key:e.id,type:`button`,class:`customer-card`,onClick:t=>c(V).push({path:`/customer`,query:{id:e.id}})},[m(`div`,{class:`customer-card__avatar`,style:l({background:e.avatarColor})},n(e.avatarLetter),5),m(`div`,z,[m(`div`,B,[m(`strong`,null,n(e.name),1)]),m(`p`,null,n(e.phone),1),m(`p`,null,`مجموع خرید: `+n(c(x)(e.totalPurchaseToman))+` تومان`,1)]),m(`span`,{class:t([`status`,{"status--ok":e.status===`active`,"status--off":e.status===`inactive`,"status--new":e.status===`new`}])},n(re(e.status)),3),s(ee,{size:18,class:`chevron`})],8,R))),128)),f[5]||=m(`section`,{class:`promo`},[m(`div`,null,[m(`strong`,null,`افزایش مشتریان = افزایش سطح شما`),m(`p`,null,`با جذب مشتریان بیشتر به سطح VIP برسید`)])],-1)])]))}}),[[`__scopeId`,`data-v-823e3f40`]]);export{V as default}; import{A as e,Ct as t,Et as n,M as r,N as i,O as a,P as o,R as s,St as c,Tt as l,Y as u,Z as d,_ as f,g as p,j as m,mt as h,n as g,q as _,rt as v,tt as y,z as b}from"./BasicIcon-C6boaehV.js";import{o as x}from"./format-BHJptHdF.js";import{o as ee,x as S,y as C}from"./index-DzzUgmYT.js";import{t as w}from"./IconFilter-D9NdLZ7w.js";import{t as T}from"./PageSkeleton-uO-jhbNm.js";import{t as E}from"./crm-CtglbPsN.js";import{t as D}from"./PageHeader-BFnKSFNh.js";import{t as O}from"./SearchField-Dx2PcshV.js";var k={class:`page customers-page`},A={class:`page__scroll page__scroll--with-nav customers-scroll`},j={class:`toolbar`},M={class:`summary-card`},N={class:`summary-card__icon`,"aria-hidden":`true`},P={class:`summary-card__cols`},F={class:`tabs`,role:`tablist`},I=[`aria-selected`,`onClick`],L={class:`tab__text`},te={key:1},R=[`onClick`],z={class:`customer-card__body`},B={class:`customer-card__top`},V=g(b({__name:`CustomersView`,setup(g){let b=p(),V=f(),H=E(),U=h(String(b.query.q??``)),W=e(()=>String(b.query.status??`all`)),G=h([]),K=h(!0),q=h(null),J=e(()=>G.value),Y=e(()=>G.value.filter(e=>e.status===`active`).length),X=e(()=>G.value.reduce((e,t)=>e+t.totalPurchaseToman,0)),Z=[{id:`all`,label:`همه`},{id:`active`,label:`فعال`},{id:`inactive`,label:`غیرفعال`},{id:`new`,label:`جدید`}];async function Q(){K.value=!0,q.value=null;try{G.value=await H.loadCustomers({q:U.value||void 0,status:W.value===`all`?void 0:W.value})}catch(e){q.value=e instanceof Error?e.message:`بارگذاری مشتریان ناموفق بود.`,G.value=[]}finally{K.value=!1}}_(Q),y([W,U],Q);function $(e){V.replace({path:`/customers`,query:{...e===`all`?{}:{status:e},...U.value?{q:U.value}:{}}})}function ne(e){U.value=e,V.replace({path:`/customers`,query:{...W.value===`all`?{}:{status:W.value},...e?{q:e}:{}}})}function re(e){return e===`active`?`فعال`:e===`inactive`?`غیرفعال`:`جدید`}function ie(){V.push(`/customers/new`)}function ae(){let e=[`all`,`active`,`inactive`,`new`],t=e.indexOf(W.value);$(e[(t<0?0:t+1)%e.length]??`all`)}return(e,f)=>(u(),o(`div`,k,[s(D,{title:`مشتریان من`,"show-back":``,centered:``,onBack:f[0]||=e=>c(V).back()},{trailing:v(()=>[m(`button`,{type:`button`,class:`nav-add-btn`,"aria-label":`ثبت مشتری`,onClick:ie},[s(C,{size:22})])]),_:1}),m(`div`,A,[m(`div`,j,[s(O,{"model-value":U.value,placeholder:`جستجو بر اساس نام یا موبایل...`,"onUpdate:modelValue":ne},null,8,[`model-value`]),m(`button`,{type:`button`,class:`filter-btn`,"aria-label":`فیلتر وضعیت`,onClick:ae},[s(w,{size:18})])]),m(`section`,M,[m(`div`,N,[s(S,{size:22,color:`#fff`})]),m(`div`,P,[m(`div`,null,[f[1]||=m(`span`,null,`تعداد مشتری فعال`,-1),m(`strong`,null,n(c(x)(Y.value)),1)]),f[4]||=m(`span`,{class:`summary-card__divider`,"aria-hidden":`true`},null,-1),m(`div`,null,[f[2]||=m(`span`,null,`مجموع خرید (ریال)`,-1),m(`strong`,null,n(c(x)(X.value)),1),f[3]||=m(`em`,null,`تومان`,-1)])])]),m(`div`,F,[(u(),o(a,null,d(Z,e=>m(`button`,{key:e.id,type:`button`,role:`tab`,class:t([`tab`,{"tab--active":W.value===e.id}]),"aria-selected":W.value===e.id,onClick:t=>$(e.id)},[m(`span`,L,n(e.label),1)],10,I)),64))]),K.value?(u(),r(T,{key:0,variant:`list`})):q.value?(u(),o(`p`,te,n(q.value),1)):i(``,!0),(u(!0),o(a,null,d(J.value,e=>(u(),o(`button`,{key:e.id,type:`button`,class:`customer-card`,onClick:t=>c(V).push({path:`/customer`,query:{id:e.id}})},[m(`div`,{class:`customer-card__avatar`,style:l({background:e.avatarColor})},n(e.avatarLetter),5),m(`div`,z,[m(`div`,B,[m(`strong`,null,n(e.name),1)]),m(`p`,null,n(e.phone),1),m(`p`,null,`مجموع خرید: `+n(c(x)(e.totalPurchaseToman))+` تومان`,1)]),m(`span`,{class:t([`status`,{"status--ok":e.status===`active`,"status--off":e.status===`inactive`,"status--new":e.status===`new`}])},n(re(e.status)),3),s(ee,{size:18,class:`chevron`})],8,R))),128)),f[5]||=m(`section`,{class:`promo`},[m(`div`,null,[m(`strong`,null,`افزایش مشتریان = افزایش سطح شما`),m(`p`,null,`با جذب مشتریان بیشتر به سطح VIP برسید`)])],-1)])]))}}),[[`__scopeId`,`data-v-823e3f40`]]);export{V as default};
@@ -1 +1 @@
import{D as e,Et as t,M as n,N as r,O as i,P as a,R as o,St as s,Y as c,Z as l,_ as u,j as d,mt as f,n as p,q as m,z as h}from"./BasicIcon-C6boaehV.js";import{l as g,o as _}from"./index-B6MlTGUp.js";import{t as v}from"./PageSkeleton-uO-jhbNm.js";import{t as y}from"./crm-CtglbPsN.js";import{t as b}from"./PageHeader-CDQMUtCp.js";import{t as x}from"./IconHeart-CZMe29Nz.js";var S={class:`page`},C={class:`page__scroll page__scroll--with-nav body`},w={key:1},T={key:2,class:`empty`},E=[`onClick`],D={class:`row__body`},O=[`onClick`],k=p(h({__name:`FavoritesView`,setup(p){let h=u(),k=g(),A=y(),j=f([]),M=f(!0),N=f(null);m(async()=>{M.value=!0;try{await k.hydrate();let e=await A.loadBrowseProducts(),t=new Set(k.ids);j.value=e.filter(e=>t.has(e.id))}catch(e){N.value=e instanceof Error?e.message:`بارگذاری علاقه‌مندی‌ها ناموفق بود.`}finally{M.value=!1}});async function P(e){await k.toggle(e),j.value=j.value.filter(t=>t.id!==e)}return(u,f)=>(c(),a(`div`,S,[o(b,{title:`علاقه‌مندی‌ها`,"show-back":``,centered:``,onBack:f[0]||=e=>s(h).back()}),d(`div`,C,[M.value?(c(),n(v,{key:0,variant:`grid`})):N.value?(c(),a(`p`,w,t(N.value),1)):j.value.length?r(``,!0):(c(),a(`p`,T,`هنوز محصولی ذخیره نکرده‌اید.`)),(c(!0),a(i,null,l(j.value,n=>(c(),a(`article`,{key:n.id,class:`row`,onClick:e=>s(h).push({path:`/product`,query:{id:n.id}})},[d(`div`,D,[d(`strong`,null,t(n.name),1),d(`span`,null,`کد `+t(n.code),1)]),d(`button`,{type:`button`,class:`bookmark`,"aria-label":`حذف از نشان‌شده‌ها`,onClick:e(e=>P(n.id),[`stop`])},[o(x,{size:18,filled:``,color:`#0a353c`})],8,O),o(_,{size:16,color:`#9aa3a0`})],8,E))),128))])]))}}),[[`__scopeId`,`data-v-8e70f9c9`]]);export{k as default}; import{D as e,Et as t,M as n,N as r,O as i,P as a,R as o,St as s,Y as c,Z as l,_ as u,j as d,mt as f,n as p,q as m,z as h}from"./BasicIcon-C6boaehV.js";import{l as g,o as _}from"./index-DzzUgmYT.js";import{t as v}from"./PageSkeleton-uO-jhbNm.js";import{t as y}from"./crm-CtglbPsN.js";import{t as b}from"./PageHeader-BFnKSFNh.js";import{t as x}from"./IconHeart-CZMe29Nz.js";var S={class:`page`},C={class:`page__scroll page__scroll--with-nav body`},w={key:1},T={key:2,class:`empty`},E=[`onClick`],D={class:`row__body`},O=[`onClick`],k=p(h({__name:`FavoritesView`,setup(p){let h=u(),k=g(),A=y(),j=f([]),M=f(!0),N=f(null);m(async()=>{M.value=!0;try{await k.hydrate();let e=await A.loadBrowseProducts(),t=new Set(k.ids);j.value=e.filter(e=>t.has(e.id))}catch(e){N.value=e instanceof Error?e.message:`بارگذاری علاقه‌مندی‌ها ناموفق بود.`}finally{M.value=!1}});async function P(e){await k.toggle(e),j.value=j.value.filter(t=>t.id!==e)}return(u,f)=>(c(),a(`div`,S,[o(b,{title:`علاقه‌مندی‌ها`,"show-back":``,centered:``,onBack:f[0]||=e=>s(h).back()}),d(`div`,C,[M.value?(c(),n(v,{key:0,variant:`grid`})):N.value?(c(),a(`p`,w,t(N.value),1)):j.value.length?r(``,!0):(c(),a(`p`,T,`هنوز محصولی ذخیره نکرده‌اید.`)),(c(!0),a(i,null,l(j.value,n=>(c(),a(`article`,{key:n.id,class:`row`,onClick:e=>s(h).push({path:`/product`,query:{id:n.id}})},[d(`div`,D,[d(`strong`,null,t(n.name),1),d(`span`,null,`کد `+t(n.code),1)]),d(`button`,{type:`button`,class:`bookmark`,"aria-label":`حذف از نشان‌شده‌ها`,onClick:e(e=>P(n.id),[`stop`])},[o(x,{size:18,filled:``,color:`#0a353c`})],8,O),o(_,{size:16,color:`#9aa3a0`})],8,E))),128))])]))}}),[[`__scopeId`,`data-v-8e70f9c9`]]);export{k 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 @@
.meeting-page[data-v-e8f8b993]{background:var(--color-bg)}.meeting-scroll[data-v-e8f8b993]{padding:8px var(--page-padding) 100px;flex-direction:column;gap:16px;display:flex}.customer-card[data-v-e8f8b993]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);align-items:center;gap:12px;padding:12px;display:flex}.avatar[data-v-e8f8b993]{border-radius:999px;place-items:center;width:52px;height:52px;font-weight:700;display:grid}.customer-card p[data-v-e8f8b993]{color:var(--color-text-muted);margin:2px 0 0;font-size:.8rem}h3[data-v-e8f8b993]{margin:0 0 8px;font-size:.9rem}.type-grid[data-v-e8f8b993]{grid-template-columns:repeat(4,1fr);gap:8px;display:grid}.submit-icon-btn[data-v-e8f8b993]{width:40px;height:40px;color:var(--color-primary);cursor:pointer;background:0 0;border:none;place-items:center;padding:0;display:grid}.submit-icon-btn[data-v-e8f8b993]:disabled{opacity:.5;cursor:not-allowed}.type-btn[data-v-e8f8b993]{border:1.5px solid var(--color-border);background:var(--color-surface);cursor:pointer;border-radius:12px;flex-direction:column;justify-content:center;align-items:center;gap:6px;min-height:72px;padding:8px 6px;font-size:.72rem;display:flex}.type-btn--active[data-v-e8f8b993]{border-color:var(--color-primary);color:var(--color-primary);background:var(--color-primary-soft);border-width:2px;font-weight:700}.two-cols[data-v-e8f8b993]{grid-template-columns:1fr 1fr;gap:8px;display:grid}.field[data-v-e8f8b993]{flex-direction:column;gap:6px;font-size:.85rem;display:flex}.field input[data-v-e8f8b993],.field textarea[data-v-e8f8b993]{border:1px solid var(--color-border);font:inherit;resize:vertical;background:#f5f5f5;border-radius:12px;padding:12px}.field em[data-v-e8f8b993]{color:var(--color-text-muted);align-self:flex-end;font-size:.75rem;font-style:normal}.files[data-v-e8f8b993]{gap:8px;display:flex}.file-thumb[data-v-e8f8b993],.file-add[data-v-e8f8b993]{border-radius:12px;width:64px;height:64px}.file-thumb[data-v-e8f8b993]{background:linear-gradient(145deg,#4b5563,#1f2937)}.file-add[data-v-e8f8b993]{border:1.5px dashed var(--color-border);color:var(--color-text-muted);cursor:pointer;background:0 0;font-size:1.4rem}.reminder-box[data-v-e8f8b993]{background:var(--color-surface);border-radius:var(--radius-md);flex-direction:column;gap:12px;padding:14px;display:flex}.reminder-box__head[data-v-e8f8b993]{justify-content:space-between;align-items:center;display:flex}.toggle[data-v-e8f8b993]{cursor:pointer;background:#d1d5db;border:none;border-radius:999px;width:46px;height:28px;padding:3px}.toggle span[data-v-e8f8b993]{background:var(--color-surface);border-radius:999px;width:22px;height:22px;transition:transform .15s;display:block}.toggle--on[data-v-e8f8b993]{background:var(--color-primary)}.toggle--on span[data-v-e8f8b993]{transform:translate(-18px)}.footer[data-v-e8f8b993]{padding:12px var(--page-padding) calc(12px + env(safe-area-inset-bottom,0));background:var(--color-surface);border-top:1px solid var(--color-border);position:sticky;bottom:0}.submit[data-v-e8f8b993]{background:var(--color-primary);color:#fff;cursor:pointer;border:none;border-radius:14px;width:100%;min-height:52px;font-weight:700}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.meeting-page[data-v-2708a25b]{background:var(--color-bg)}.meeting-scroll[data-v-2708a25b]{padding:8px var(--page-padding) 100px;flex-direction:column;gap:16px;display:flex}.customer-card[data-v-2708a25b]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-md);align-items:center;gap:12px;padding:12px;display:flex}.avatar[data-v-2708a25b]{border-radius:999px;place-items:center;width:52px;height:52px;font-weight:700;display:grid}.customer-card p[data-v-2708a25b]{color:var(--color-text-muted);margin:2px 0 0;font-size:.8rem}h3[data-v-2708a25b]{margin:0 0 8px;font-size:.9rem}.type-grid[data-v-2708a25b]{grid-template-columns:repeat(4,1fr);gap:8px;display:grid}.submit-icon-btn[data-v-2708a25b]{width:40px;height:40px;color:var(--color-primary);cursor:pointer;background:0 0;border:none;place-items:center;padding:0;display:grid}.submit-icon-btn[data-v-2708a25b]:disabled{opacity:.5;cursor:not-allowed}.type-btn[data-v-2708a25b]{border:1.5px solid var(--color-border);background:var(--color-surface);cursor:pointer;border-radius:12px;flex-direction:column;justify-content:center;align-items:center;gap:6px;min-height:72px;padding:8px 6px;font-size:.72rem;display:flex}.type-btn--active[data-v-2708a25b]{border-color:var(--color-primary);color:var(--color-primary);background:var(--color-primary-soft);border-width:2px;font-weight:700}.two-cols[data-v-2708a25b]{grid-template-columns:1fr 1fr;gap:8px;display:grid}.field[data-v-2708a25b]{flex-direction:column;gap:6px;font-size:.85rem;display:flex}.field input[data-v-2708a25b],.field textarea[data-v-2708a25b]{border:1px solid var(--color-border);font:inherit;resize:vertical;background:#f5f5f5;border-radius:12px;padding:12px}.field em[data-v-2708a25b]{color:var(--color-text-muted);align-self:flex-end;font-size:.75rem;font-style:normal}.files[data-v-2708a25b]{gap:8px;display:flex}.file-thumb[data-v-2708a25b],.file-add[data-v-2708a25b]{border-radius:12px;width:64px;height:64px}.file-thumb[data-v-2708a25b]{background:linear-gradient(145deg,#4b5563,#1f2937)}.file-add[data-v-2708a25b]{border:1.5px dashed var(--color-border);color:var(--color-text-muted);cursor:pointer;background:0 0;font-size:1.4rem}.reminder-box[data-v-2708a25b]{background:var(--color-surface);border-radius:var(--radius-md);flex-direction:column;gap:12px;padding:14px;display:flex}.reminder-box__head[data-v-2708a25b]{justify-content:space-between;align-items:center;display:flex}.toggle[data-v-2708a25b]{cursor:pointer;background:#d1d5db;border:none;border-radius:999px;width:46px;height:28px;padding:3px}.toggle span[data-v-2708a25b]{background:#fff;border-radius:999px;width:22px;height:22px;transition:transform .15s;display:block}.toggle--on[data-v-2708a25b]{background:var(--color-primary)}.toggle--on span[data-v-2708a25b]{transform:translate(-18px)}.footer[data-v-2708a25b]{padding:12px var(--page-padding) calc(12px + env(safe-area-inset-bottom,0));background:var(--color-surface);border-top:1px solid var(--color-border);position:sticky;bottom:0}.submit[data-v-2708a25b]{background:var(--color-primary);color:#fff;cursor:pointer;border:none;border-radius:14px;width:100%;min-height:52px;font-weight:700}
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{A as e,Et as t,L as n,N as r,P as i,R as a,St as o,Y as s,_ as c,g as l,j as u,n as d,rt as f,z as p}from"./BasicIcon-C6boaehV.js";import{a as m}from"./format-BHJptHdF.js";import{d as h,n as g}from"./index-B6MlTGUp.js";import{t as _}from"./useGoBack-CgNp0uP9.js";import{t as v}from"./PageHeader-CDQMUtCp.js";var y={class:`page success-page`},b={class:`page__scroll success-page__scroll`},x={key:0,class:`card mb-4`},S={class:`info-row`},C={class:`info-row`},w={class:`info-row`},T=d(p({__name:`OrderSuccessView`,setup(d){let p=l(),T=c(),{goBack:E}=_(`/orders`),D=h(),O=e(()=>D.getOrderById(p.params.id));return(e,c)=>(s(),i(`div`,y,[a(v,{title:`ثبت موفق`,"show-back":``,centered:``,onBack:o(E)},null,8,[`onBack`]),u(`div`,b,[c[8]||=u(`div`,{class:`success-icon`},`✓`,-1),c[9]||=u(`h1`,{class:`page-title text-center`},`سفارش ثبت شد`,-1),c[10]||=u(`p`,{class:`text-center text-muted mb-4`},` سفارش شما با موفقیت ثبت شد. کارشناسان ما سفارش را بررسی و قیمت‌گذاری می‌کنند و به‌محض صدور فاکتور، از طریق پیامک و پنل کاربری به شما اطلاع می‌دهیم تا روش تسویه را انتخاب کنید. `,-1),O.value?(s(),i(`div`,x,[u(`div`,S,[c[2]||=u(`span`,null,`شماره سفارش:`,-1),u(`strong`,null,t(O.value.orderNumber),1)]),u(`div`,C,[c[3]||=u(`span`,null,`شرکت:`,-1),u(`span`,null,t(O.value.companyName),1)]),u(`div`,w,[c[4]||=u(`span`,null,`متراژ:`,-1),u(`span`,null,t(o(m)(O.value.totalMeterage)),1)]),c[5]||=u(`div`,{class:`info-row`},[u(`span`,null,`وضعیت:`),u(`span`,null,`در انتظار قیمت‌گذاری`)],-1)])):r(``,!0),a(g,{block:``,onClick:c[0]||=e=>o(T).replace(`/orders/${O.value?.id}`)},{default:f(()=>[...c[6]||=[n(` مشاهده جزئیات سفارش `,-1)]]),_:1}),a(g,{block:``,variant:`outline`,class:`mt-2`,onClick:c[1]||=e=>o(T).replace(`/`)},{default:f(()=>[...c[7]||=[n(` بازگشت به خانه `,-1)]]),_:1})])]))}}),[[`__scopeId`,`data-v-3d172af9`]]);export{T as default}; import{A as e,Et as t,L as n,N as r,P as i,R as a,St as o,Y as s,_ as c,g as l,j as u,n as d,rt as f,z as p}from"./BasicIcon-C6boaehV.js";import{a as m}from"./format-BHJptHdF.js";import{d as h,n as g}from"./index-DzzUgmYT.js";import{t as _}from"./useGoBack-CgNp0uP9.js";import{t as v}from"./PageHeader-BFnKSFNh.js";var y={class:`page success-page`},b={class:`page__scroll success-page__scroll`},x={key:0,class:`card mb-4`},S={class:`info-row`},C={class:`info-row`},w={class:`info-row`},T=d(p({__name:`OrderSuccessView`,setup(d){let p=l(),T=c(),{goBack:E}=_(`/orders`),D=h(),O=e(()=>D.getOrderById(p.params.id));return(e,c)=>(s(),i(`div`,y,[a(v,{title:`ثبت موفق`,"show-back":``,centered:``,onBack:o(E)},null,8,[`onBack`]),u(`div`,b,[c[8]||=u(`div`,{class:`success-icon`},`✓`,-1),c[9]||=u(`h1`,{class:`page-title text-center`},`سفارش ثبت شد`,-1),c[10]||=u(`p`,{class:`text-center text-muted mb-4`},` سفارش شما با موفقیت ثبت شد. کارشناسان ما سفارش را بررسی و قیمت‌گذاری می‌کنند و به‌محض صدور فاکتور، از طریق پیامک و پنل کاربری به شما اطلاع می‌دهیم تا روش تسویه را انتخاب کنید. `,-1),O.value?(s(),i(`div`,x,[u(`div`,S,[c[2]||=u(`span`,null,`شماره سفارش:`,-1),u(`strong`,null,t(O.value.orderNumber),1)]),u(`div`,C,[c[3]||=u(`span`,null,`شرکت:`,-1),u(`span`,null,t(O.value.companyName),1)]),u(`div`,w,[c[4]||=u(`span`,null,`متراژ:`,-1),u(`span`,null,t(o(m)(O.value.totalMeterage)),1)]),c[5]||=u(`div`,{class:`info-row`},[u(`span`,null,`وضعیت:`),u(`span`,null,`در انتظار قیمت‌گذاری`)],-1)])):r(``,!0),a(g,{block:``,onClick:c[0]||=e=>o(T).replace(`/orders/${O.value?.id}`)},{default:f(()=>[...c[6]||=[n(` مشاهده جزئیات سفارش `,-1)]]),_:1}),a(g,{block:``,variant:`outline`,class:`mt-2`,onClick:c[1]||=e=>o(T).replace(`/`)},{default:f(()=>[...c[7]||=[n(` بازگشت به خانه `,-1)]]),_:1})])]))}}),[[`__scopeId`,`data-v-3d172af9`]]);export{T as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{Et as e,N as t,O as n,P as r,Q as i,R as a,Y as o,j as s,n as c,z as l}from"./BasicIcon-C6boaehV.js";import{i as u}from"./index-B6MlTGUp.js";import{t as d}from"./IconChevronBack-DORV2H0q.js";var f={class:`page-nav`},p={key:1,class:`nav-btn`},m={class:`page-nav__title`},h={class:`page-nav__trailing`},g={key:0,class:`page-nav__subtitle`},_={key:1,class:`page-header`},v={class:`header-text`},y={class:`page-title`},b={key:0,class:`page-subtitle`},x={class:`page-header__trailing`},S=c(l({__name:`PageHeader`,props:{title:{},subtitle:{},showBack:{type:Boolean},centered:{type:Boolean}},emits:[`back`],setup(c){return(l,S)=>c.centered?(o(),r(n,{key:0},[s(`header`,f,[c.showBack?(o(),r(`button`,{key:0,type:`button`,class:`nav-btn`,"aria-label":`بازگشت`,onClick:S[0]||=e=>l.$emit(`back`)},[a(d,{size:20})])):(o(),r(`span`,p)),s(`h1`,m,e(c.title),1),s(`div`,h,[i(l.$slots,`trailing`,{},void 0,!0),a(u,{variant:`inline`,class:`mobile-only`})])]),c.subtitle?(o(),r(`p`,g,e(c.subtitle),1)):t(``,!0)],64)):(o(),r(`header`,_,[c.showBack?(o(),r(`button`,{key:0,type:`button`,class:`back-btn`,"aria-label":`بازگشت`,onClick:S[1]||=e=>l.$emit(`back`)},[a(d,{size:20})])):t(``,!0),s(`div`,v,[s(`h1`,y,e(c.title),1),c.subtitle?(o(),r(`p`,b,e(c.subtitle),1)):t(``,!0)]),s(`div`,x,[i(l.$slots,`trailing`,{},void 0,!0),a(u,{variant:`inline`,class:`mobile-only`})])]))}}),[[`__scopeId`,`data-v-dd86426f`]]);export{S as t}; import{Et as e,N as t,O as n,P as r,Q as i,R as a,Y as o,j as s,n as c,z as l}from"./BasicIcon-C6boaehV.js";import{i as u}from"./index-DzzUgmYT.js";import{t as d}from"./IconChevronBack-DORV2H0q.js";var f={class:`page-nav`},p={key:1,class:`nav-btn`},m={class:`page-nav__title`},h={class:`page-nav__trailing`},g={key:0,class:`page-nav__subtitle`},_={key:1,class:`page-header`},v={class:`header-text`},y={class:`page-title`},b={key:0,class:`page-subtitle`},x={class:`page-header__trailing`},S=c(l({__name:`PageHeader`,props:{title:{},subtitle:{},showBack:{type:Boolean},centered:{type:Boolean}},emits:[`back`],setup(c){return(l,S)=>c.centered?(o(),r(n,{key:0},[s(`header`,f,[c.showBack?(o(),r(`button`,{key:0,type:`button`,class:`nav-btn`,"aria-label":`بازگشت`,onClick:S[0]||=e=>l.$emit(`back`)},[a(d,{size:20})])):(o(),r(`span`,p)),s(`h1`,m,e(c.title),1),s(`div`,h,[i(l.$slots,`trailing`,{},void 0,!0),a(u,{variant:`inline`,class:`mobile-only`})])]),c.subtitle?(o(),r(`p`,g,e(c.subtitle),1)):t(``,!0)],64)):(o(),r(`header`,_,[c.showBack?(o(),r(`button`,{key:0,type:`button`,class:`back-btn`,"aria-label":`بازگشت`,onClick:S[1]||=e=>l.$emit(`back`)},[a(d,{size:20})])):t(``,!0),s(`div`,v,[s(`h1`,y,e(c.title),1),c.subtitle?(o(),r(`p`,b,e(c.subtitle),1)):t(``,!0)]),s(`div`,x,[i(l.$slots,`trailing`,{},void 0,!0),a(u,{variant:`inline`,class:`mobile-only`})])]))}}),[[`__scopeId`,`data-v-dd86426f`]]);export{S 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 @@
.products-page[data-v-ea9e9c36]{background:var(--color-bg);position:relative}.nav-filter[data-v-ea9e9c36]{cursor:pointer;width:40px;height:40px;color:var(--color-text);background:0 0;border:none;place-items:center;display:grid}.products-scroll[data-v-ea9e9c36]{padding:8px var(--page-padding) 24px;flex-direction:column;gap:12px;display:flex}.cat-banner[data-v-ea9e9c36]{border-radius:var(--radius-lg);background:linear-gradient(100deg, rgba(var(--color-primary-rgb), .75), rgba(var(--color-primary-rgb), .35)), #2f3430;color:#fff;align-items:center;gap:12px;min-height:96px;padding:16px;display:flex}.cat-banner__icon[data-v-ea9e9c36]{background:#fff3;border-radius:999px;flex-shrink:0;width:48px;height:48px}.cat-banner h2[data-v-ea9e9c36]{margin:0 0 4px;font-size:1rem}.cat-banner p[data-v-ea9e9c36]{opacity:.9;margin:0;font-size:.8rem}.chips[data-v-ea9e9c36]{gap:8px;display:flex;overflow-x:auto}.chip[data-v-ea9e9c36]{color:var(--color-text);white-space:nowrap;cursor:pointer;background:#ecefee;border:none;border-radius:999px;padding:8px 14px;font-size:.8rem}.chip--active[data-v-ea9e9c36]{background:var(--color-primary);color:#fff}.list-meta[data-v-ea9e9c36]{color:var(--color-text-muted);justify-content:space-between;align-items:center;font-size:.8rem;display:flex}.list-meta__sort[data-v-ea9e9c36]{color:var(--color-primary);font:inherit;cursor:pointer;background:0 0;border:none;padding:0;font-weight:700}.product-item[data-v-ea9e9c36]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);cursor:pointer;grid-template-columns:112px 1fr;align-items:stretch;gap:12px;padding:12px;display:grid}.product-item__image[data-v-ea9e9c36]{background:linear-gradient(145deg,#2a3544,#111827) 50%/cover;border-radius:12px;min-height:112px}.product-item__body[data-v-ea9e9c36]{flex-direction:column;gap:6px;min-width:0;display:flex}.product-item__head[data-v-ea9e9c36]{justify-content:space-between;align-items:flex-start;gap:8px;display:flex}.product-item__title[data-v-ea9e9c36]{flex-wrap:wrap;align-items:center;gap:6px;min-width:0;display:flex}.product-item__title strong[data-v-ea9e9c36]{color:var(--color-text);font-size:.9375rem;font-weight:700;line-height:1.4}.new-tag[data-v-ea9e9c36]{background:var(--color-primary);color:#fff;white-space:nowrap;border-radius:999px;padding:2px 8px;font-size:.65rem;font-weight:700}.bookmark[data-v-ea9e9c36]{cursor:pointer;background:0 0;border:none;flex-shrink:0;place-items:center;width:28px;height:28px;margin-inline-start:auto;padding:0;display:grid}.product-item__specs[data-v-ea9e9c36]{color:var(--color-text-muted);text-align:start;flex-direction:column;gap:2px;margin:0;padding:0;font-size:.75rem;line-height:1.55;list-style:none;display:flex}.swatches[data-v-ea9e9c36]{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.swatch[data-v-ea9e9c36]{background-position:50%;background-size:cover;border:1px solid #00000014;border-radius:999px;width:14px;height:14px}.details-link[data-v-ea9e9c36]{color:var(--color-info);cursor:pointer;background:0 0;border:none;align-self:flex-end;align-items:center;gap:2px;margin-top:auto;padding:0;font-size:.75rem;display:inline-flex}.empty-list[data-v-ea9e9c36]{text-align:center;color:var(--color-text-muted);margin:12px 0 0}
+1
View File
@@ -0,0 +1 @@
.products-page[data-v-67beb24b]{background:var(--color-bg);position:relative}.nav-filter[data-v-67beb24b]{cursor:pointer;width:40px;height:40px;color:var(--color-text);background:0 0;border:none;place-items:center;display:grid}.products-scroll[data-v-67beb24b]{padding:8px var(--page-padding) 24px;flex-direction:column;gap:12px;display:flex}.cat-banner[data-v-67beb24b]{border-radius:var(--radius-lg);background:linear-gradient(100deg, rgba(var(--color-primary-rgb), .75), rgba(var(--color-primary-rgb), .35)), #2f3430;color:#fff;align-items:center;gap:12px;min-height:96px;padding:16px;display:flex}.cat-banner__icon[data-v-67beb24b]{background:#fff3;border-radius:999px;flex-shrink:0;width:48px;height:48px}.cat-banner h2[data-v-67beb24b]{margin:0 0 4px;font-size:1rem}.cat-banner p[data-v-67beb24b]{opacity:.9;margin:0;font-size:.8rem}.chips[data-v-67beb24b]{gap:8px;display:flex;overflow-x:auto}.chip[data-v-67beb24b]{background:var(--color-bg);color:var(--color-text);white-space:nowrap;cursor:pointer;border:none;border-radius:999px;padding:8px 14px;font-size:.8rem}.chip--active[data-v-67beb24b]{background:var(--color-primary);color:#fff}.list-meta[data-v-67beb24b]{color:var(--color-text-muted);justify-content:space-between;align-items:center;font-size:.8rem;display:flex}.list-meta__sort[data-v-67beb24b]{color:var(--color-primary);font:inherit;cursor:pointer;background:0 0;border:none;padding:0;font-weight:700}.product-item[data-v-67beb24b]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);cursor:pointer;grid-template-columns:112px 1fr;align-items:stretch;gap:12px;padding:12px;display:grid}.product-item__image[data-v-67beb24b]{background:linear-gradient(145deg,#2a3544,#111827) 50%/cover;border-radius:12px;min-height:112px}.product-item__body[data-v-67beb24b]{flex-direction:column;gap:6px;min-width:0;display:flex}.product-item__head[data-v-67beb24b]{justify-content:space-between;align-items:flex-start;gap:8px;display:flex}.product-item__title[data-v-67beb24b]{flex-wrap:wrap;align-items:center;gap:6px;min-width:0;display:flex}.product-item__title strong[data-v-67beb24b]{color:var(--color-text);font-size:.9375rem;font-weight:700;line-height:1.4}.new-tag[data-v-67beb24b]{background:var(--color-primary);color:#fff;white-space:nowrap;border-radius:999px;padding:2px 8px;font-size:.65rem;font-weight:700}.bookmark[data-v-67beb24b]{cursor:pointer;background:0 0;border:none;flex-shrink:0;place-items:center;width:28px;height:28px;margin-inline-start:auto;padding:0;display:grid}.product-item__specs[data-v-67beb24b]{color:var(--color-text-muted);text-align:start;flex-direction:column;gap:2px;margin:0;padding:0;font-size:.75rem;line-height:1.55;list-style:none;display:flex}.swatches[data-v-67beb24b]{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.swatch[data-v-67beb24b]{background-position:50%;background-size:cover;border:1px solid #00000014;border-radius:999px;width:14px;height:14px}.details-link[data-v-67beb24b]{color:var(--color-info);cursor:pointer;background:0 0;border:none;align-self:flex-end;align-items:center;gap:2px;margin-top:auto;padding:0;font-size:.75rem;display:inline-flex}.empty-list[data-v-67beb24b]{text-align:center;color:var(--color-text-muted);margin:12px 0 0}
@@ -1 +1 @@
import{t as e}from"./CheckListView-Bq44NHdA.js";import{M as t,Y as n,z as r}from"./BasicIcon-C6boaehV.js";var i=r({__name:`ProfileChecksView`,setup(r){return(r,i)=>(n(),t(e))}});export{i as default}; import{t as e}from"./CheckListView-DSyd4n4b.js";import{M as t,Y as n,z as r}from"./BasicIcon-C6boaehV.js";var i=r({__name:`ProfileChecksView`,setup(r){return(r,i)=>(n(),t(e))}});export{i as default};
@@ -1 +1 @@
import{A as e,Ct as t,Et as n,L as r,M as i,N as a,O as o,P as s,R as c,St as l,Y as u,Z as d,_ as f,j as p,mt as m,n as h,rt as g,t as _,z as v}from"./BasicIcon-C6boaehV.js";import{u as y}from"./dist-CUUvbY4x.js";import{a as b,c as x,d as S,n as C}from"./index-B6MlTGUp.js";import{t as w}from"./ProfileSubpageLayout-PN2YWH-s.js";var T={class:`settings-page`},E={class:`settings-links`,"aria-label":`حساب کاربری`},D={class:`settings-link__icon`,"aria-hidden":`true`},O={class:`settings-link__icon`,"aria-hidden":`true`},k={class:`settings-link__label`},A={key:0},j={class:`display-fieldset`},M={class:`display-grid`,role:`radiogroup`,"aria-label":`حالت نمایش`},N=[`aria-checked`,`onClick`],P={class:`theme-card__icon`,"aria-hidden":`true`},F={class:`theme-card__body`},I={class:`theme-card__label`},L={class:`theme-card__desc`},R={class:`theme-card__check`,"aria-hidden":`true`},z={key:0,class:`display-saved`},B=h(v({__name:`ProfileDisplayView`,setup(h){let v=f(),B=x(),V=S(),H=b(),U=m(!1),{start:W}=y(()=>{U.value=!1},2e3),G=[{value:`light`,label:`روشن`,description:`پس‌زمینه روشن و کنتراست بالا`,icon:`vuesax-linear:sun-1`},{value:`dark`,label:`تاریک`,description:`مناسب استفاده در شب`,icon:`vuesax-linear:moon`},{value:`system`,label:`سیستم`,description:`هم‌تراز با تنظیمات دستگاه`,icon:`vuesax-linear:mobile`}],K=e(()=>V.draftChecks.length);function q(){U.value=!0,W()}function J(){B.logout(),v.replace(`/welcome`)}return(e,f)=>(u(),i(w,{title:`تنظیمات`,subtitle:`نمایش، حساب کاربری و خروج`},{default:g(()=>[p(`div`,T,[p(`nav`,E,[p(`button`,{type:`button`,class:`settings-link`,onClick:f[0]||=e=>l(v).push(`/profile/edit`)},[p(`span`,D,[c(_,{icon:`vuesax-linear:profile-circle`,size:20,"stroke-width":1.5})]),f[2]||=p(`span`,{class:`settings-link__label`},`اطلاعات کاربری`,-1),c(_,{class:`settings-link__chevron`,icon:`vuesax-linear:arrow-left-1`,size:16,"stroke-width":1.5})]),p(`button`,{type:`button`,class:`settings-link`,onClick:f[1]||=e=>l(v).push(`/profile/checks`)},[p(`span`,O,[c(_,{icon:`vuesax-linear:card`,size:20,"stroke-width":1.5})]),p(`span`,k,[f[3]||=r(` لیست چک‌ها `,-1),K.value?(u(),s(`em`,A,`(`+n(K.value)+`)`,1)):a(``,!0)]),c(_,{class:`settings-link__chevron`,icon:`vuesax-linear:arrow-left-1`,size:16,"stroke-width":1.5})])]),p(`fieldset`,j,[f[4]||=p(`legend`,{class:`display-fieldset__legend`},`حالت نمایش`,-1),p(`div`,M,[(u(),s(o,null,d(G,e=>p(`button`,{key:e.value,type:`button`,class:t([`theme-card`,{"theme-card--active":l(H).preference===e.value}]),role:`radio`,"aria-checked":l(H).preference===e.value,onClick:t=>l(H).preference=e.value},[p(`span`,P,[c(_,{icon:e.icon,size:26,"stroke-width":1.5},null,8,[`icon`])]),p(`span`,F,[p(`strong`,I,n(e.label),1),p(`span`,L,n(e.description),1)]),p(`span`,R,[l(H).preference===e.value?(u(),i(_,{key:0,icon:`vuesax-linear:tick-circle`,size:20,"stroke-width":1.75})):a(``,!0)])],10,N)),64))])]),c(C,{block:``,onClick:q},{default:g(()=>[...f[5]||=[r(`ذخیره تنظیمات`,-1)]]),_:1}),U.value?(u(),s(`p`,z,`تنظیمات نمایش ذخیره شد ✓`)):a(``,!0),p(`button`,{type:`button`,class:`settings-logout`,onClick:J},`خروج`)])]),_:1}))}}),[[`__scopeId`,`data-v-f16dc950`]]);export{B as default}; import{A as e,Ct as t,Et as n,L as r,M as i,N as a,O as o,P as s,R as c,St as l,Y as u,Z as d,_ as f,j as p,mt as m,n as h,rt as g,t as _,z as v}from"./BasicIcon-C6boaehV.js";import{u as y}from"./dist-CUUvbY4x.js";import{a as b,c as x,d as S,n as C}from"./index-DzzUgmYT.js";import{t as w}from"./ProfileSubpageLayout-PN2YWH-s.js";var T={class:`settings-page`},E={class:`settings-links`,"aria-label":`حساب کاربری`},D={class:`settings-link__icon`,"aria-hidden":`true`},O={class:`settings-link__icon`,"aria-hidden":`true`},k={class:`settings-link__label`},A={key:0},j={class:`display-fieldset`},M={class:`display-grid`,role:`radiogroup`,"aria-label":`حالت نمایش`},N=[`aria-checked`,`onClick`],P={class:`theme-card__icon`,"aria-hidden":`true`},F={class:`theme-card__body`},I={class:`theme-card__label`},L={class:`theme-card__desc`},R={class:`theme-card__check`,"aria-hidden":`true`},z={key:0,class:`display-saved`},B=h(v({__name:`ProfileDisplayView`,setup(h){let v=f(),B=x(),V=S(),H=b(),U=m(!1),{start:W}=y(()=>{U.value=!1},2e3),G=[{value:`light`,label:`روشن`,description:`پس‌زمینه روشن و کنتراست بالا`,icon:`vuesax-linear:sun-1`},{value:`dark`,label:`تاریک`,description:`مناسب استفاده در شب`,icon:`vuesax-linear:moon`},{value:`system`,label:`سیستم`,description:`هم‌تراز با تنظیمات دستگاه`,icon:`vuesax-linear:mobile`}],K=e(()=>V.draftChecks.length);function q(){U.value=!0,W()}function J(){B.logout(),v.replace(`/welcome`)}return(e,f)=>(u(),i(w,{title:`تنظیمات`,subtitle:`نمایش، حساب کاربری و خروج`},{default:g(()=>[p(`div`,T,[p(`nav`,E,[p(`button`,{type:`button`,class:`settings-link`,onClick:f[0]||=e=>l(v).push(`/profile/edit`)},[p(`span`,D,[c(_,{icon:`vuesax-linear:profile-circle`,size:20,"stroke-width":1.5})]),f[2]||=p(`span`,{class:`settings-link__label`},`اطلاعات کاربری`,-1),c(_,{class:`settings-link__chevron`,icon:`vuesax-linear:arrow-left-1`,size:16,"stroke-width":1.5})]),p(`button`,{type:`button`,class:`settings-link`,onClick:f[1]||=e=>l(v).push(`/profile/checks`)},[p(`span`,O,[c(_,{icon:`vuesax-linear:card`,size:20,"stroke-width":1.5})]),p(`span`,k,[f[3]||=r(` لیست چک‌ها `,-1),K.value?(u(),s(`em`,A,`(`+n(K.value)+`)`,1)):a(``,!0)]),c(_,{class:`settings-link__chevron`,icon:`vuesax-linear:arrow-left-1`,size:16,"stroke-width":1.5})])]),p(`fieldset`,j,[f[4]||=p(`legend`,{class:`display-fieldset__legend`},`حالت نمایش`,-1),p(`div`,M,[(u(),s(o,null,d(G,e=>p(`button`,{key:e.value,type:`button`,class:t([`theme-card`,{"theme-card--active":l(H).preference===e.value}]),role:`radio`,"aria-checked":l(H).preference===e.value,onClick:t=>l(H).preference=e.value},[p(`span`,P,[c(_,{icon:e.icon,size:26,"stroke-width":1.5},null,8,[`icon`])]),p(`span`,F,[p(`strong`,I,n(e.label),1),p(`span`,L,n(e.description),1)]),p(`span`,R,[l(H).preference===e.value?(u(),i(_,{key:0,icon:`vuesax-linear:tick-circle`,size:20,"stroke-width":1.75})):a(``,!0)])],10,N)),64))])]),c(C,{block:``,onClick:q},{default:g(()=>[...f[5]||=[r(`ذخیره تنظیمات`,-1)]]),_:1}),U.value?(u(),s(`p`,z,`تنظیمات نمایش ذخیره شد ✓`)):a(``,!0),p(`button`,{type:`button`,class:`settings-logout`,onClick:J},`خروج`)])]),_:1}))}}),[[`__scopeId`,`data-v-f16dc950`]]);export{B as default};
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.notif-box[data-v-7e884b50]{flex-direction:column;gap:10px;display:flex}.notif-box__head[data-v-7e884b50]{justify-content:space-between;align-items:center;gap:12px;padding-bottom:4px;display:flex}.notif-box__count[data-v-7e884b50]{color:var(--color-text-muted);font-size:.8125rem}.notif-box__mark[data-v-7e884b50]{width:auto;padding-inline:14px}.notif-row[data-v-7e884b50]{text-align:right;border:1px solid var(--color-border);background:var(--color-surface);width:100%;color:var(--color-text);cursor:pointer;border-radius:14px;grid-template-columns:40px minmax(0,1fr);align-items:flex-start;gap:12px;padding:14px 16px;font-family:inherit;transition:border-color .15s;display:grid}.notif-row[data-v-7e884b50]:hover{border-color:var(--color-primary)}.notif-row--unread[data-v-7e884b50]{background:var(--color-tab-inactive-bg)}.notif-row__icon[data-v-7e884b50]{background:var(--color-info-light);width:40px;height:40px;color:var(--color-primary);border-radius:12px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.notif-row--success .notif-row__icon[data-v-7e884b50]{background:var(--color-success-light);color:var(--color-success)}.notif-row--warning .notif-row__icon[data-v-7e884b50]{background:var(--color-warning-light);color:var(--color-warning)}.notif-row--danger .notif-row__icon[data-v-7e884b50]{background:var(--color-danger-light);color:var(--color-danger)}.notif-row__body[data-v-7e884b50]{flex-direction:column;gap:4px;min-width:0;display:flex}.notif-row__title-row[data-v-7e884b50]{align-items:center;gap:8px;display:flex}.notif-row__title[data-v-7e884b50]{font-size:.875rem;font-weight:700}.notif-row__text[data-v-7e884b50]{color:var(--color-text-muted);font-size:.8125rem;line-height:1.6}.notif-row__time[data-v-7e884b50]{color:var(--color-text-muted);font-size:.6875rem}.notif-row__dot[data-v-7e884b50]{background:var(--color-danger);border-radius:50%;flex-shrink:0;width:8px;height:8px}@media (width>=768px){.notif-box[data-v-7e884b50]{gap:12px}.notif-row[data-v-7e884b50]{grid-template-columns:44px minmax(0,1fr);gap:16px;padding:16px 18px}.notif-row__icon[data-v-7e884b50]{width:44px;height:44px}.notif-row__title[data-v-7e884b50]{font-size:.9375rem}.notif-row__text[data-v-7e884b50]{font-size:.875rem}.notif-row__time[data-v-7e884b50]{margin-top:2px}}
+1
View File
@@ -0,0 +1 @@
import{A as e,Ct as t,Et as n,L as r,M as i,N as a,O as o,P as s,R as c,St as l,Y as u,Z as d,_ as f,j as p,n as m,q as h,rt as g,t as _,z as v}from"./BasicIcon-C6boaehV.js";import{i as y}from"./format-BHJptHdF.js";import{n as b,u as x}from"./index-DzzUgmYT.js";import{t as S}from"./EmptyState-C_QQeA_h.js";import{t as C}from"./ProfileSubpageLayout-PN2YWH-s.js";var w={key:0,class:`notif-box`},T={class:`notif-box__head`},E={class:`notif-box__count`},D=[`onClick`],O={class:`notif-row__icon`,"aria-hidden":`true`},k={class:`notif-row__body`},A={class:`notif-row__title-row`},j={class:`notif-row__title`},M={key:0,class:`notif-row__dot`,"aria-label":`خوانده‌نشده`},N={class:`notif-row__text`},P={class:`notif-row__time`},F=m(v({__name:`ProfileNotificationsView`,setup(m){let v=f(),F=x(),I=e(()=>F.items),L=e(()=>F.hasUnread),R={info:`vuesax-linear:information`,success:`vuesax-linear:tick-circle`,warning:`vuesax-linear:timer`,danger:`vuesax-linear:close-circle`};h(()=>{F.hydrate()});async function z(e){await F.markRead(e.id),e.link&&v.push(e.link)}return(e,f)=>(u(),i(C,{title:`اعلان‌ها`,subtitle:`سفارش‌ها، پیشنهاد محصول و سایر اعلان‌ها`},{default:g(()=>[I.value.length?(u(),s(`div`,w,[p(`div`,T,[p(`span`,E,n(L.value?`${l(F).unreadCount} اعلان خوانده‌نشده`:`همه خوانده شده`),1),L.value?(u(),i(b,{key:0,variant:`outline`,class:`notif-box__mark`,onClick:f[0]||=e=>l(F).markAllRead()},{default:g(()=>[...f[2]||=[r(` خواندن همه `,-1)]]),_:1})):a(``,!0)]),(u(!0),s(o,null,d(I.value,e=>(u(),s(`button`,{key:e.id,type:`button`,class:t([`notif-row`,[`notif-row--${e.tone}`,{"notif-row--unread":!e.isRead}]]),onClick:t=>z(e)},[p(`span`,O,[c(_,{icon:R[e.tone],size:22,"stroke-width":1.5},null,8,[`icon`])]),p(`span`,k,[p(`span`,A,[p(`strong`,j,n(e.title),1),e.isRead?a(``,!0):(u(),s(`span`,M))]),p(`span`,N,n(e.body),1),p(`span`,P,n(l(y)(e.sentAt)),1)])],10,D))),128))])):(u(),i(S,{key:1,icon:`vuesax-linear:notification-bing`,title:`اعلانی ندارید`,message:`وقتی فاکتور سفارشتان صادر شود، پیشنهاد محصولی برایتان ثبت شود یا وضعیت پرداخت تغییر کند، همین‌جا خبردار می‌شوید.`},{default:g(()=>[c(b,{variant:`outline`,onClick:f[1]||=e=>l(v).push(`/orders`)},{default:g(()=>[...f[3]||=[r(`سفارش‌های من`,-1)]]),_:1})]),_:1}))]),_:1}))}}),[[`__scopeId`,`data-v-7e884b50`]]);export{F as default};
-1
View File
@@ -1 +0,0 @@
.notif-box[data-v-57190756]{flex-direction:column;gap:10px;display:flex}.notif-box__head[data-v-57190756]{justify-content:space-between;align-items:center;gap:12px;padding-bottom:4px;display:flex}.notif-box__count[data-v-57190756]{color:var(--color-text-muted);font-size:.8125rem}.notif-box__mark[data-v-57190756]{width:auto;padding-inline:14px}.notif-row[data-v-57190756]{text-align:right;border:1px solid var(--color-border);background:var(--color-surface);width:100%;color:var(--color-text);cursor:pointer;border-radius:14px;grid-template-columns:40px minmax(0,1fr);align-items:flex-start;gap:12px;padding:14px 16px;font-family:inherit;transition:border-color .15s;display:grid}.notif-row[data-v-57190756]:hover{border-color:var(--color-primary)}.notif-row--unread[data-v-57190756]{background:var(--color-elevated)}.notif-row__icon[data-v-57190756]{background:var(--color-info-light);width:40px;height:40px;color:var(--color-primary);border-radius:12px;flex-shrink:0;justify-content:center;align-items:center;display:flex}.notif-row--success .notif-row__icon[data-v-57190756]{background:var(--color-success-light);color:var(--color-success)}.notif-row--warning .notif-row__icon[data-v-57190756]{background:var(--color-warning-light);color:var(--color-warning)}.notif-row--danger .notif-row__icon[data-v-57190756]{background:var(--color-danger-light);color:var(--color-danger)}.notif-row__body[data-v-57190756]{flex-direction:column;gap:4px;min-width:0;display:flex}.notif-row__title-row[data-v-57190756]{align-items:center;gap:8px;display:flex}.notif-row__title[data-v-57190756]{font-size:.875rem;font-weight:700}.notif-row__text[data-v-57190756]{color:var(--color-text-muted);font-size:.8125rem;line-height:1.6}.notif-row__time[data-v-57190756]{color:var(--color-text-muted);font-size:.6875rem}.notif-row__dot[data-v-57190756]{background:var(--color-danger);border-radius:50%;flex-shrink:0;width:8px;height:8px}@media (width>=768px){.notif-box[data-v-57190756]{gap:12px}.notif-row[data-v-57190756]{grid-template-columns:44px minmax(0,1fr);gap:16px;padding:16px 18px}.notif-row__icon[data-v-57190756]{width:44px;height:44px}.notif-row__title[data-v-57190756]{font-size:.9375rem}.notif-row__text[data-v-57190756]{font-size:.875rem}.notif-row__time[data-v-57190756]{margin-top:2px}}
-1
View File
@@ -1 +0,0 @@
import{A as e,Ct as t,Et as n,L as r,M as i,N as a,O as o,P as s,R as c,St as l,Y as u,Z as d,_ as f,j as p,n as m,q as h,rt as g,t as _,z as v}from"./BasicIcon-C6boaehV.js";import{i as y}from"./format-BHJptHdF.js";import{n as b,u as x}from"./index-B6MlTGUp.js";import{t as S}from"./EmptyState-C_QQeA_h.js";import{t as C}from"./ProfileSubpageLayout-PN2YWH-s.js";var w={key:0,class:`notif-box`},T={class:`notif-box__head`},E={class:`notif-box__count`},D=[`onClick`],O={class:`notif-row__icon`,"aria-hidden":`true`},k={class:`notif-row__body`},A={class:`notif-row__title-row`},j={class:`notif-row__title`},M={key:0,class:`notif-row__dot`,"aria-label":`خوانده‌نشده`},N={class:`notif-row__text`},P={class:`notif-row__time`},F=m(v({__name:`ProfileNotificationsView`,setup(m){let v=f(),F=x(),I=e(()=>F.items),L=e(()=>F.hasUnread),R={info:`vuesax-linear:information`,success:`vuesax-linear:tick-circle`,warning:`vuesax-linear:timer`,danger:`vuesax-linear:close-circle`};h(()=>{F.hydrate()});async function z(e){await F.markRead(e.id),e.link&&v.push(e.link)}return(e,f)=>(u(),i(C,{title:`اعلان‌ها`,subtitle:`آخرین خبرهای سفارش‌های شما`},{default:g(()=>[I.value.length?(u(),s(`div`,w,[p(`div`,T,[p(`span`,E,n(L.value?`${l(F).unreadCount} اعلان خوانده‌نشده`:`همه خوانده شده`),1),L.value?(u(),i(b,{key:0,variant:`outline`,class:`notif-box__mark`,onClick:f[0]||=e=>l(F).markAllRead()},{default:g(()=>[...f[2]||=[r(` خواندن همه `,-1)]]),_:1})):a(``,!0)]),(u(!0),s(o,null,d(I.value,e=>(u(),s(`button`,{key:e.id,type:`button`,class:t([`notif-row`,[`notif-row--${e.tone}`,{"notif-row--unread":!e.isRead}]]),onClick:t=>z(e)},[p(`span`,O,[c(_,{icon:R[e.tone],size:22,"stroke-width":1.5},null,8,[`icon`])]),p(`span`,k,[p(`span`,A,[p(`strong`,j,n(e.title),1),e.isRead?a(``,!0):(u(),s(`span`,M))]),p(`span`,N,n(e.body),1),p(`span`,P,n(l(y)(e.sentAt)),1)])],10,D))),128))])):(u(),i(S,{key:1,icon:`vuesax-linear:notification-bing`,title:`اعلانی ندارید`,message:`وقتی فاکتور سفارشتان صادر شود یا وضعیت چک و پرداخت تغییر کند، همین‌جا خبردار می‌شوید.`},{default:g(()=>[c(b,{variant:`outline`,onClick:f[1]||=e=>l(v).push(`/orders`)},{default:g(()=>[...f[3]||=[r(`سفارش‌های من`,-1)]]),_:1})]),_:1}))]),_:1}))}}),[[`__scopeId`,`data-v-57190756`]]);export{F as default};
@@ -1 +1 @@
import{L as e,M as t,N as n,P as r,R as i,Y as a,j as o,mt as s,n as c,rt as l,z as u}from"./BasicIcon-C6boaehV.js";import{n as d}from"./index-B6MlTGUp.js";import{t as f}from"./FormErrorBanner-CjdBfIQT.js";import{n as p,r as m,t as h}from"./formErrors-DglFjYkZ.js";import{t as g}from"./BaseInput-qoEXOHCQ.js";import{t as _}from"./ProfileSubpageLayout-PN2YWH-s.js";var v={class:`settings-form`},y={key:0,class:`settings-form__saved`},b=c(u({__name:`ProfileSecurityView`,setup(c){let u=s(``),b=s(``),x=s(``),S=s({}),C=s(``),w=s(!1);function T(e){S.value=p(S.value,e),m(S.value)||(C.value=``)}function E(){S.value={},C.value=``,w.value=!1;let e={};if(u.value||(e.currentPassword=`رمز فعلی الزامی است.`),b.value.length<6&&(e.newPassword=`رمز جدید باید حداقل ۶ کاراکتر باشد.`),x.value!==b.value&&(e.confirmPassword=`تکرار رمز با رمز جدید یکسان نیست.`),m(e)){S.value=e,C.value=h;return}u.value=``,b.value=``,x.value=``,w.value=!0,setTimeout(()=>{w.value=!1},2e3)}return(s,c)=>(a(),t(_,{title:`امنیت حساب`,subtitle:`تغییر رمز عبور و تنظیمات ورود`},{default:l(()=>[i(f,{message:C.value},null,8,[`message`]),o(`div`,v,[i(g,{modelValue:u.value,"onUpdate:modelValue":[c[0]||=e=>u.value=e,c[1]||=e=>T(`currentPassword`)],type:`password`,label:`رمز عبور فعلی`,error:S.value.currentPassword},null,8,[`modelValue`,`error`]),i(g,{modelValue:b.value,"onUpdate:modelValue":[c[2]||=e=>b.value=e,c[3]||=e=>T(`newPassword`)],type:`password`,label:`رمز عبور جدید`,error:S.value.newPassword},null,8,[`modelValue`,`error`]),i(g,{modelValue:x.value,"onUpdate:modelValue":[c[4]||=e=>x.value=e,c[5]||=e=>T(`confirmPassword`)],type:`password`,label:`تکرار رمز عبور جدید`,error:S.value.confirmPassword},null,8,[`modelValue`,`error`]),i(d,{block:``,onClick:E},{default:l(()=>[...c[6]||=[e(`ذخیره رمز جدید`,-1)]]),_:1}),w.value?(a(),r(`p`,y,`رمز عبور با موفقیت تغییر کرد ✓`)):n(``,!0)])]),_:1}))}}),[[`__scopeId`,`data-v-074827a2`]]);export{b as default}; import{L as e,M as t,N as n,P as r,R as i,Y as a,j as o,mt as s,n as c,rt as l,z as u}from"./BasicIcon-C6boaehV.js";import{n as d}from"./index-DzzUgmYT.js";import{t as f}from"./FormErrorBanner-CjdBfIQT.js";import{n as p,r as m,t as h}from"./formErrors-DglFjYkZ.js";import{t as g}from"./BaseInput-qoEXOHCQ.js";import{t as _}from"./ProfileSubpageLayout-PN2YWH-s.js";var v={class:`settings-form`},y={key:0,class:`settings-form__saved`},b=c(u({__name:`ProfileSecurityView`,setup(c){let u=s(``),b=s(``),x=s(``),S=s({}),C=s(``),w=s(!1);function T(e){S.value=p(S.value,e),m(S.value)||(C.value=``)}function E(){S.value={},C.value=``,w.value=!1;let e={};if(u.value||(e.currentPassword=`رمز فعلی الزامی است.`),b.value.length<6&&(e.newPassword=`رمز جدید باید حداقل ۶ کاراکتر باشد.`),x.value!==b.value&&(e.confirmPassword=`تکرار رمز با رمز جدید یکسان نیست.`),m(e)){S.value=e,C.value=h;return}u.value=``,b.value=``,x.value=``,w.value=!0,setTimeout(()=>{w.value=!1},2e3)}return(s,c)=>(a(),t(_,{title:`امنیت حساب`,subtitle:`تغییر رمز عبور و تنظیمات ورود`},{default:l(()=>[i(f,{message:C.value},null,8,[`message`]),o(`div`,v,[i(g,{modelValue:u.value,"onUpdate:modelValue":[c[0]||=e=>u.value=e,c[1]||=e=>T(`currentPassword`)],type:`password`,label:`رمز عبور فعلی`,error:S.value.currentPassword},null,8,[`modelValue`,`error`]),i(g,{modelValue:b.value,"onUpdate:modelValue":[c[2]||=e=>b.value=e,c[3]||=e=>T(`newPassword`)],type:`password`,label:`رمز عبور جدید`,error:S.value.newPassword},null,8,[`modelValue`,`error`]),i(g,{modelValue:x.value,"onUpdate:modelValue":[c[4]||=e=>x.value=e,c[5]||=e=>T(`confirmPassword`)],type:`password`,label:`تکرار رمز عبور جدید`,error:S.value.confirmPassword},null,8,[`modelValue`,`error`]),i(d,{block:``,onClick:E},{default:l(()=>[...c[6]||=[e(`ذخیره رمز جدید`,-1)]]),_:1}),w.value?(a(),r(`p`,y,`رمز عبور با موفقیت تغییر کرد ✓`)):n(``,!0)])]),_:1}))}}),[[`__scopeId`,`data-v-074827a2`]]);export{b as default};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.reminder-detail[data-v-59c58cf4]{background:var(--color-bg)}.detail-scroll[data-v-59c58cf4]{padding:12px var(--page-padding) 24px}.form-error[data-v-59c58cf4]{color:var(--color-danger,#b91c1c);margin:0 0 12px;font-size:.85rem}.layout[data-v-59c58cf4]{flex-direction:column;gap:12px;display:flex}@media (width>=1024px){.layout[data-v-59c58cf4]{grid-template-columns:1.1fr 1fr 1fr;align-items:start;display:grid}}.panel[data-v-59c58cf4]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px}.panel h2[data-v-59c58cf4]{margin:0 0 14px;font-size:1rem}.field[data-v-59c58cf4]{margin-bottom:14px}.field>span[data-v-59c58cf4]{color:var(--color-text-muted);margin-bottom:4px;font-size:.78rem;display:block}.customer-line[data-v-59c58cf4]{align-items:center;gap:10px;display:flex}.avatar[data-v-59c58cf4]{border-radius:999px;place-items:center;width:40px;height:40px;font-weight:700;display:grid}.customer-line p[data-v-59c58cf4]{color:var(--color-text-muted);margin:2px 0 0;font-size:.8rem}.actions[data-v-59c58cf4]{grid-template-columns:1fr 1fr;gap:8px;margin-top:8px;display:grid}.btn-outline[data-v-59c58cf4],.btn-primary[data-v-59c58cf4]{cursor:pointer;border-radius:12px;min-height:44px;font-weight:700}.btn-outline[data-v-59c58cf4]{border:1px solid var(--color-border);background:var(--color-surface)}.btn-primary[data-v-59c58cf4]{background:var(--color-primary);color:#fff;border:none}.btn-primary[data-v-59c58cf4]:disabled{opacity:.7;cursor:not-allowed}.history-row[data-v-59c58cf4]{border-bottom:1px solid var(--color-border);justify-content:space-between;gap:10px;padding:10px 0;display:flex}.history-row p[data-v-59c58cf4]{color:var(--color-text-muted);margin:2px 0 0;font-size:.78rem}.see-all[data-v-59c58cf4]{cursor:pointer;background:#eef1ef;border:none;border-radius:12px;width:100%;min-height:44px;margin-top:12px;font-weight:600}.chart-panel__head[data-v-59c58cf4]{justify-content:space-between;align-items:center;display:flex}.chart-panel__head span[data-v-59c58cf4]{color:var(--color-text-muted);font-size:.8rem}.bars[data-v-59c58cf4]{grid-template-columns:repeat(6,1fr);align-items:end;gap:8px;height:140px;margin:16px 0;display:grid}.bar[data-v-59c58cf4]{flex-direction:column-reverse;gap:2px;height:100%;display:flex}.seg[data-v-59c58cf4]{border-radius:4px 4px 0 0;width:100%;display:block}.seg--a[data-v-59c58cf4]{background:var(--color-primary)}.seg--b[data-v-59c58cf4]{background:#3b82f6}.seg--c[data-v-59c58cf4]{background:#a78bfa}.stats-grid[data-v-59c58cf4]{grid-template-columns:1fr 1fr;gap:8px;display:grid}.stats-grid div[data-v-59c58cf4]{background:var(--color-bg);border-radius:12px;padding:10px}.stats-grid span[data-v-59c58cf4]{color:var(--color-text-muted);font-size:.72rem;display:block}.stats-grid strong[data-v-59c58cf4]{margin-top:4px;display:block}.empty[data-v-59c58cf4]{text-align:center;padding:40px}
-1
View File
@@ -1 +0,0 @@
.reminder-detail[data-v-9fc8e0f1]{background:var(--color-bg)}.detail-scroll[data-v-9fc8e0f1]{padding:12px var(--page-padding) 24px}.form-error[data-v-9fc8e0f1]{color:var(--color-danger,#b91c1c);margin:0 0 12px;font-size:.85rem}.layout[data-v-9fc8e0f1]{flex-direction:column;gap:12px;display:flex}@media (width>=1024px){.layout[data-v-9fc8e0f1]{grid-template-columns:1.1fr 1fr 1fr;align-items:start;display:grid}}.panel[data-v-9fc8e0f1]{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:16px}.panel h2[data-v-9fc8e0f1]{margin:0 0 14px;font-size:1rem}.field[data-v-9fc8e0f1]{margin-bottom:14px}.field>span[data-v-9fc8e0f1]{color:var(--color-text-muted);margin-bottom:4px;font-size:.78rem;display:block}.customer-line[data-v-9fc8e0f1]{align-items:center;gap:10px;display:flex}.avatar[data-v-9fc8e0f1]{border-radius:999px;place-items:center;width:40px;height:40px;font-weight:700;display:grid}.customer-line p[data-v-9fc8e0f1]{color:var(--color-text-muted);margin:2px 0 0;font-size:.8rem}.actions[data-v-9fc8e0f1]{grid-template-columns:1fr 1fr;gap:8px;margin-top:8px;display:grid}.btn-outline[data-v-9fc8e0f1],.btn-primary[data-v-9fc8e0f1]{cursor:pointer;border-radius:12px;min-height:44px;font-weight:700}.btn-outline[data-v-9fc8e0f1]{border:1px solid var(--color-border);background:#fff}.btn-primary[data-v-9fc8e0f1]{background:var(--color-primary);color:#fff;border:none}.btn-primary[data-v-9fc8e0f1]:disabled{opacity:.7;cursor:not-allowed}.history-row[data-v-9fc8e0f1]{border-bottom:1px solid var(--color-border);justify-content:space-between;gap:10px;padding:10px 0;display:flex}.history-row p[data-v-9fc8e0f1]{color:var(--color-text-muted);margin:2px 0 0;font-size:.78rem}.see-all[data-v-9fc8e0f1]{cursor:pointer;background:#eef1ef;border:none;border-radius:12px;width:100%;min-height:44px;margin-top:12px;font-weight:600}.chart-panel__head[data-v-9fc8e0f1]{justify-content:space-between;align-items:center;display:flex}.chart-panel__head span[data-v-9fc8e0f1]{color:var(--color-text-muted);font-size:.8rem}.bars[data-v-9fc8e0f1]{grid-template-columns:repeat(6,1fr);align-items:end;gap:8px;height:140px;margin:16px 0;display:grid}.bar[data-v-9fc8e0f1]{flex-direction:column-reverse;gap:2px;height:100%;display:flex}.seg[data-v-9fc8e0f1]{border-radius:4px 4px 0 0;width:100%;display:block}.seg--a[data-v-9fc8e0f1]{background:var(--color-primary)}.seg--b[data-v-9fc8e0f1]{background:#3b82f6}.seg--c[data-v-9fc8e0f1]{background:#a78bfa}.stats-grid[data-v-9fc8e0f1]{grid-template-columns:1fr 1fr;gap:8px;display:grid}.stats-grid div[data-v-9fc8e0f1]{background:var(--color-bg);border-radius:12px;padding:10px}.stats-grid span[data-v-9fc8e0f1]{color:var(--color-text-muted);font-size:.72rem;display:block}.stats-grid strong[data-v-9fc8e0f1]{margin-top:4px;display:block}.empty[data-v-9fc8e0f1]{text-align:center;padding:40px}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
.reminders-page[data-v-5f336dcc]{background:var(--color-bg)}.top[data-v-5f336dcc]{padding:10px var(--page-padding);background:var(--color-surface);color:var(--color-text);grid-template-columns:40px 1fr auto;align-items:center;display:grid}.top h1[data-v-5f336dcc]{text-align:center;margin:0;font-size:1rem}.top__actions[data-v-5f336dcc]{align-items:center;display:flex}.icon-btn[data-v-5f336dcc]{cursor:pointer;width:40px;height:40px;color:var(--color-text);background:0 0;border:none;place-items:center;font-size:1.4rem;display:grid}.reminders-scroll[data-v-5f336dcc]{padding:12px var(--page-padding) 24px;flex-direction:column;gap:12px;display:flex}.filters[data-v-5f336dcc]{gap:12px;display:flex;overflow-x:auto}.filter[data-v-5f336dcc]{color:var(--color-text-muted);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:8px 2px;position:relative}.filter--active[data-v-5f336dcc]{color:var(--color-primary);font-weight:700}.filter--active[data-v-5f336dcc]:after{content:"";inset-inline:0;background:var(--color-primary);border-radius:999px;height:3px;position:absolute;bottom:0}.count-badge[data-v-5f336dcc]{background:var(--color-danger);color:#fff;border-radius:999px;place-items:center;min-width:18px;height:18px;margin-inline-start:4px;font-size:.65rem;display:inline-grid}.summary[data-v-5f336dcc]{background:var(--color-primary);color:#fff;border-radius:var(--radius-lg);flex-direction:column;gap:6px;padding:16px;display:flex}.summary__label[data-v-5f336dcc]{opacity:.92;font-size:.85rem}.summary__count[data-v-5f336dcc]{align-items:baseline;gap:6px;display:flex}.summary__count strong[data-v-5f336dcc]{font-size:2rem;line-height:1}.summary__count span[data-v-5f336dcc]{font-size:.95rem;font-weight:600}.summary__sub[data-v-5f336dcc]{opacity:.88;margin:4px 0 0;font-size:.78rem}.reminder-card[data-v-5f336dcc]{background:var(--color-surface);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);cursor:pointer;grid-template-rows:auto 1fr;grid-template-columns:24px 1fr 40px;gap:4px 10px;padding:12px;display:grid}.time[data-v-5f336dcc]{color:var(--color-text-muted);grid-area:1/1/auto/3;font-size:.8rem;font-weight:700}.check[data-v-5f336dcc]{cursor:pointer;background:0 0;border:1.5px solid #cbd5e1;border-radius:999px;grid-area:2/1;align-self:start;width:22px;height:22px;margin-top:4px}.reminder-card__body[data-v-5f336dcc]{grid-area:2/2}.avatar[data-v-5f336dcc]{border-radius:999px;grid-area:1/3/3;place-self:start end;place-items:center;width:40px;height:40px;font-size:.85rem;font-weight:700;display:grid}.reminder-card__top[data-v-5f336dcc]{justify-content:space-between;gap:8px;display:flex}.customer[data-v-5f336dcc]{margin:6px 0 0;font-size:.85rem;font-weight:600}.contact[data-v-5f336dcc],.desc[data-v-5f336dcc]{color:var(--color-text-muted);margin:2px 0 0;font-size:.75rem}.tag[data-v-5f336dcc]{border-radius:999px;margin-top:6px;padding:2px 8px;font-size:.65rem;display:inline-block}.tag--green[data-v-5f336dcc]{background:var(--color-primary-soft);color:var(--color-primary)}.tag--blue[data-v-5f336dcc]{color:#0369a1;background:#e0f2fe}.tag--purple[data-v-5f336dcc]{color:#6d28d9;background:#ede9fe}.completed-bar[data-v-5f336dcc]{border-radius:var(--radius-md);cursor:pointer;background:#ecefee;border:none;justify-content:space-between;padding:14px;font-weight:600;display:flex}.new-btn[data-v-5f336dcc]{background:var(--color-primary);color:#fff;cursor:pointer;border:none;border-radius:14px;justify-content:center;align-items:center;gap:8px;width:100%;min-height:52px;font-weight:700;display:flex}.create-panel[data-v-5f336dcc]{background:var(--color-surface);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);flex-direction:column;gap:10px;padding:14px;display:flex}.create-panel h3[data-v-5f336dcc]{margin:0;font-size:.95rem}.create-panel label[data-v-5f336dcc]{color:var(--color-text-muted);flex-direction:column;gap:4px;font-size:.8rem;display:flex}.create-panel input[data-v-5f336dcc],.create-panel select[data-v-5f336dcc],.create-panel textarea[data-v-5f336dcc]{border:1px solid var(--color-border);font:inherit;color:var(--color-text);background:var(--color-surface);border-radius:10px;padding:10px 12px}.create-row[data-v-5f336dcc]{grid-template-columns:1fr 1fr;gap:8px;display:grid}.create-actions[data-v-5f336dcc]{grid-template-columns:1fr 1.2fr;gap:8px;display:grid}.btn-cancel[data-v-5f336dcc],.btn-save[data-v-5f336dcc]{cursor:pointer;border-radius:12px;min-height:44px;font-weight:700}.btn-cancel[data-v-5f336dcc]{border:1px solid var(--color-border);background:var(--color-surface)}.btn-save[data-v-5f336dcc]{background:var(--color-primary);color:#fff;border:none}.btn-save[data-v-5f336dcc]:disabled{opacity:.7}.create-error[data-v-5f336dcc]{color:#b42318;margin:0;font-size:.8rem}
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
.reminders-page[data-v-89857cb4]{background:var(--color-bg)}.top[data-v-89857cb4]{padding:10px var(--page-padding);background:var(--color-surface);color:var(--color-text);grid-template-columns:40px 1fr auto;align-items:center;display:grid}.top h1[data-v-89857cb4]{text-align:center;margin:0;font-size:1rem}.top__actions[data-v-89857cb4]{align-items:center;display:flex}.icon-btn[data-v-89857cb4]{cursor:pointer;width:40px;height:40px;color:var(--color-text);background:0 0;border:none;place-items:center;font-size:1.4rem;display:grid}.reminders-scroll[data-v-89857cb4]{padding:12px var(--page-padding) 24px;flex-direction:column;gap:12px;display:flex}.filters[data-v-89857cb4]{gap:12px;display:flex;overflow-x:auto}.filter[data-v-89857cb4]{color:var(--color-text-muted);cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:8px 2px;position:relative}.filter--active[data-v-89857cb4]{color:var(--color-primary);font-weight:700}.filter--active[data-v-89857cb4]:after{content:"";inset-inline:0;background:var(--color-primary);border-radius:999px;height:3px;position:absolute;bottom:0}.count-badge[data-v-89857cb4]{background:var(--color-danger);color:#fff;border-radius:999px;place-items:center;min-width:18px;height:18px;margin-inline-start:4px;font-size:.65rem;display:inline-grid}.summary[data-v-89857cb4]{background:var(--color-primary);color:#fff;border-radius:var(--radius-lg);flex-direction:column;gap:6px;padding:16px;display:flex}.summary__label[data-v-89857cb4]{opacity:.92;font-size:.85rem}.summary__count[data-v-89857cb4]{align-items:baseline;gap:6px;display:flex}.summary__count strong[data-v-89857cb4]{font-size:2rem;line-height:1}.summary__count span[data-v-89857cb4]{font-size:.95rem;font-weight:600}.summary__sub[data-v-89857cb4]{opacity:.88;margin:4px 0 0;font-size:.78rem}.reminder-card[data-v-89857cb4]{background:var(--color-surface);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);cursor:pointer;grid-template-rows:auto 1fr;grid-template-columns:24px 1fr 40px;gap:4px 10px;padding:12px;display:grid}.time[data-v-89857cb4]{color:var(--color-text-muted);grid-area:1/1/auto/3;font-size:.8rem;font-weight:700}.check[data-v-89857cb4]{cursor:pointer;background:0 0;border:1.5px solid #cbd5e1;border-radius:999px;grid-area:2/1;align-self:start;width:22px;height:22px;margin-top:4px}.reminder-card__body[data-v-89857cb4]{grid-area:2/2}.avatar[data-v-89857cb4]{border-radius:999px;grid-area:1/3/3;place-self:start end;place-items:center;width:40px;height:40px;font-size:.85rem;font-weight:700;display:grid}.reminder-card__top[data-v-89857cb4]{justify-content:space-between;gap:8px;display:flex}.customer[data-v-89857cb4]{margin:6px 0 0;font-size:.85rem;font-weight:600}.contact[data-v-89857cb4],.desc[data-v-89857cb4]{color:var(--color-text-muted);margin:2px 0 0;font-size:.75rem}.tag[data-v-89857cb4]{border-radius:999px;margin-top:6px;padding:2px 8px;font-size:.65rem;display:inline-block}.tag--green[data-v-89857cb4]{background:var(--color-primary-soft);color:var(--color-primary)}.tag--blue[data-v-89857cb4]{color:#0369a1;background:#e0f2fe}.tag--purple[data-v-89857cb4]{color:#6d28d9;background:#ede9fe}.completed-bar[data-v-89857cb4]{border-radius:var(--radius-md);cursor:pointer;background:#ecefee;border:none;justify-content:space-between;padding:14px;font-weight:600;display:flex}.new-btn[data-v-89857cb4]{background:var(--color-primary);color:#fff;cursor:pointer;border:none;border-radius:14px;justify-content:center;align-items:center;gap:8px;width:100%;min-height:52px;font-weight:700;display:flex}.create-panel[data-v-89857cb4]{background:var(--color-surface);border-radius:var(--radius-md);box-shadow:var(--shadow-sm);flex-direction:column;gap:10px;padding:14px;display:flex}.create-panel h3[data-v-89857cb4]{margin:0;font-size:.95rem}.create-panel label[data-v-89857cb4]{color:var(--color-text-muted);flex-direction:column;gap:4px;font-size:.8rem;display:flex}.create-panel input[data-v-89857cb4],.create-panel select[data-v-89857cb4],.create-panel textarea[data-v-89857cb4]{border:1px solid var(--color-border);font:inherit;color:var(--color-text);background:#fff;border-radius:10px;padding:10px 12px}.create-row[data-v-89857cb4]{grid-template-columns:1fr 1fr;gap:8px;display:grid}.create-actions[data-v-89857cb4]{grid-template-columns:1fr 1.2fr;gap:8px;display:grid}.btn-cancel[data-v-89857cb4],.btn-save[data-v-89857cb4]{cursor:pointer;border-radius:12px;min-height:44px;font-weight:700}.btn-cancel[data-v-89857cb4]{border:1px solid var(--color-border);background:#fff}.btn-save[data-v-89857cb4]{background:var(--color-primary);color:#fff;border:none}.btn-save[data-v-89857cb4]:disabled{opacity:.7}.create-error[data-v-89857cb4]{color:#b42318;margin:0;font-size:.8rem}
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
import{Et as e,M as t,O as n,P as r,R as i,St as a,Y as o,Z as s,_ as c,j as l,n as u,t as d,z as f}from"./BasicIcon-C6boaehV.js";import{_ as p,o as m}from"./index-B6MlTGUp.js";import{t as h}from"./login-fabric-CzrIXTnD.js";var g={class:`page welcome-page`},_=[`src`],v={class:`welcome-page__scroll`},y={class:`welcome-page__stage`},b={class:`welcome-brand`},x={class:`welcome-features`},S={class:`welcome-feature__icon`,"aria-hidden":`true`},C={key:0,class:`welcome-feature__star`,viewBox:`0 0 24 24`,fill:`none`},w={class:`welcome-cta__chevron`,"aria-hidden":`true`},T=u(f({__name:`WelcomeView`,setup(u){let f=c(),T=[{icon:`star`,title:`همراه همیشگی کسب‌وکارتان`,subtitle:`با خدمات ویژه مشتریان`},{icon:`vuesax-linear:truck-time`,title:`سفارش عمده`,subtitle:`مستقیم از تولیدکننده`},{icon:`vuesax-linear:element-3`,title:`تنوع بی‌نظیر محصولات`,subtitle:`از کلاسیک تا مدرن`}];function E(){let e=typeof f.currentRoute.value.query.redirect==`string`?f.currentRoute.value.query.redirect:void 0;f.push({name:`login`,query:e?{redirect:e}:void 0})}return(c,u)=>(o(),r(`div`,g,[l(`img`,{class:`welcome-page__bg`,src:a(h),alt:``},null,8,_),u[7]||=l(`div`,{class:`welcome-page__veil`,"aria-hidden":`true`},null,-1),l(`div`,v,[l(`div`,y,[l(`header`,{class:`welcome-top`},[l(`button`,{type:`button`,class:`welcome-skip`,onClick:E},`رد شدن`)]),l(`div`,b,[i(p,{size:88}),u[0]||=l(`h1`,{class:`welcome-brand__fa`},`نادمی تکس`,-1),u[1]||=l(`p`,{class:`welcome-brand__tag`},`انتخاب حرفه‌ای، شروع یک همکاری ماندگار`,-1),u[2]||=l(`span`,{class:`welcome-brand__rule`,"aria-hidden":`true`},null,-1)]),l(`ul`,x,[(o(),r(n,null,s(T,n=>l(`li`,{key:n.title,class:`welcome-feature`},[l(`span`,S,[n.icon===`star`?(o(),r(`svg`,C,[...u[3]||=[l(`path`,{d:`M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z`,stroke:`#fff`,"stroke-width":`1.5`,"stroke-linejoin":`round`},null,-1)]])):(o(),t(d,{key:1,icon:n.icon,size:22,color:`#ffffff`},null,8,[`icon`]))]),l(`strong`,null,e(n.title),1),l(`span`,null,e(n.subtitle),1)])),64))]),u[5]||=l(`div`,{class:`welcome-spacer`,"aria-hidden":`true`},null,-1),u[6]||=l(`div`,{class:`welcome-dots`,"aria-hidden":`true`},[l(`i`,{class:`is-on`}),l(`i`),l(`i`),l(`i`)],-1),l(`button`,{type:`button`,class:`welcome-cta`,onClick:E},[u[4]||=l(`span`,null,`ورود / ثبت‌نام`,-1),l(`span`,w,[i(m,{size:18,color:`#ffffff`})])])])])]))}}),[[`__scopeId`,`data-v-66f13882`]]);export{T as default}; import{Et as e,M as t,O as n,P as r,R as i,St as a,Y as o,Z as s,_ as c,j as l,n as u,t as d,z as f}from"./BasicIcon-C6boaehV.js";import{_ as p,o as m}from"./index-DzzUgmYT.js";import{t as h}from"./login-fabric-CzrIXTnD.js";var g={class:`page welcome-page`},_=[`src`],v={class:`welcome-page__scroll`},y={class:`welcome-page__stage`},b={class:`welcome-brand`},x={class:`welcome-features`},S={class:`welcome-feature__icon`,"aria-hidden":`true`},C={key:0,class:`welcome-feature__star`,viewBox:`0 0 24 24`,fill:`none`},w={class:`welcome-cta__chevron`,"aria-hidden":`true`},T=u(f({__name:`WelcomeView`,setup(u){let f=c(),T=[{icon:`star`,title:`همراه همیشگی کسب‌وکارتان`,subtitle:`با خدمات ویژه مشتریان`},{icon:`vuesax-linear:truck-time`,title:`سفارش عمده`,subtitle:`مستقیم از تولیدکننده`},{icon:`vuesax-linear:element-3`,title:`تنوع بی‌نظیر محصولات`,subtitle:`از کلاسیک تا مدرن`}];function E(){let e=typeof f.currentRoute.value.query.redirect==`string`?f.currentRoute.value.query.redirect:void 0;f.push({name:`login`,query:e?{redirect:e}:void 0})}return(c,u)=>(o(),r(`div`,g,[l(`img`,{class:`welcome-page__bg`,src:a(h),alt:``},null,8,_),u[7]||=l(`div`,{class:`welcome-page__veil`,"aria-hidden":`true`},null,-1),l(`div`,v,[l(`div`,y,[l(`header`,{class:`welcome-top`},[l(`button`,{type:`button`,class:`welcome-skip`,onClick:E},`رد شدن`)]),l(`div`,b,[i(p,{size:88}),u[0]||=l(`h1`,{class:`welcome-brand__fa`},`نادمی تکس`,-1),u[1]||=l(`p`,{class:`welcome-brand__tag`},`انتخاب حرفه‌ای، شروع یک همکاری ماندگار`,-1),u[2]||=l(`span`,{class:`welcome-brand__rule`,"aria-hidden":`true`},null,-1)]),l(`ul`,x,[(o(),r(n,null,s(T,n=>l(`li`,{key:n.title,class:`welcome-feature`},[l(`span`,S,[n.icon===`star`?(o(),r(`svg`,C,[...u[3]||=[l(`path`,{d:`M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z`,stroke:`#fff`,"stroke-width":`1.5`,"stroke-linejoin":`round`},null,-1)]])):(o(),t(d,{key:1,icon:n.icon,size:22,color:`#ffffff`},null,8,[`icon`]))]),l(`strong`,null,e(n.title),1),l(`span`,null,e(n.subtitle),1)])),64))]),u[5]||=l(`div`,{class:`welcome-spacer`,"aria-hidden":`true`},null,-1),u[6]||=l(`div`,{class:`welcome-dots`,"aria-hidden":`true`},[l(`i`,{class:`is-on`}),l(`i`),l(`i`),l(`i`)],-1),l(`button`,{type:`button`,class:`welcome-cta`,onClick:E},[u[4]||=l(`span`,null,`ورود / ثبت‌نام`,-1),l(`span`,w,[i(m,{size:18,color:`#ffffff`})])])])])]))}}),[[`__scopeId`,`data-v-66f13882`]]);export{T 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
@@ -1 +1 @@
import{A as e,_ as t,g as n,tt as r}from"./BasicIcon-C6boaehV.js";import{d as i}from"./index-B6MlTGUp.js";import{n as a}from"./orderInvoice-B0L1Aeuk.js";function o(){let o=n(),s=t(),c=i(),l=e(()=>o.params.id),u=e(()=>c.getOrderById(l.value)),d=e(()=>a(u.value));function f(e){e&&(a(e)||s.replace(`/orders/${e.id}`))}return r(l,async e=>{e&&f(c.getOrderById(e)??await c.fetchOrder(e))},{immediate:!0}),r(u,e=>{f(e)}),{orderId:l,order:u,invoiceReady:d}}export{o as t}; import{A as e,_ as t,g as n,tt as r}from"./BasicIcon-C6boaehV.js";import{d as i}from"./index-DzzUgmYT.js";import{n as a}from"./orderInvoice-B0L1Aeuk.js";function o(){let o=n(),s=t(),c=i(),l=e(()=>o.params.id),u=e(()=>c.getOrderById(l.value)),d=e(()=>a(u.value));function f(e){e&&(a(e)||s.replace(`/orders/${e.id}`))}return r(l,async e=>{e&&f(c.getOrderById(e)??await c.fetchOrder(e))},{immediate:!0}),r(u,e=>{f(e)}),{orderId:l,order:u,invoiceReady:d}}export{o as t};
+2 -2
View File
@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#17379f"> <meta name="theme-color" content="#17379f">
<title>سامانه سفارش عمده پارچه</title> <title>سامانه سفارش عمده پارچه</title>
<script type="module" crossorigin src="/assets/index-B6MlTGUp.js"></script> <script type="module" crossorigin src="/assets/index-DzzUgmYT.js"></script>
<link rel="modulepreload" crossorigin href="/assets/BasicIcon-C6boaehV.js"> <link rel="modulepreload" crossorigin href="/assets/BasicIcon-C6boaehV.js">
<link rel="modulepreload" crossorigin href="/assets/http-BC1hNfZ4.js"> <link rel="modulepreload" crossorigin href="/assets/http-BC1hNfZ4.js">
<link rel="modulepreload" crossorigin href="/assets/dist-CUUvbY4x.js"> <link rel="modulepreload" crossorigin href="/assets/dist-CUUvbY4x.js">
@@ -15,7 +15,7 @@
<link rel="modulepreload" crossorigin href="/assets/validation-BD7oi-2I.js"> <link rel="modulepreload" crossorigin href="/assets/validation-BD7oi-2I.js">
<link rel="modulepreload" crossorigin href="/assets/catalog-s7nVUdlj.js"> <link rel="modulepreload" crossorigin href="/assets/catalog-s7nVUdlj.js">
<link rel="stylesheet" crossorigin href="/assets/BasicIcon-QF_b9Svq.css"> <link rel="stylesheet" crossorigin href="/assets/BasicIcon-QF_b9Svq.css">
<link rel="stylesheet" crossorigin href="/assets/index-CVG_qU1h.css"> <link rel="stylesheet" crossorigin href="/assets/index-8S-fr-dB.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

+27 -15
View File
@@ -19,19 +19,19 @@
--color-purple-light: #ede9fe; --color-purple-light: #ede9fe;
--color-info: #3b6fa0; --color-info: #3b6fa0;
--color-info-light: #eef3f7; --color-info-light: #eef3f7;
--color-bg: #f6f8f7; --color-bg: #eef1f0;
--color-page-background: #f6f8f7; --color-page-background: #eef1f0;
--color-shell-bg: #eef1f0; --color-shell-bg: #eef1f0;
--color-surface: #ffffff; --color-surface: #eef1f0;
--color-border: #e5e9e8; --color-border: #d8dedc;
--color-text: #121212; --color-text: #121212;
--color-text-primary: #121212; --color-text-primary: #121212;
--color-text-muted: #6b7280; --color-text-muted: #6b7280;
--color-text-secondary: #6b7280; --color-text-secondary: #6b7280;
--color-tab-inactive-bg: #f1f5f9; --color-tab-inactive-bg: #e4e8e6;
--color-tab-inactive-text: #64748b; --color-tab-inactive-text: #64748b;
--color-summary-bg: #f1f5f9; --color-summary-bg: #e4e8e6;
--color-elevated: #ffffff; --color-elevated: #eef1f0;
--color-brand-warm: #f3ece3; --color-brand-warm: #f3ece3;
--color-brand-warm-text: #1a202c; --color-brand-warm-text: #1a202c;
--color-brand-warm-muted: #718096; --color-brand-warm-muted: #718096;
@@ -40,9 +40,9 @@
--radius-sm: 8px; --radius-sm: 8px;
--radius-md: 12px; --radius-md: 12px;
--radius-lg: 16px; --radius-lg: 16px;
--shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.04); --shadow-sm: none;
--shadow-md: 0 4px 16px rgba(16, 24, 40, 0.06); --shadow-md: none;
--shadow-card: 0 1px 2px rgba(16, 24, 40, 0.04), 0 4px 16px rgba(16, 24, 40, 0.04); --shadow-card: none;
--shadow-frame: 0 8px 32px rgba(15, 23, 42, 0.1); --shadow-frame: 0 8px 32px rgba(15, 23, 42, 0.1);
--nav-height: 64px; --nav-height: 64px;
--page-padding: 16px; --page-padding: 16px;
@@ -84,9 +84,9 @@ html.dark {
--color-info-light: #1b2740; --color-info-light: #1b2740;
--color-bg: #0b0f14; --color-bg: #0b0f14;
--color-page-background: #0b0f14; --color-page-background: #0b0f14;
--color-shell-bg: #080b10; --color-shell-bg: #0b0f14;
--color-surface: #121821; --color-surface: #0b0f14;
--color-elevated: #1a2230; --color-elevated: #0b0f14;
--color-border: #2a3444; --color-border: #2a3444;
--color-text: #eef2f8; --color-text: #eef2f8;
--color-text-primary: #eef2f8; --color-text-primary: #eef2f8;
@@ -158,6 +158,19 @@ textarea {
font-family: inherit; font-family: inherit;
} }
input:not([type='checkbox']):not([type='radio']):not([type='submit']):not([type='button']):not([type='reset']):not([type='file']):not([type='hidden']):not([type='image']):not([type='range']):not([type='color']),
textarea,
select {
background-color: transparent;
}
.persian-date-input__trigger,
.time-input__trigger,
.search-field__input,
.field-select__trigger {
background-color: transparent;
}
input[type='number']::-webkit-inner-spin-button, input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button { input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
@@ -212,7 +225,7 @@ input[type='number'] {
border-radius: var(--radius-md); border-radius: var(--radius-md);
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
padding: 16px; padding: 16px;
box-shadow: var(--shadow-sm); box-shadow: none;
color: var(--color-text); color: var(--color-text);
} }
@@ -294,7 +307,6 @@ input[type='number'] {
.auth-page .otp-box { .auth-page .otp-box {
border: 1px solid #eeeef1; border: 1px solid #eeeef1;
border-radius: 12px; border-radius: 12px;
background: var(--color-surface);
} }
.auth-page .btn { .auth-page .btn {
@@ -0,0 +1,149 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useAuthStore } from '@/stores/auth'
import { useCrmStore } from '@/stores/crm'
import BaseInput from '@/components/ui/BaseInput.vue'
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
import BaseButton from '@/components/ui/BaseButton.vue'
import { normalizePhone } from '@/utils/format'
import { validatePhone } from '@/utils/validation'
import {
FORM_VALIDATION_SUMMARY,
clearFieldError,
hasFieldErrors,
type FieldErrors,
} from '@/utils/formErrors'
import { ApiError } from '@/services/http'
const emit = defineEmits<{
success: [id?: string]
}>()
const auth = useAuthStore()
const crm = useCrmStore()
const firstName = ref('')
const lastName = ref('')
const phone = ref('')
const businessName = ref('')
const fieldErrors = ref<FieldErrors>({})
const formError = ref('')
const saving = ref(false)
function clearField(key: string) {
fieldErrors.value = clearFieldError(fieldErrors.value, key)
if (!hasFieldErrors(fieldErrors.value)) formError.value = ''
}
async function submit() {
fieldErrors.value = {}
formError.value = ''
if (!auth.isAgent) {
formError.value = 'فقط واسطه می‌تواند مشتری به لیست خود اضافه کند.'
return
}
const normalized = normalizePhone(phone.value)
const errors: FieldErrors = {}
if (!firstName.value.trim()) errors.firstName = 'نام الزامی است.'
if (!lastName.value.trim()) errors.lastName = 'نام خانوادگی الزامی است.'
if (!validatePhone(normalized)) errors.phone = 'شماره موبایل معتبر نیست.'
if (hasFieldErrors(errors)) {
fieldErrors.value = errors
formError.value = FORM_VALIDATION_SUMMARY
return
}
saving.value = true
try {
const created = (await crm.registerCustomer({
firstName: firstName.value.trim(),
lastName: lastName.value.trim(),
phone: normalized,
businessName: businessName.value.trim() || undefined,
})) as { id?: string }
emit('success', created?.id)
} catch (e) {
formError.value =
e instanceof ApiError || e instanceof Error ? e.message : 'ثبت مشتری ناموفق بود.'
} finally {
saving.value = false
}
}
defineExpose({ submit, saving })
</script>
<template>
<div class="customer-create-form">
<p class="customer-create-form__intro">
مشتری به لیست شما اضافه می‌شود و بعد از ورود با همین شماره، سفارش‌هایش به حساب شما وصل می‌ماند.
</p>
<FormErrorBanner :message="formError" />
<div class="customer-create-form__fields">
<BaseInput
v-model="firstName"
label="نام"
placeholder="نام مشتری"
:error="fieldErrors.firstName"
@update:model-value="clearField('firstName')"
/>
<BaseInput
v-model="lastName"
label="نام خانوادگی"
placeholder="نام خانوادگی"
:error="fieldErrors.lastName"
@update:model-value="clearField('lastName')"
/>
<BaseInput
v-model="phone"
label="شماره موبایل"
type="tel"
inputmode="tel"
dir="ltr"
placeholder="۰۹۱۲۳۴۵۶۷۸۹"
:error="fieldErrors.phone"
@update:model-value="clearField('phone')"
/>
<BaseInput
v-model="businessName"
label="نام فروشگاه یا برند (اختیاری)"
placeholder="مثلاً بوتیک رز"
/>
</div>
<BaseButton block :loading="saving" :disabled="saving" @click="submit">
ثبت مشتری
</BaseButton>
</div>
</template>
<style scoped>
.customer-create-form {
display: flex;
flex-direction: column;
gap: 12px;
}
.customer-create-form__intro {
margin: 0;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.7;
}
.customer-create-form__fields {
display: flex;
flex-direction: column;
gap: 12px;
background: var(--color-surface);
border-radius: var(--radius-md);
padding: 16px;
box-shadow: var(--shadow-sm);
}
</style>
@@ -0,0 +1,183 @@
<script setup lang="ts">
import { onBeforeUnmount, watch } from 'vue'
import CustomerCreateForm from '@/components/customers/CustomerCreateForm.vue'
const props = defineProps<{
open: boolean
}>()
const emit = defineEmits<{
close: []
success: [id?: string]
}>()
function close() {
emit('close')
}
function onSuccess(id?: string) {
emit('success', id)
}
function onKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && props.open) close()
}
watch(
() => props.open,
(isOpen) => {
document.body.style.overflow = isOpen ? 'hidden' : ''
if (isOpen) {
window.addEventListener('keydown', onKeydown)
} else {
window.removeEventListener('keydown', onKeydown)
}
},
{ immediate: true },
)
onBeforeUnmount(() => {
document.body.style.overflow = ''
window.removeEventListener('keydown', onKeydown)
})
</script>
<template>
<Teleport to="body">
<Transition name="customer-create-sheet">
<div
v-if="open"
class="customer-create-overlay"
role="dialog"
aria-modal="true"
aria-labelledby="customer-create-title"
@click.self="close"
>
<div class="customer-create-sheet">
<div class="customer-create-sheet__handle" aria-hidden="true" />
<header class="customer-create-sheet__head">
<h2 id="customer-create-title">افزودن مشتری</h2>
<button
type="button"
class="customer-create-sheet__close"
aria-label="بستن"
@click="close"
>
×
</button>
</header>
<CustomerCreateForm @success="onSuccess" />
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.customer-create-overlay {
position: fixed;
inset: 0;
z-index: 200;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: flex-end;
justify-content: center;
padding: 0;
}
.customer-create-sheet {
width: 100%;
max-width: 520px;
max-height: min(92dvh, 680px);
overflow-y: auto;
background: var(--color-bg);
color: var(--color-text);
border-radius: 20px 20px 0 0;
padding: 10px 16px calc(16px + env(safe-area-inset-bottom, 0px));
box-shadow: var(--shadow-md);
display: flex;
flex-direction: column;
gap: 16px;
}
.customer-create-sheet__handle {
width: 40px;
height: 4px;
border-radius: 999px;
background: var(--color-border);
margin: 4px auto 0;
}
.customer-create-sheet__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.customer-create-sheet__head h2 {
margin: 0;
font-size: 1.0625rem;
font-weight: 700;
}
.customer-create-sheet__close {
width: 36px;
height: 36px;
border: none;
border-radius: 999px;
background: var(--color-surface);
color: var(--color-text-muted);
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
padding: 0;
}
.customer-create-sheet-enter-active,
.customer-create-sheet-leave-active {
transition: opacity 0.2s ease;
}
.customer-create-sheet-enter-active .customer-create-sheet,
.customer-create-sheet-leave-active .customer-create-sheet {
transition: transform 0.22s ease;
}
.customer-create-sheet-enter-from,
.customer-create-sheet-leave-to {
opacity: 0;
}
.customer-create-sheet-enter-from .customer-create-sheet,
.customer-create-sheet-leave-to .customer-create-sheet {
transform: translateY(100%);
}
@media (min-width: 768px) {
.customer-create-overlay {
align-items: center;
padding: 24px;
}
.customer-create-sheet {
border-radius: 16px;
max-width: 440px;
max-height: min(85dvh, 620px);
padding: 20px 20px 24px;
background: var(--color-surface);
}
.customer-create-sheet__handle {
display: none;
}
.customer-create-sheet-enter-from .customer-create-sheet,
.customer-create-sheet-leave-to .customer-create-sheet {
transform: translateY(16px) scale(0.98);
}
}
</style>
@@ -1,20 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref } from 'vue' import { onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useCrmStore } from '@/stores/crm' import { useCrmStore } from '@/stores/crm'
import type { CrmCustomer, MeetingInteractionType } from '@/types/crm' import type { CrmCustomer, MeetingInteractionType } from '@/types/crm'
import PageHeader from '@/components/layout/PageHeader.vue'
import PageSkeleton from '@/components/ui/PageSkeleton.vue' import PageSkeleton from '@/components/ui/PageSkeleton.vue'
import IconCheckCircleOutline from '@/components/icons/IconCheckCircleOutline.vue' import PersianDateInput from '@/components/ui/PersianDateInput.vue'
import TimeInput from '@/components/ui/TimeInput.vue'
import IconClipboardEmpty from '@/components/icons/IconClipboardEmpty.vue' import IconClipboardEmpty from '@/components/icons/IconClipboardEmpty.vue'
import IconPhone from '@/components/icons/IconPhone.vue' import IconPhone from '@/components/icons/IconPhone.vue'
import IconMail from '@/components/icons/IconMail.vue' import IconMail from '@/components/icons/IconMail.vue'
import IconEditPen from '@/components/icons/IconEditPen.vue' import IconEditPen from '@/components/icons/IconEditPen.vue'
const route = useRoute() const props = defineProps<{
const router = useRouter() customerId: string
}>()
const emit = defineEmits<{
success: []
}>()
const crm = useCrmStore() const crm = useCrmStore()
const customerId = computed(() => String(route.query.customerId ?? ''))
const customer = ref<CrmCustomer | null>(null) const customer = ref<CrmCustomer | null>(null)
const loading = ref(true) const loading = ref(true)
const saving = ref(false) const saving = ref(false)
@@ -38,32 +42,41 @@ const types: Array<{ id: MeetingInteractionType; label: string; icon: 'clipboard
{ id: 'note', label: 'یادداشت', icon: 'pen' }, { id: 'note', label: 'یادداشت', icon: 'pen' },
] ]
onMounted(async () => { async function loadCustomer() {
if (!customerId.value) { if (!props.customerId) {
loading.value = false loading.value = false
error.value = 'مشتری مشخص نشده است.' error.value = 'مشتری مشخص نشده است.'
customer.value = null
return return
} }
loading.value = true
error.value = null
try { try {
const data = await crm.loadCustomerDetail(customerId.value) const data = await crm.loadCustomerDetail(props.customerId)
customer.value = data.customer customer.value = data.customer
} catch (e) { } catch (e) {
customer.value = null
error.value = e instanceof Error ? e.message : 'بارگذاری مشتری ناموفق بود.' error.value = e instanceof Error ? e.message : 'بارگذاری مشتری ناموفق بود.'
} finally { } finally {
loading.value = false loading.value = false
} }
}) }
onMounted(loadCustomer)
watch(() => props.customerId, loadCustomer)
async function submit() { async function submit() {
if (!customerId.value || !subject.value.trim() || !date.value.trim()) { if (!props.customerId || !subject.value.trim() || !date.value.trim()) {
error.value = 'موضوع و تاریخ الزامی است.' error.value = 'موضوع و تاریخ الزامی است.'
return return
} }
saving.value = true saving.value = true
error.value = null error.value = null
try { try {
await crm.submitMeeting({ await crm.submitMeeting({
customerId: customerId.value, customerId: props.customerId,
type: interactionType.value, type: interactionType.value,
title: subject.value.trim(), title: subject.value.trim(),
subject: subject.value.trim(), subject: subject.value.trim(),
@@ -81,14 +94,9 @@ async function submit() {
reminderTime: reminderOn.value reminderTime: reminderOn.value
? reminderTime.value.trim() || time.value.trim() || '10:00' ? reminderTime.value.trim() || time.value.trim() || '10:00'
: undefined, : undefined,
reminderNote: reminderOn.value reminderNote: reminderOn.value ? reminderNote.value.trim() || undefined : undefined,
? reminderNote.value.trim() || undefined
: undefined,
})
router.push({
path: '/customer',
query: { id: customerId.value, tab: 'reports' },
}) })
emit('success')
} catch (e) { } catch (e) {
error.value = e instanceof Error ? e.message : 'ثبت گزارش ناموفق بود.' error.value = e instanceof Error ? e.message : 'ثبت گزارش ناموفق بود.'
} finally { } finally {
@@ -98,25 +106,12 @@ async function submit() {
</script> </script>
<template> <template>
<div class="page meeting-page"> <div class="meeting-report-form">
<PageHeader title="ثبت گزارش ملاقات" show-back centered @back="router.back()"> <PageSkeleton v-if="loading" variant="form" />
<template #trailing> <p v-else-if="error" class="form-error">{{ error }}</p>
<button
type="button"
class="submit-icon-btn"
aria-label="ثبت گزارش"
:disabled="saving"
@click="submit"
>
<IconCheckCircleOutline :size="22" />
</button>
</template>
</PageHeader>
<div class="page__scroll meeting-scroll"> <template v-if="customer">
<PageSkeleton v-if="loading" variant="form" /> <section class="customer-card">
<p v-else-if="error" class="form-error">{{ error }}</p>
<section v-if="customer" class="customer-card">
<div class="avatar" :style="{ background: customer.avatarColor }"> <div class="avatar" :style="{ background: customer.avatarColor }">
{{ customer.avatarLetter }} {{ customer.avatarLetter }}
</div> </div>
@@ -149,15 +144,9 @@ async function submit() {
<section> <section>
<h3>تاریخ و زمان</h3> <h3>تاریخ و زمان</h3>
<div class="two-cols"> <div class="two-cols two-cols--date">
<label class="field"> <PersianDateInput v-model="date" label="تاریخ" class="meeting-date" />
<span>تاریخ</span> <TimeInput v-model="time" label="ساعت" placeholder="مثلاً ۱۰:۳۰" class="meeting-time" />
<input v-model="date" type="text" />
</label>
<label class="field">
<span>ساعت</span>
<input v-model="time" type="text" />
</label>
</div> </div>
</section> </section>
@@ -199,15 +188,14 @@ async function submit() {
<span>عنوان یادآور</span> <span>عنوان یادآور</span>
<input v-model="reminderTitle" type="text" /> <input v-model="reminderTitle" type="text" />
</label> </label>
<div class="two-cols"> <div class="two-cols two-cols--date">
<label class="field"> <PersianDateInput v-model="reminderDate" label="تاریخ یادآور" class="meeting-date" />
<span>تاریخ یادآور</span> <TimeInput
<input v-model="reminderDate" type="text" /> v-model="reminderTime"
</label> label="ساعت یادآور"
<label class="field"> placeholder="مثلاً ۱۰:۳۰"
<span>ساعت یادآور</span> class="meeting-time"
<input v-model="reminderTime" type="text" /> />
</label>
</div> </div>
<label class="field"> <label class="field">
<span>یادداشت یادآور (اختیاری)</span> <span>یادداشت یادآور (اختیاری)</span>
@@ -215,28 +203,27 @@ async function submit() {
</label> </label>
</template> </template>
</section> </section>
</div>
<footer class="footer">
<button type="button" class="submit" :disabled="saving" @click="submit"> <button type="button" class="submit" :disabled="saving" @click="submit">
{{ saving ? 'در حال ثبت...' : 'ثبت گزارش و یادآور' }} {{ saving ? 'در حال ثبت...' : 'ثبت گزارش و یادآور' }}
</button> </button>
</footer> </template>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.meeting-page { .meeting-report-form {
background: var(--color-bg);
}
.meeting-scroll {
padding: 8px var(--page-padding) 100px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
} }
.form-error {
margin: 0;
color: var(--color-danger);
font-size: 0.875rem;
}
.customer-card { .customer-card {
display: flex; display: flex;
gap: 12px; gap: 12px;
@@ -273,23 +260,6 @@ h3 {
gap: 8px; gap: 8px;
} }
.submit-icon-btn {
width: 40px;
height: 40px;
border: none;
background: transparent;
color: var(--color-primary);
cursor: pointer;
display: grid;
place-items: center;
padding: 0;
}
.submit-icon-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.type-btn { .type-btn {
border: 1.5px solid var(--color-border); border: 1.5px solid var(--color-border);
background: var(--color-surface); background: var(--color-surface);
@@ -319,6 +289,62 @@ h3 {
gap: 8px; gap: 8px;
} }
.two-cols--date {
align-items: end;
}
.meeting-report-form :deep(.meeting-time.form-group),
.meeting-report-form :deep(.meeting-date.form-group) {
margin-bottom: 0;
}
.meeting-report-form :deep(.meeting-time .form-label),
.meeting-report-form :deep(.meeting-date .form-label) {
font-size: 0.85rem;
font-weight: 400;
margin-bottom: 6px;
}
.meeting-report-form :deep(.meeting-date .persian-date-input__trigger) {
border-radius: 12px;
padding-inline: 10px;
font-size: 0.8125rem;
gap: 6px;
}
.meeting-report-form :deep(.meeting-date .persian-date-input__trigger > span:first-child) {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.meeting-report-form :deep(.meeting-date .persian-date-input__icon) {
flex-shrink: 0;
}
.meeting-report-form :deep(.meeting-date .persian-date-input__icon svg) {
width: 16px;
height: 16px;
}
.meeting-report-form :deep(.meeting-time .time-input__trigger) {
border-radius: 12px;
padding-inline: 10px;
font-size: 0.8125rem;
gap: 6px;
}
.meeting-report-form :deep(.meeting-time .time-input__icon svg) {
width: 16px;
height: 16px;
}
.reminder-box :deep(.meeting-time .time-input__panel) {
top: auto;
bottom: calc(100% + 6px);
}
.field { .field {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -330,7 +356,6 @@ h3 {
.field textarea { .field textarea {
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: 12px; border-radius: 12px;
background: #f5f5f5;
padding: 12px; padding: 12px;
font: inherit; font: inherit;
resize: vertical; resize: vertical;
@@ -397,7 +422,7 @@ h3 {
width: 22px; width: 22px;
height: 22px; height: 22px;
border-radius: 999px; border-radius: 999px;
background: #fff; background: var(--color-surface);
transition: transform 0.15s; transition: transform 0.15s;
} }
@@ -409,14 +434,6 @@ h3 {
transform: translateX(-18px); transform: translateX(-18px);
} }
.footer {
position: sticky;
bottom: 0;
padding: 12px var(--page-padding) calc(12px + env(safe-area-inset-bottom, 0));
background: var(--color-surface);
border-top: 1px solid var(--color-border);
}
.submit { .submit {
width: 100%; width: 100%;
min-height: 52px; min-height: 52px;
@@ -427,4 +444,9 @@ h3 {
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
} }
.submit:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style> </style>
@@ -0,0 +1,189 @@
<script setup lang="ts">
import { onBeforeUnmount, watch } from 'vue'
import MeetingReportForm from '@/components/customers/MeetingReportForm.vue'
const props = defineProps<{
open: boolean
customerId: string
}>()
const emit = defineEmits<{
close: []
success: []
}>()
function close() {
emit('close')
}
function onSuccess() {
emit('success')
}
function onKeydown(event: KeyboardEvent) {
if (event.key === 'Escape' && props.open) close()
}
watch(
() => props.open,
(isOpen) => {
document.body.style.overflow = isOpen ? 'hidden' : ''
if (isOpen) {
window.addEventListener('keydown', onKeydown)
} else {
window.removeEventListener('keydown', onKeydown)
}
},
{ immediate: true },
)
onBeforeUnmount(() => {
document.body.style.overflow = ''
window.removeEventListener('keydown', onKeydown)
})
</script>
<template>
<Teleport to="body">
<Transition name="meeting-report-sheet">
<div
v-if="open"
class="meeting-report-overlay"
role="dialog"
aria-modal="true"
aria-labelledby="meeting-report-title"
@click.self="close"
>
<div class="meeting-report-sheet">
<div class="meeting-report-sheet__handle" aria-hidden="true" />
<header class="meeting-report-sheet__head">
<h2 id="meeting-report-title">ثبت گزارش ملاقات</h2>
<button
type="button"
class="meeting-report-sheet__close"
aria-label="بستن"
@click="close"
>
×
</button>
</header>
<MeetingReportForm
v-if="customerId"
:key="customerId"
:customer-id="customerId"
@success="onSuccess"
/>
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.meeting-report-overlay {
position: fixed;
inset: 0;
z-index: 200;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: flex-end;
justify-content: center;
padding: 0;
}
.meeting-report-sheet {
width: 100%;
max-width: 560px;
max-height: min(94dvh, 760px);
overflow-y: auto;
background: var(--color-bg);
color: var(--color-text);
border-radius: 20px 20px 0 0;
padding: 10px 16px calc(16px + env(safe-area-inset-bottom, 0px));
box-shadow: var(--shadow-md);
display: flex;
flex-direction: column;
gap: 16px;
}
.meeting-report-sheet__handle {
width: 40px;
height: 4px;
border-radius: 999px;
background: var(--color-border);
margin: 4px auto 0;
}
.meeting-report-sheet__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.meeting-report-sheet__head h2 {
margin: 0;
font-size: 1.0625rem;
font-weight: 700;
}
.meeting-report-sheet__close {
width: 36px;
height: 36px;
border: none;
border-radius: 999px;
background: var(--color-surface);
color: var(--color-text-muted);
font-size: 1.5rem;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
padding: 0;
}
.meeting-report-sheet-enter-active,
.meeting-report-sheet-leave-active {
transition: opacity 0.2s ease;
}
.meeting-report-sheet-enter-active .meeting-report-sheet,
.meeting-report-sheet-leave-active .meeting-report-sheet {
transition: transform 0.22s ease;
}
.meeting-report-sheet-enter-from,
.meeting-report-sheet-leave-to {
opacity: 0;
}
.meeting-report-sheet-enter-from .meeting-report-sheet,
.meeting-report-sheet-leave-to .meeting-report-sheet {
transform: translateY(100%);
}
@media (min-width: 768px) {
.meeting-report-overlay {
align-items: center;
padding: 24px;
}
.meeting-report-sheet {
border-radius: 16px;
max-width: 520px;
max-height: min(88dvh, 720px);
padding: 20px 20px 24px;
background: var(--color-surface);
}
.meeting-report-sheet__handle {
display: none;
}
.meeting-report-sheet-enter-from .meeting-report-sheet,
.meeting-report-sheet-leave-to .meeting-report-sheet {
transform: translateY(16px) scale(0.98);
}
}
</style>
+35
View File
@@ -0,0 +1,35 @@
<script setup lang="ts">
withDefaults(
defineProps<{
size?: number
color?: string
strokeWidth?: number
}>(),
{ size: 24, color: 'currentColor', strokeWidth: 1.5 },
)
</script>
<template>
<svg
:width="size"
:height="size"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M8.25 5.25 12 7.75l3.75-2.5 2.25 2.75L16 9.5V18.5a1 1 0 0 1-1 1H9a1 1 0 0 1-1-1V9.5L6 8l2.25-2.75Z"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 7.75V18.5"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
/>
</svg>
</template>
+48
View File
@@ -0,0 +1,48 @@
<script setup lang="ts">
withDefaults(
defineProps<{
size?: number
color?: string
strokeWidth?: number
}>(),
{ size: 24, color: 'currentColor', strokeWidth: 1.5 },
)
</script>
<template>
<svg
:width="size"
:height="size"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M7.5 5.5h9l2 3v10.5a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1V8.5l2-3Z"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 5.5v13"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
/>
<path
d="M7.5 9 12 11.75 16.5 9"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M10.25 5.5V4a1.75 1.75 0 0 1 3.5 0v1.5"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
/>
</svg>
</template>
+41
View File
@@ -0,0 +1,41 @@
<script setup lang="ts">
withDefaults(
defineProps<{
size?: number
color?: string
strokeWidth?: number
}>(),
{ size: 24, color: 'currentColor', strokeWidth: 1.5 },
)
</script>
<template>
<svg
:width="size"
:height="size"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
d="M8 4.5h8v4.25l-1.75 11H13l-1-9.5-1 9.5H7.75L6 8.75V4.5Z"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M12 8.75v11"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
/>
<path
d="M8 7.25h8"
:stroke="color"
:stroke-width="strokeWidth"
stroke-linecap="round"
/>
</svg>
</template>
-1
View File
@@ -73,7 +73,6 @@ const resolvedAlign = computed(() => props.align ?? (isNumericField.value ? 'end
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
font-size: 0.9375rem; font-size: 0.9375rem;
background: var(--color-surface);
color: var(--color-text); color: var(--color-text);
transition: border-color 0.15s; transition: border-color 0.15s;
} }
-1
View File
@@ -61,7 +61,6 @@ function onInput(event: Event) {
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
font-size: 0.9375rem; font-size: 0.9375rem;
background: var(--color-surface);
color: var(--color-text); color: var(--color-text);
transition: border-color 0.15s; transition: border-color 0.15s;
text-align: right; text-align: right;
+158 -11
View File
@@ -15,24 +15,51 @@ withDefaults(
</script> </script>
<template> <template>
<div class="page-skeleton" role="status" aria-live="polite" aria-busy="true" aria-label="در حال بارگذاری"> <div
class="page-skeleton"
:class="{ 'page-skeleton--home': variant === 'home' }"
role="status"
aria-live="polite"
aria-busy="true"
aria-label="در حال بارگذاری"
>
<!-- Home --> <!-- Home -->
<template v-if="variant === 'home'"> <template v-if="variant === 'home'">
<div class="page-skeleton__banner"> <div class="page-skeleton__hero">
<SkeletonBlock height="148px" radius="16px" /> <SkeletonBlock class="page-skeleton__hero-block" height="148px" radius="16px" />
</div> </div>
<div class="page-skeleton__quick"> <div class="page-skeleton__quick">
<div v-for="n in 4" :key="`q-${n}`" class="page-skeleton__quick-item"> <div v-for="n in 4" :key="`q-${n}`" class="page-skeleton__quick-item">
<SkeletonBlock circle width="48px" height="48px" /> <SkeletonBlock circle width="48px" height="48px" />
<SkeletonBlock width="64%" height="10px" /> <SkeletonBlock width="72%" height="10px" />
</div> </div>
</div> </div>
<SkeletonBlock width="40%" height="16px" />
<div class="page-skeleton__grid"> <div class="page-skeleton__level">
<div v-for="n in 4" :key="`g-${n}`" class="page-skeleton__card"> <div class="page-skeleton__level-top">
<SkeletonBlock height="110px" radius="12px" /> <SkeletonBlock circle width="48px" height="48px" />
<SkeletonBlock width="80%" height="12px" /> <div class="page-skeleton__level-text">
<SkeletonBlock width="50%" height="10px" /> <SkeletonBlock width="56px" height="10px" />
<SkeletonBlock width="88px" height="16px" />
<SkeletonBlock width="104px" height="10px" />
</div>
</div>
<SkeletonBlock height="8px" radius="999px" />
<SkeletonBlock width="62%" height="10px" />
</div>
<div class="page-skeleton__products">
<div class="page-skeleton__section-head">
<SkeletonBlock width="132px" height="16px" />
<SkeletonBlock width="72px" height="12px" />
</div>
<div class="page-skeleton__grid page-skeleton__product-grid">
<div v-for="n in 6" :key="`g-${n}`" class="page-skeleton__card">
<SkeletonBlock class="page-skeleton__product-image" height="110px" radius="12px" />
<SkeletonBlock width="82%" height="12px" />
<SkeletonBlock width="54%" height="10px" />
</div>
</div> </div>
</div> </div>
</template> </template>
@@ -126,7 +153,7 @@ withDefaults(
width: 100%; width: 100%;
} }
.page-skeleton__banner, .page-skeleton__hero,
.page-skeleton__panel, .page-skeleton__panel,
.page-skeleton__detail-body, .page-skeleton__detail-body,
.page-skeleton__form { .page-skeleton__form {
@@ -135,6 +162,52 @@ withDefaults(
gap: 12px; gap: 12px;
} }
.page-skeleton__hero {
width: 100%;
}
.page-skeleton__hero-block {
width: 100%;
}
.page-skeleton__level {
display: flex;
flex-direction: column;
gap: 12px;
padding: 16px;
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
background: transparent;
}
.page-skeleton__level-top {
display: flex;
align-items: center;
gap: 12px;
}
.page-skeleton__level-text {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.page-skeleton__products {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0;
}
.page-skeleton__section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.page-skeleton__quick { .page-skeleton__quick {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
@@ -228,8 +301,82 @@ withDefaults(
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.page-skeleton {
padding: 8px 0 28px;
}
.page-skeleton--home {
display: grid;
grid-column: 1 / -1;
grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-areas:
'hero hero level'
'quick quick quick'
'products products products';
gap: 18px 20px;
width: 100%;
min-width: 0;
}
.page-skeleton__hero {
grid-area: hero;
aspect-ratio: 2.1 / 1;
min-height: 0;
}
.page-skeleton__hero-block {
height: 100% !important;
border-radius: 20px !important;
}
.page-skeleton__quick {
grid-area: quick;
gap: 14px;
}
.page-skeleton__quick-item {
min-height: 96px;
justify-content: center;
padding: 12px 8px;
border: 1px solid var(--color-border);
border-radius: 16px;
}
.page-skeleton__level {
grid-area: level;
align-self: stretch;
justify-content: center;
padding: 20px 22px;
}
.page-skeleton__products {
grid-area: products;
}
.page-skeleton__grid { .page-skeleton__grid {
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
}
.page-skeleton__product-image {
height: 160px !important;
}
.page-skeleton__profile-hero {
align-items: flex-start;
padding: 0 0 8px;
}
.page-skeleton__panel {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.page-skeleton__list {
gap: 12px;
}
.page-skeleton__row--card {
padding: 14px 16px;
} }
} }
</style> </style>
-1
View File
@@ -190,7 +190,6 @@ onBeforeUnmount(() => {
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
font-size: 0.9375rem; font-size: 0.9375rem;
background: var(--color-surface);
color: var(--color-text); color: var(--color-text);
transition: border-color 0.15s; transition: border-color 0.15s;
} }
-1
View File
@@ -28,7 +28,6 @@ defineEmits<{
padding: 0 16px; padding: 0 16px;
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
background: var(--color-surface);
color: var(--color-text); color: var(--color-text);
font-family: inherit; font-family: inherit;
font-size: 0.9375rem; font-size: 0.9375rem;
+371
View File
@@ -0,0 +1,371 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { toPersianDigits } from '@/utils/format'
import IconProgressClock from '@/components/icons/IconProgressClock.vue'
const props = defineProps<{
modelValue?: string
label?: string
placeholder?: string
hint?: string
error?: string
disabled?: boolean
}>()
const emit = defineEmits<{
'update:modelValue': [value: string]
}>()
const rootRef = ref<HTMLElement | null>(null)
const isOpen = ref(false)
const draftHour = ref(10)
const draftMinute = ref(0)
const minuteSteps = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
const parsed = computed(() => parseTime(props.modelValue ?? ''))
const displayValue = computed(() => {
if (!parsed.value) return ''
return formatTimeLabel(parsed.value.hour, parsed.value.minute)
})
watch(
() => props.modelValue,
(value) => {
const next = parseTime(value ?? '')
if (next) {
draftHour.value = next.hour
draftMinute.value = snapMinute(next.minute)
}
},
{ immediate: true },
)
function snapMinute(minute: number) {
const snapped = Math.round(minute / 5) * 5
return snapped === 60 ? 55 : snapped
}
function parseTime(value: string) {
const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim())
if (!match) return null
const hour = Number(match[1])
const minute = Number(match[2])
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) return null
return { hour, minute }
}
function formatTimeLabel(hour: number, minute: number) {
return toPersianDigits(
`${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`,
)
}
function padTime(hour: number, minute: number) {
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`
}
function togglePanel() {
if (props.disabled) return
if (!isOpen.value) {
if (parsed.value) {
draftHour.value = parsed.value.hour
draftMinute.value = snapMinute(parsed.value.minute)
} else {
const now = new Date()
draftHour.value = now.getHours()
draftMinute.value = snapMinute(now.getMinutes())
}
}
isOpen.value = !isOpen.value
}
function closePanel() {
isOpen.value = false
}
function stepHour(delta: number) {
draftHour.value = (draftHour.value + delta + 24) % 24
}
function stepMinute(delta: number) {
const index = minuteSteps.indexOf(draftMinute.value)
const base = index < 0 ? 0 : index
const next = (base + delta + minuteSteps.length) % minuteSteps.length
draftMinute.value = minuteSteps[next]
}
function confirmSelection() {
emit('update:modelValue', padTime(draftHour.value, draftMinute.value))
closePanel()
}
function selectNow() {
const now = new Date()
const hour = now.getHours()
const minute = snapMinute(now.getMinutes())
draftHour.value = hour
draftMinute.value = minute
emit('update:modelValue', padTime(hour, minute))
closePanel()
}
function onDocumentClick(event: MouseEvent) {
if (!rootRef.value?.contains(event.target as Node)) {
closePanel()
}
}
onMounted(() => {
document.addEventListener('click', onDocumentClick)
})
onBeforeUnmount(() => {
document.removeEventListener('click', onDocumentClick)
})
</script>
<template>
<div ref="rootRef" class="form-group time-input">
<label v-if="label" class="form-label">{{ label }}</label>
<button
type="button"
class="input time-input__trigger"
:class="{ 'input-error': error, 'is-open': isOpen }"
:disabled="disabled"
@click.stop="togglePanel"
>
<span :class="{ 'time-input__placeholder': !displayValue }">
{{ displayValue || placeholder || 'انتخاب ساعت' }}
</span>
<span class="time-input__icon" aria-hidden="true">
<IconProgressClock :size="18" />
</span>
</button>
<div v-if="isOpen" class="time-input__panel" @click.stop>
<div class="time-input__picker" role="group" aria-label="انتخاب ساعت">
<div class="time-input__spinner">
<span class="time-input__spinner-label">ساعت</span>
<button type="button" class="time-input__step" aria-label="یک ساعت بعد" @click="stepHour(1)">
<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 14l6-6 6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
<strong class="time-input__value">{{ toPersianDigits(String(draftHour).padStart(2, '0')) }}</strong>
<button type="button" class="time-input__step" aria-label="یک ساعت قبل" @click="stepHour(-1)">
<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 10l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
</div>
<span class="time-input__colon" aria-hidden="true">:</span>
<div class="time-input__spinner">
<span class="time-input__spinner-label">دقیقه</span>
<button type="button" class="time-input__step" aria-label="پنج دقیقه بعد" @click="stepMinute(1)">
<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 14l6-6 6 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
<strong class="time-input__value">
{{ toPersianDigits(String(draftMinute).padStart(2, '0')) }}
</strong>
<button type="button" class="time-input__step" aria-label="پنج دقیقه قبل" @click="stepMinute(-1)">
<svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 10l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
</svg>
</button>
</div>
</div>
<div class="time-input__footer">
<button type="button" class="time-input__now" @click="selectNow">اکنون</button>
<button type="button" class="time-input__confirm" @click="confirmSelection">تأیید</button>
</div>
</div>
<p v-if="error" class="input-error-text">{{ error }}</p>
<p v-else-if="hint" class="form-hint">{{ hint }}</p>
</div>
</template>
<style scoped>
.time-input {
position: relative;
}
.input {
width: 100%;
height: var(--btn-height);
padding: 0 14px;
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
font-size: 0.9375rem;
color: var(--color-text);
transition: border-color 0.15s;
}
.time-input__trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
cursor: pointer;
text-align: right;
}
.time-input__trigger:focus {
outline: none;
border-color: var(--color-primary);
}
.time-input__trigger.is-open,
.time-input__trigger:focus-visible {
border-color: var(--color-primary);
}
.time-input__trigger:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.time-input__placeholder {
color: var(--color-text-muted);
}
.time-input__trigger > span:first-child {
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.time-input__icon {
display: flex;
align-items: center;
flex-shrink: 0;
color: var(--color-text-muted);
}
.input-error {
border-color: var(--color-danger);
}
.input-error-text {
color: var(--color-danger);
font-size: 0.8125rem;
margin: 4px 0 0;
}
.time-input__panel {
position: absolute;
top: calc(100% + 4px);
right: 0;
left: 0;
z-index: 320;
padding: 8px;
border: 1px solid var(--color-border);
border-radius: 12px;
background: var(--color-surface);
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
}
.time-input__picker {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 6px;
align-items: center;
}
.time-input__spinner {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 4px 6px 6px;
border: 1px solid var(--color-border);
border-radius: 10px;
background: transparent;
}
.time-input__spinner-label {
font-size: 0.6875rem;
color: var(--color-text-muted);
line-height: 1.2;
}
.time-input__value {
display: flex;
align-items: center;
justify-content: center;
min-height: 24px;
font-size: 0.9375rem;
font-weight: 700;
letter-spacing: 0.04em;
color: var(--color-text);
}
.time-input__step {
width: 28px;
height: 24px;
border: none;
border-radius: 6px;
background: transparent;
color: var(--color-text-muted);
display: grid;
place-items: center;
padding: 0;
cursor: pointer;
}
.time-input__step:hover {
background: var(--color-primary-soft);
color: var(--color-primary);
}
.time-input__colon {
font-size: 0.9375rem;
font-weight: 700;
color: var(--color-text-muted);
line-height: 1;
padding-top: 12px;
}
.time-input__footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 8px;
padding-top: 8px;
border-top: 1px solid var(--color-border);
}
.time-input__now {
border: none;
background: transparent;
color: var(--color-primary);
font: inherit;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
padding: 4px 0;
}
.time-input__confirm {
min-height: 32px;
padding: 0 14px;
border: none;
border-radius: 8px;
background: var(--color-primary);
color: #fff;
font: inherit;
font-size: 0.75rem;
font-weight: 700;
cursor: pointer;
}
</style>
+12 -6
View File
@@ -96,9 +96,10 @@ const router = createRouter({
}, },
{ {
path: '/customers/new', path: '/customers/new',
name: 'customer-new', redirect: (to) => ({
component: () => import('@/views/customers/CustomerCreateView.vue'), path: '/customers',
meta: { requiresAuth: true, hideNav: true }, query: { ...to.query, create: '1' },
}),
}, },
{ {
path: '/customer', path: '/customer',
@@ -108,9 +109,14 @@ const router = createRouter({
}, },
{ {
path: '/meeting-report', path: '/meeting-report',
name: 'meeting-report', redirect: (to) => ({
component: () => import('@/views/customers/MeetingReportView.vue'), path: '/customer',
meta: { requiresAuth: true, hideNav: true }, query: {
id: String(to.query.customerId ?? ''),
tab: 'reports',
meetingReport: '1',
},
}),
}, },
{ {
path: '/reminders', path: '/reminders',
+19
View File
@@ -8,6 +8,9 @@ import IconIdCard from '@/components/icons/IconIdCard.vue'
import IconCatalog from '@/components/icons/IconCatalog.vue' import IconCatalog from '@/components/icons/IconCatalog.vue'
import IconHeadset from '@/components/icons/IconHeadset.vue' import IconHeadset from '@/components/icons/IconHeadset.vue'
import IconHome from '@/components/icons/IconHome.vue' import IconHome from '@/components/icons/IconHome.vue'
import IconShirt from '@/components/icons/IconShirt.vue'
import IconSuit from '@/components/icons/IconSuit.vue'
import IconTrousers from '@/components/icons/IconTrousers.vue'
export const CATEGORY_ICON_MAP: Record<string, Component> = { export const CATEGORY_ICON_MAP: Record<string, Component> = {
bolt: IconBolt, bolt: IconBolt,
@@ -19,6 +22,22 @@ export const CATEGORY_ICON_MAP: Record<string, Component> = {
catalog: IconCatalog, catalog: IconCatalog,
headset: IconHeadset, headset: IconHeadset,
home: IconHome, home: IconHome,
shirt: IconShirt,
suit: IconSuit,
trousers: IconTrousers,
}
const USAGE_ICON_BY_ID: Record<string, string> = {
shirt: 'shirt',
suit: 'suit',
trousers: 'trousers',
}
export function usageCategoryIconKey(item: { id: string; icon?: string }) {
// آیکون‌های ذخیره‌شده در ادمین (grid/bag/id-card) را برای دسته‌های استاندارد نادیده بگیر
if (USAGE_ICON_BY_ID[item.id]) return USAGE_ICON_BY_ID[item.id]
if (item.icon && CATEGORY_ICON_MAP[item.icon]) return item.icon
return item.icon ?? 'grid'
} }
export function categoryIconOf(icon?: string, fallback: Component = IconDiamond) { export function categoryIconOf(icon?: string, fallback: Component = IconDiamond) {
+1 -2
View File
@@ -90,7 +90,7 @@ onMounted(async () => {
<template> <template>
<div class="page login-page"> <div class="page login-page">
<img class="login-page__bg" :src="fabricUrl" alt="" /> <img class="login-page__bg" :src="fabricUrl" alt="" />
<img class="login-page__fabric" :src="fabricUrl2" alt="" /> <!-- <img class="login-page__fabric" :src="fabricUrl2" alt="" />-->
<div class="login-page__scroll"> <div class="login-page__scroll">
<div class="login-page__stage"> <div class="login-page__stage">
@@ -346,7 +346,6 @@ onMounted(async () => {
padding: 0 8px 0 14px; padding: 0 8px 0 14px;
border: 1px solid #e4e0db; border: 1px solid #e4e0db;
border-radius: 14px; border-radius: 14px;
background: #e4ddd4;
} }
.phone-field.is-invalid { .phone-field.is-invalid {
-2
View File
@@ -456,7 +456,6 @@ onMounted(() => {
padding: 0; padding: 0;
border: 1px solid #e4e0db; border: 1px solid #e4e0db;
border-radius: 16px; border-radius: 16px;
background: #f7f5f1;
color: #1c1b19; color: #1c1b19;
font-family: inherit; font-family: inherit;
font-size: 1.25rem; font-size: 1.25rem;
@@ -469,7 +468,6 @@ onMounted(() => {
.otp-box:focus { .otp-box:focus {
outline: none; outline: none;
border-color: var(--color-primary); border-color: var(--color-primary);
background: #fff;
} }
.otp-box--error { .otp-box--error {
-1
View File
@@ -385,7 +385,6 @@ async function submit() {
padding: 0 14px; padding: 0 14px;
border: 1px solid #e0d9d0; border: 1px solid #e0d9d0;
border-radius: 14px; border-radius: 14px;
background: #ebe4dc;
} }
.reg-field.is-invalid, .reg-field.is-invalid,
+8 -7
View File
@@ -400,8 +400,8 @@ async function addAllToInquiry() {
<style scoped> <style scoped>
.calite-detail { .calite-detail {
background: #f4f0ed; background: var(--color-bg);
color: #2a2622; color: var(--color-text);
} }
.calite-detail__nav, .calite-detail__nav,
@@ -563,7 +563,7 @@ async function addAllToInquiry() {
top: 16%; top: 16%;
bottom: 16%; bottom: 16%;
width: 1px; width: 1px;
background: #e4dfd8; background: var(--color-border);
} }
.attr__icon { .attr__icon {
@@ -617,7 +617,7 @@ async function addAllToInquiry() {
padding: 0 14px; padding: 0 14px;
border: none; border: none;
border-radius: 999px; border-radius: 999px;
background: #ece8e2; background: var(--color-tab-inactive-bg);
color: #6a645e; color: #6a645e;
font: inherit; font: inherit;
font-size: 0.8125rem; font-size: 0.8125rem;
@@ -644,11 +644,12 @@ async function addAllToInquiry() {
overflow: hidden; overflow: hidden;
border: none; border: none;
border-radius: 20px; border-radius: 20px;
background: #fff; background: var(--color-surface);
text-align: start; text-align: start;
cursor: pointer; cursor: pointer;
color: #1c1c1c; color: #1c1c1c;
box-shadow: 0 1px 3px rgba(40, 32, 24, 0.05); box-shadow: none;
border: 1px solid var(--color-border);
} }
.pattern-card__photo { .pattern-card__photo {
@@ -903,7 +904,7 @@ html.dark .calite-detail__footer.sticky-footer {
width: 100%; width: 100%;
max-width: none; max-width: none;
padding: 12px 28px 24px; padding: 12px 28px 24px;
background: #f4f0ed; background: var(--color-bg);
} }
.inquiry-bar { .inquiry-bar {
@@ -175,7 +175,6 @@ async function submit() {
border-radius: 10px; border-radius: 10px;
text-align: center; text-align: center;
font: inherit; font: inherit;
background: var(--color-bg);
color: var(--color-text); color: var(--color-text);
} }
@@ -193,7 +192,6 @@ async function submit() {
padding: 10px 12px; padding: 10px 12px;
font: inherit; font: inherit;
resize: vertical; resize: vertical;
background: var(--color-surface);
color: var(--color-text); color: var(--color-text);
} }
+50 -30
View File
@@ -10,7 +10,7 @@ import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
import IconHeadset from '@/components/icons/IconHeadset.vue' import IconHeadset from '@/components/icons/IconHeadset.vue'
import IconDiamond from '@/components/icons/IconDiamond.vue' import IconDiamond from '@/components/icons/IconDiamond.vue'
import IconCatalog from '@/components/icons/IconCatalog.vue' import IconCatalog from '@/components/icons/IconCatalog.vue'
import { categoryIconOf } from '@/utils/categoryIcons' import { categoryIconOf, usageCategoryIconKey } from '@/utils/categoryIcons'
import { useGoBack } from '@/composables/useGoBack' import { useGoBack } from '@/composables/useGoBack'
import AuthThemeToggle from '@/components/ui/AuthThemeToggle.vue' import AuthThemeToggle from '@/components/ui/AuthThemeToggle.vue'
import PageSkeleton from '@/components/ui/PageSkeleton.vue' import PageSkeleton from '@/components/ui/PageSkeleton.vue'
@@ -23,10 +23,6 @@ const seasonCategories = ref<SeasonCategory[]>([])
const loading = ref(true) const loading = ref(true)
const error = ref<string | null>(null) const error = ref<string | null>(null)
function usageIconKey(item: ProductCategory) {
return item.icon || (item.id === 'shirt' ? 'id-card' : item.id === 'suit' ? 'bag' : 'grid')
}
function seasonIconKey(item: SeasonCategory) { function seasonIconKey(item: SeasonCategory) {
return item.icon || (item.id === 'summer' ? 'bolt' : item.id === 'winter' ? 'feather' : 'diamond') return item.icon || (item.id === 'summer' ? 'bolt' : item.id === 'winter' ? 'feather' : 'diamond')
} }
@@ -91,7 +87,7 @@ function openSeason(id: string) {
:style="item.imageUrl ? { backgroundImage: `url(${item.imageUrl})` } : undefined" :style="item.imageUrl ? { backgroundImage: `url(${item.imageUrl})` } : undefined"
> >
<span v-if="item.showIcon !== false" class="usage-card__icon"> <span v-if="item.showIcon !== false" class="usage-card__icon">
<component :is="categoryIconOf(usageIconKey(item), IconCatalog)" :size="18" /> <component :is="categoryIconOf(usageCategoryIconKey(item), IconCatalog)" :size="18" />
</span> </span>
</div> </div>
<div class="usage-card__body"> <div class="usage-card__body">
@@ -114,7 +110,7 @@ function openSeason(id: string) {
:key="item.id" :key="item.id"
type="button" type="button"
class="season-card" class="season-card"
:class="`season-card--${item.id}`" :class="[`season-card--${item.id}`, { 'season-card--banner': !!item.imageUrl }]"
:style="item.imageUrl ? { backgroundImage: `url(${item.imageUrl})` } : undefined" :style="item.imageUrl ? { backgroundImage: `url(${item.imageUrl})` } : undefined"
@click="openSeason(item.id)" @click="openSeason(item.id)"
> >
@@ -260,7 +256,8 @@ function openSeason(id: string) {
margin: 0; margin: 0;
line-height: normal; line-height: normal;
font-family: inherit; font-family: inherit;
box-shadow: var(--shadow-sm); box-shadow: none;
border: 1px solid var(--color-border);
} }
.usage-card__image { .usage-card__image {
@@ -297,11 +294,12 @@ function openSeason(id: string) {
width: 28px; width: 28px;
height: 28px; height: 28px;
border-radius: 999px; border-radius: 999px;
background: var(--color-surface); background: var(--color-primary-soft);
color: var(--color-primary); color: var(--color-primary);
display: grid; display: grid;
place-items: center; place-items: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); box-shadow: none;
border: 1px solid var(--color-border);
} }
.usage-card__body { .usage-card__body {
@@ -335,9 +333,9 @@ function openSeason(id: string) {
position: relative; position: relative;
isolation: isolate; isolation: isolate;
display: flex; display: flex;
align-items: center; align-items: flex-end;
justify-content: flex-start; justify-content: flex-start;
min-height: 124px; min-height: 148px;
width: 100%; width: 100%;
appearance: none; appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
@@ -346,17 +344,28 @@ function openSeason(id: string) {
overflow: hidden; overflow: hidden;
text-align: start; text-align: start;
cursor: pointer; cursor: pointer;
padding: 16px 18px; padding: 14px 16px;
margin: 0; margin: 0;
font-family: inherit; font-family: inherit;
box-shadow: var(--shadow-sm); box-shadow: none;
border: 1px solid var(--color-border);
background-color: #d6c3a8; background-color: #d6c3a8;
background-image: linear-gradient(160deg, #d6c3a8, #8b7355); background-image: linear-gradient(160deg, #d6c3a8, #8b7355);
background-size: cover; background-size: cover;
background-position: center left; background-position: center top;
background-repeat: no-repeat; background-repeat: no-repeat;
} }
.season-card--banner {
align-items: stretch;
justify-content: flex-start;
padding: 0;
min-height: 156px;
aspect-ratio: 2.35 / 1;
background-size: cover;
background-position: center top;
}
.season-card--summer { .season-card--summer {
background-image: linear-gradient(160deg, #f3e6c8, #c4a574); background-image: linear-gradient(160deg, #f3e6c8, #c4a574);
} }
@@ -369,25 +378,23 @@ function openSeason(id: string) {
background-image: linear-gradient(160deg, #efe6d6, #b9a48a); background-image: linear-gradient(160deg, #efe6d6, #b9a48a);
} }
.season-card::after {
content: '';
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background: linear-gradient(
to left,
color-mix(in srgb, var(--color-surface) 90%, transparent) 0%,
color-mix(in srgb, var(--color-surface) 72%, transparent) 38%,
color-mix(in srgb, var(--color-surface) 22%, transparent) 68%,
transparent 100%
);
}
.season-card__content { .season-card__content {
position: relative; position: relative;
z-index: 1; z-index: 1;
max-width: 58%; max-width: 58%;
padding: 14px 16px;
}
.season-card--banner .season-card__content {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-self: stretch;
max-width: none;
width: 100%;
min-height: 100%;
margin-top: auto;
padding: 14px 16px;
} }
.season-card__content strong { .season-card__content strong {
@@ -415,6 +422,19 @@ function openSeason(id: string) {
flex-shrink: 0; flex-shrink: 0;
} }
.season-card--banner .season-card__content strong,
.season-card--banner .season-card__content p,
.season-card--banner .season-card__link {
color: #fff;
text-shadow:
0 1px 2px rgba(0, 0, 0, 0.55),
0 2px 12px rgba(0, 0, 0, 0.35);
}
.season-card--banner .season-card__icon {
background: rgba(255, 255, 255, 0.92);
}
.season-card__content p { .season-card__content p {
margin: 0 0 10px; margin: 0 0 10px;
color: var(--color-text-muted); color: var(--color-text-muted);
@@ -398,7 +398,7 @@ const currentStep = 1
} }
.company-showcase--par .company-showcase__cta { .company-showcase--par .company-showcase__cta {
background: #fff; background: var(--color-surface);
color: var(--color-primary); color: var(--color-primary);
} }
-169
View File
@@ -1,169 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { useCrmStore } from '@/stores/crm'
import { useGoBack } from '@/composables/useGoBack'
import PageHeader from '@/components/layout/PageHeader.vue'
import BaseInput from '@/components/ui/BaseInput.vue'
import FormErrorBanner from '@/components/ui/FormErrorBanner.vue'
import BaseButton from '@/components/ui/BaseButton.vue'
import { normalizePhone } from '@/utils/format'
import { validatePhone } from '@/utils/validation'
import {
FORM_VALIDATION_SUMMARY,
clearFieldError,
hasFieldErrors,
type FieldErrors,
} from '@/utils/formErrors'
import { ApiError } from '@/services/http'
const router = useRouter()
const auth = useAuthStore()
const crm = useCrmStore()
const { goBack } = useGoBack('/customers')
const firstName = ref('')
const lastName = ref('')
const phone = ref('')
const businessName = ref('')
const fieldErrors = ref<FieldErrors>({})
const formError = ref('')
const saving = ref(false)
function clearField(key: string) {
fieldErrors.value = clearFieldError(fieldErrors.value, key)
if (!hasFieldErrors(fieldErrors.value)) formError.value = ''
}
async function submit() {
fieldErrors.value = {}
formError.value = ''
if (!auth.isAgent) {
formError.value = 'فقط واسطه می‌تواند مشتری به لیست خود اضافه کند.'
return
}
const normalized = normalizePhone(phone.value)
const errors: FieldErrors = {}
if (!firstName.value.trim()) errors.firstName = 'نام الزامی است.'
if (!lastName.value.trim()) errors.lastName = 'نام خانوادگی الزامی است.'
if (!validatePhone(normalized)) errors.phone = 'شماره موبایل معتبر نیست.'
if (hasFieldErrors(errors)) {
fieldErrors.value = errors
formError.value = FORM_VALIDATION_SUMMARY
return
}
saving.value = true
try {
const created = (await crm.registerCustomer({
firstName: firstName.value.trim(),
lastName: lastName.value.trim(),
phone: normalized,
businessName: businessName.value.trim() || undefined,
})) as { id?: string }
if (created?.id) {
await router.replace({ path: '/customer', query: { id: created.id } })
return
}
await router.replace('/customers')
} catch (e) {
formError.value =
e instanceof ApiError || e instanceof Error
? e.message
: 'ثبت مشتری ناموفق بود.'
} finally {
saving.value = false
}
}
</script>
<template>
<div class="page customer-create-page">
<PageHeader title="افزودن مشتری" show-back centered @back="goBack" />
<div class="page__scroll customer-create-scroll">
<p class="intro">
مشتری به لیست شما اضافه می‌شود و بعد از ورود با همین شماره، سفارش‌هایش به حساب شما وصل می‌ماند.
</p>
<FormErrorBanner :message="formError" />
<div class="form-card">
<BaseInput
v-model="firstName"
label="نام"
placeholder="نام مشتری"
:error="fieldErrors.firstName"
@update:model-value="clearField('firstName')"
/>
<BaseInput
v-model="lastName"
label="نام خانوادگی"
placeholder="نام خانوادگی"
:error="fieldErrors.lastName"
@update:model-value="clearField('lastName')"
/>
<BaseInput
v-model="phone"
label="شماره موبایل"
type="tel"
inputmode="tel"
dir="ltr"
placeholder="۰۹۱۲۳۴۵۶۷۸۹"
:error="fieldErrors.phone"
@update:model-value="clearField('phone')"
/>
<BaseInput
v-model="businessName"
label="نام فروشگاه یا برند (اختیاری)"
placeholder="مثلاً بوتیک رز"
/>
</div>
</div>
<footer class="footer">
<BaseButton block :loading="saving" :disabled="saving" @click="submit">
ثبت مشتری
</BaseButton>
</footer>
</div>
</template>
<style scoped>
.customer-create-page {
background: var(--color-bg);
}
.customer-create-scroll {
padding: 8px var(--page-padding) 100px;
display: flex;
flex-direction: column;
gap: 12px;
}
.intro {
margin: 0;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.7;
}
.form-card {
background: var(--color-surface);
border-radius: var(--radius-md);
padding: 16px;
box-shadow: var(--shadow-sm);
}
.footer {
position: sticky;
bottom: 0;
padding: 12px var(--page-padding) calc(12px + env(safe-area-inset-bottom, 0px));
background: color-mix(in srgb, var(--color-bg) 92%, transparent);
}
</style>
+467 -229
View File
@@ -7,6 +7,7 @@ import type {
CrmCustomer, CrmCustomer,
CrmMeeting, CrmMeeting,
CrmOrderSummary, CrmOrderSummary,
CustomerStatus,
CustomerTab, CustomerTab,
MeetingInteractionType, MeetingInteractionType,
} from '@/types/crm' } from '@/types/crm'
@@ -21,6 +22,7 @@ import IconClipboardEmpty from '@/components/icons/IconClipboardEmpty.vue'
import IconPhone from '@/components/icons/IconPhone.vue' import IconPhone from '@/components/icons/IconPhone.vue'
import IconMail from '@/components/icons/IconMail.vue' import IconMail from '@/components/icons/IconMail.vue'
import IconEditPen from '@/components/icons/IconEditPen.vue' import IconEditPen from '@/components/icons/IconEditPen.vue'
import MeetingReportOverlay from '@/components/customers/MeetingReportOverlay.vue'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -28,6 +30,7 @@ const crm = useCrmStore()
const auth = useAuthStore() const auth = useAuthStore()
const id = computed(() => String(route.query.id ?? '')) const id = computed(() => String(route.query.id ?? ''))
const tab = computed(() => (String(route.query.tab ?? 'info') as CustomerTab)) const tab = computed(() => (String(route.query.tab ?? 'info') as CustomerTab))
const showMeetingReport = computed(() => route.query.meetingReport === '1')
const customer = ref<CrmCustomer | null>(null) const customer = ref<CrmCustomer | null>(null)
const orders = ref<CrmOrderSummary[]>([]) const orders = ref<CrmOrderSummary[]>([])
const meetings = ref<CrmMeeting[]>([]) const meetings = ref<CrmMeeting[]>([])
@@ -69,6 +72,25 @@ function setTab(next: CustomerTab) {
router.replace({ path: '/customer', query: { id: id.value, tab: next } }) router.replace({ path: '/customer', query: { id: id.value, tab: next } })
} }
function openMeetingReport() {
router.push({
path: '/customer',
query: { id: id.value, tab: 'reports', meetingReport: '1' },
})
}
function closeMeetingReport() {
router.replace({
path: '/customer',
query: { id: id.value, tab: 'reports' },
})
}
async function onMeetingReportSuccess() {
closeMeetingReport()
await load()
}
function typeLabel(type: MeetingInteractionType) { function typeLabel(type: MeetingInteractionType) {
if (type === 'in_person') return 'ملاقات حضوری' if (type === 'in_person') return 'ملاقات حضوری'
if (type === 'call') return 'تماس' if (type === 'call') return 'تماس'
@@ -117,6 +139,20 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
if (variant === 'warning' || variant === 'danger') return 'warning' if (variant === 'warning' || variant === 'danger') return 'warning'
return order.statusTone || 'info' return order.statusTone || 'info'
} }
const recentOrders = computed(() => orders.value.slice(0, 3))
function customerStatusLabel(status: CustomerStatus) {
if (status === 'active') return 'فعال'
if (status === 'inactive') return 'غیرفعال'
return 'جدید'
}
function customerStatusClass(status: CustomerStatus) {
if (status === 'active') return 'status-badge--active'
if (status === 'inactive') return 'status-badge--inactive'
return 'status-badge--new'
}
</script> </script>
<template> <template>
@@ -133,22 +169,24 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
</PageHeader> </PageHeader>
<div class="page__scroll page__scroll--with-nav customer-scroll"> <div class="page__scroll page__scroll--with-nav customer-scroll">
<section v-if="tab !== 'reports'" class="profile-row"> <section v-if="tab !== 'reports'" class="profile-card">
<div class="avatar" :style="{ background: customer.avatarColor }"> <div class="profile-card__avatar" :style="{ background: customer.avatarColor }">
{{ customer.avatarLetter }} {{ customer.avatarLetter }}
</div> </div>
<div> <div class="profile-card__info">
<div class="name-row"> <div class="profile-card__name-row">
<h2>{{ customer.name }}</h2> <h2>{{ customer.name }}</h2>
<span class="badge">فعال</span> <span class="status-badge" :class="customerStatusClass(customer.status)">
{{ customerStatusLabel(customer.status) }}
</span>
</div> </div>
<p dir="ltr">{{ customer.phone }}</p> <p class="profile-card__phone" dir="ltr">{{ customer.phone }}</p>
</div> </div>
</section> </section>
<section v-else class="profile-banner"> <section v-else class="profile-banner">
<div class="profile-banner__top"> <div class="profile-banner__top">
<div class="avatar avatar--lg" :style="{ background: customer.avatarColor }"> <div class="profile-card__avatar profile-card__avatar--lg" :style="{ background: customer.avatarColor }">
{{ customer.avatarLetter }} {{ customer.avatarLetter }}
</div> </div>
<div> <div>
@@ -156,7 +194,7 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
<p>{{ customer.contactName }}</p> <p>{{ customer.contactName }}</p>
<p dir="ltr">{{ customer.phone }}</p> <p dir="ltr">{{ customer.phone }}</p>
</div> </div>
<span class="badge badge--light">مشتری فعال</span> <span class="status-badge status-badge--active status-badge--on-dark">فعال</span>
</div> </div>
<div class="profile-banner__stats"> <div class="profile-banner__stats">
<div> <div>
@@ -174,54 +212,85 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
</div> </div>
</section> </section>
<div class="tabs"> <div class="tabs" role="tablist">
<button <button
v-for="item in tabs" v-for="item in tabs"
:key="item.id" :key="item.id"
type="button" type="button"
role="tab"
class="tab" class="tab"
:class="{ 'tab--active': tab === item.id }" :class="{ 'tab--active': tab === item.id }"
:aria-selected="tab === item.id"
@click="setTab(item.id)" @click="setTab(item.id)"
> >
{{ item.label }} <span class="tab__text">{{ item.label }}</span>
</button> </button>
</div> </div>
<template v-if="tab === 'info'"> <template v-if="tab === 'info'">
<section class="stats"> <section class="purchase-card">
<h3>اطلاعات خرید</h3> <h3 class="section-title">اطلاعات خرید</h3>
<div class="stats__grid"> <div class="purchase-stats">
<div> <div class="purchase-stats__item">
<span>تعداد سفارش</span> <span class="purchase-stats__label">تعداد سفارش</span>
<strong>{{ formatNumber(customer.orderCount) }}</strong> <strong class="purchase-stats__value">{{ formatNumber(customer.orderCount) }}</strong>
</div> </div>
<div> <span class="purchase-stats__divider" aria-hidden="true" />
<span>مجموع خرید</span> <div class="purchase-stats__item">
<strong>{{ formatNumber(customer.totalPurchaseToman) }}</strong> <span class="purchase-stats__label">مجموع خرید</span>
<em>تومان</em> <strong class="purchase-stats__value">{{ formatNumber(customer.totalPurchaseToman) }}</strong>
<em class="purchase-stats__unit">تومان</em>
</div> </div>
<div> <span class="purchase-stats__divider" aria-hidden="true" />
<span>میانگین هر سفارش</span> <div class="purchase-stats__item">
<strong>{{ formatNumber(customer.averageOrderToman) }}</strong> <span class="purchase-stats__label">میانگین هر سفارش</span>
<em>تومان</em> <strong class="purchase-stats__value">{{ formatNumber(customer.averageOrderToman) }}</strong>
<em class="purchase-stats__unit">تومان</em>
</div> </div>
</div> </div>
</section> </section>
<button type="button" class="agent-card" @click="router.push('/profile')"> <section class="panel-card">
<div class="agent-card__avatar">{{ agentInitial }}</div> <button type="button" class="agent-row" @click="router.push('/profile')">
<div> <div class="agent-row__avatar">{{ agentInitial }}</div>
<strong>{{ agentName }}</strong> <div class="agent-row__info">
<p>کد واسطه: {{ agentCode }}</p> <strong>{{ agentName }}</strong>
</div> <p>کد واسطه: {{ agentCode }}</p>
<IconChevronLeft :size="18" /> </div>
</button> <IconChevronLeft :size="18" class="agent-row__chevron" aria-hidden="true" />
</button>
<section> <div class="orders-block">
<div class="section-head"> <div class="orders-block__head">
<h3>آخرین سفارش‌ها</h3> <h3 class="section-title">آخرین سفارش‌ها</h3>
<button type="button" class="link" @click="setTab('orders')">مشاهده همه</button> <button type="button" class="link-btn" @click="setTab('orders')">مشاهده همه</button>
</div>
<article
v-for="order in recentOrders"
:key="order.id"
class="order-row"
role="button"
tabindex="0"
@click="openOrder(order.id)"
@keydown.enter="openOrder(order.id)"
>
<div class="order-row__title">
<strong>{{ order.title }}</strong>
<IconChevronLeft :size="14" class="order-row__chevron" aria-hidden="true" />
</div>
<span class="order-row__amount">{{ formatNumber(order.amountToman) }} تومان</span>
<span class="status-pill" :class="`status-pill--${orderStatusTone(order)}`">
{{ orderStatusLabel(order) }}
</span>
</article>
<p v-if="!recentOrders.length" class="empty-inline">سفارشی ثبت نشده است.</p>
</div> </div>
</section>
</template>
<template v-else-if="tab === 'orders'">
<section class="panel-card panel-card--list">
<article <article
v-for="order in orders" v-for="order in orders"
:key="order.id" :key="order.id"
@@ -231,74 +300,58 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
@click="openOrder(order.id)" @click="openOrder(order.id)"
@keydown.enter="openOrder(order.id)" @keydown.enter="openOrder(order.id)"
> >
<div> <div class="order-row__title">
<strong>{{ order.title }}</strong> <strong>{{ order.title }}</strong>
<p>{{ formatNumber(order.amountToman) }} تومان</p> <IconChevronLeft :size="14" class="order-row__chevron" aria-hidden="true" />
</div> </div>
<span class="tone" :class="`tone--${orderStatusTone(order)}`">{{ orderStatusLabel(order) }}</span> <span class="order-row__amount">{{ formatNumber(order.amountToman) }} تومان</span>
<IconChevronLeft :size="16" class="order-row__chevron" /> <span class="status-pill" :class="`status-pill--${orderStatusTone(order)}`">
{{ orderStatusLabel(order) }}
</span>
</article> </article>
<p v-if="!orders.length" class="empty-inline">سفارشی ثبت نشده است.</p> <p v-if="!orders.length" class="empty-inline">سفارشی ثبت نشده است.</p>
</section> </section>
</template> </template>
<template v-else-if="tab === 'orders'">
<article
v-for="order in orders"
:key="order.id"
class="order-row"
role="button"
tabindex="0"
@click="openOrder(order.id)"
@keydown.enter="openOrder(order.id)"
>
<div>
<strong>{{ order.title }}</strong>
<p>{{ order.date }} — {{ formatNumber(order.amountToman) }} تومان</p>
</div>
<span class="tone" :class="`tone--${orderStatusTone(order)}`">{{ orderStatusLabel(order) }}</span>
<IconChevronLeft :size="16" class="order-row__chevron" />
</article>
<p v-if="!orders.length" class="empty-inline">سفارشی ثبت نشده است.</p>
</template>
<template v-else> <template v-else>
<h3 class="block-title">تعاملات و پیگیری‌ها</h3> <section class="panel-card panel-card--reports">
<button <h3 class="section-title">تعاملات و پیگیری‌ها</h3>
type="button"
class="dashed-btn"
@click="router.push({ path: '/meeting-report', query: { customerId: customer.id } })"
>
+ ثبت گزارش ملاقات جدید
</button>
<div class="meeting-filters">
<button <button
v-for="chip in meetingFilters"
:key="chip.id"
type="button" type="button"
class="meeting-filter" class="dashed-btn"
:class="{ 'meeting-filter--active': meetingFilter === chip.id }" @click="openMeetingReport"
@click="meetingFilter = chip.id"
> >
{{ chip.label }} + ثبت گزارش ملاقات جدید
</button> </button>
</div> <div class="meeting-filters">
<article v-for="item in filteredMeetings" :key="item.id" class="meeting-row"> <button
<div class="meeting-row__icon" :class="meetingIconClass(item.type)"> v-for="chip in meetingFilters"
<IconClipboardEmpty v-if="item.type === 'in_person'" :size="16" /> :key="chip.id"
<IconPhone v-else-if="item.type === 'call'" :size="16" /> type="button"
<IconMail v-else-if="item.type === 'message'" :size="16" /> class="meeting-filter"
<IconEditPen v-else :size="16" /> :class="{ 'meeting-filter--active': meetingFilter === chip.id }"
@click="meetingFilter = chip.id"
>
{{ chip.label }}
</button>
</div> </div>
<div class="meeting-row__body"> <article v-for="item in filteredMeetings" :key="item.id" class="meeting-row">
<strong>{{ typeLabel(item.type) }}</strong> <div class="meeting-row__icon" :class="meetingIconClass(item.type)">
<p>{{ item.description || item.title }}</p> <IconClipboardEmpty v-if="item.type === 'in_person'" :size="16" />
</div> <IconPhone v-else-if="item.type === 'call'" :size="16" />
<span>{{ item.date }}</span> <IconMail v-else-if="item.type === 'message'" :size="16" />
</article> <IconEditPen v-else :size="16" />
<p v-if="!filteredMeetings.length" class="empty-inline"> </div>
گزارشی برای این فیلتر ثبت نشده است. <div class="meeting-row__body">
</p> <strong>{{ typeLabel(item.type) }}</strong>
<p>{{ item.description || item.title }}</p>
</div>
<span class="meeting-row__date">{{ item.date }}</span>
</article>
<p v-if="!filteredMeetings.length" class="empty-inline">
گزارشی برای این فیلتر ثبت نشده است.
</p>
</section>
</template> </template>
</div> </div>
@@ -317,6 +370,13 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
<div v-else class="page empty"> <div v-else class="page empty">
<p>{{ error || 'مشتری یافت نشد.' }}</p> <p>{{ error || 'مشتری یافت نشد.' }}</p>
</div> </div>
<MeetingReportOverlay
:open="showMeetingReport"
:customer-id="id"
@close="closeMeetingReport"
@success="onMeetingReportSuccess"
/>
</template> </template>
<style scoped> <style scoped>
@@ -325,62 +385,100 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
} }
.customer-scroll { .customer-scroll {
padding: 8px var(--page-padding) 100px; padding: 0 var(--page-padding) 100px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 14px; gap: 0;
} }
.profile-row { .profile-card,
.purchase-card,
.panel-card {
background: transparent;
border-radius: 0;
box-shadow: none;
}
.profile-card {
display: flex; display: flex;
gap: 12px;
align-items: center; align-items: center;
gap: 14px;
padding: 12px 0 16px;
border-bottom: 1px solid var(--color-border);
} }
.avatar { .profile-card__avatar {
width: 64px; width: 56px;
height: 64px; height: 56px;
border-radius: 999px; border-radius: 999px;
display: grid; display: grid;
place-items: center; place-items: center;
font-size: 1.4rem; font-size: 1.25rem;
font-weight: 700; font-weight: 700;
color: #111;
flex-shrink: 0;
} }
.avatar--lg { .profile-card__avatar--lg {
width: 56px; width: 56px;
height: 56px; height: 56px;
} }
.name-row { .profile-card__info {
min-width: 0;
flex: 1;
}
.profile-card__name-row {
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 8px; gap: 8px;
} }
.name-row h2, .profile-card__name-row h2,
.profile-banner h2 { .profile-banner h2 {
margin: 0; margin: 0;
font-size: 1.1rem; font-size: 1.05rem;
}
.profile-row p,
.profile-banner p {
margin: 2px 0 0;
color: var(--color-text-muted);
font-size: 0.85rem;
}
.badge {
background: var(--color-primary-soft);
color: var(--color-primary);
border-radius: 999px;
padding: 2px 10px;
font-size: 0.75rem;
font-weight: 700; font-weight: 700;
line-height: 1.35;
} }
.badge--light { .profile-card__phone,
.profile-banner p {
margin: 6px 0 0;
color: var(--color-text-muted);
font-size: 0.875rem;
line-height: 1.4;
}
.status-badge {
display: inline-flex;
align-items: center;
height: 24px;
padding: 0 10px;
border-radius: 6px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
}
.status-badge--active {
background: var(--color-success-light);
color: var(--color-success);
}
.status-badge--inactive {
background: #f3f4f6;
color: #6b7280;
}
.status-badge--new {
background: #ede9fe;
color: #6d28d9;
}
.status-badge--on-dark {
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
color: #fff; color: #fff;
} }
@@ -390,6 +488,7 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
color: #fff; color: #fff;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
padding: 16px; padding: 16px;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 6px 16px rgba(16, 24, 40, 0.05);
} }
.profile-banner__top { .profile-banner__top {
@@ -422,169 +521,269 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
.tabs { .tabs {
display: flex; display: flex;
gap: 12px; width: 100%;
align-items: stretch;
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border);
overflow-x: auto;
} }
.tab { .tab {
border: none; flex: 1;
background: transparent; display: flex;
padding: 10px 2px; justify-content: center;
color: var(--color-text-muted); align-items: flex-end;
white-space: nowrap; min-width: 0;
appearance: none;
-webkit-appearance: none;
border: 0;
background: none;
padding: 10px 0 0;
color: #9ca3af;
font-family: inherit;
font-size: 0.875rem;
font-weight: 400;
line-height: 1.5;
cursor: pointer; cursor: pointer;
} }
.tab__text {
position: relative;
display: inline-block;
padding-bottom: 11px;
white-space: nowrap;
}
.tab--active { .tab--active {
color: var(--color-text); color: var(--color-text);
font-weight: 700; font-weight: 700;
box-shadow: inset 0 -3px 0 var(--color-text);
} }
.stats, .tab--active .tab__text::after {
.agent-card, content: '';
.order-row, position: absolute;
.meeting-row { inset-inline: 0;
background: var(--color-surface); bottom: -1px;
border-radius: var(--radius-md); height: 3px;
padding: 14px; background: var(--color-primary);
box-shadow: var(--shadow-sm); border-radius: 2px 2px 0 0;
} }
.stats h3, .section-title {
.section-head h3, margin: 0;
.block-title {
margin: 0 0 10px;
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 700;
color: var(--color-text);
} }
.stats__grid { .purchase-card {
padding: 16px 0;
border-bottom: 1px solid var(--color-border);
}
.purchase-stats {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: 1fr 1px 1fr 1px 1fr;
gap: 8px; align-items: stretch;
text-align: center; gap: 0;
margin-top: 14px;
} }
.stats__grid span, .purchase-stats__item {
.stats__grid em { display: flex;
display: block; flex-direction: column;
font-size: 0.7rem; align-items: center;
justify-content: flex-start;
gap: 4px;
padding: 0 6px;
text-align: center;
min-width: 0;
}
.purchase-stats__divider {
width: 1px;
background: var(--color-border);
align-self: stretch;
}
.purchase-stats__label {
font-size: 0.72rem;
color: var(--color-text-muted);
line-height: 1.4;
}
.purchase-stats__value {
font-size: 1rem;
font-weight: 700;
line-height: 1.25;
color: var(--color-text);
}
.purchase-stats__unit {
font-size: 0.72rem;
color: var(--color-text-muted); color: var(--color-text-muted);
font-style: normal; font-style: normal;
line-height: 1.3;
} }
.stats__grid strong { .panel-card {
display: block; padding: 0 0 8px;
margin: 4px 0; overflow: hidden;
font-size: 0.9rem;
} }
.agent-card { .panel-card--list,
width: 100%; .panel-card--reports {
border: none; padding: 16px 0;
display: grid; }
grid-template-columns: 44px 1fr 20px;
gap: 10px; .agent-row {
display: flex;
align-items: center; align-items: center;
gap: 12px;
width: 100%;
padding: 16px 0;
border: none;
border-bottom: 1px solid var(--color-border);
background: transparent;
text-align: start; text-align: start;
cursor: pointer; cursor: pointer;
font-family: inherit;
} }
.agent-card__avatar { .agent-row__avatar {
width: 44px; width: 44px;
height: 44px; height: 44px;
border-radius: 999px; border-radius: 999px;
background: var(--color-primary-soft); background: #e8ecea;
color: var(--color-primary); color: var(--color-primary);
display: grid; display: grid;
place-items: center; place-items: center;
font-weight: 700; font-weight: 700;
}
.agent-card p {
margin: 2px 0 0;
color: var(--color-text-muted);
font-size: 0.78rem;
}
.section-head {
display: flex;
justify-content: space-between;
align-items: center;
}
.link {
border: none;
background: transparent;
color: var(--color-info);
cursor: pointer;
font-size: 0.8rem;
}
.order-row,
.meeting-row {
display: flex;
justify-content: space-between;
gap: 10px;
align-items: center;
margin-bottom: 8px;
}
.order-row {
cursor: pointer;
}
.order-row__chevron {
color: var(--color-text-muted);
flex-shrink: 0; flex-shrink: 0;
} }
.meeting-row__body { .agent-row__info {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
} }
.order-row p, .agent-row__info strong {
.meeting-row p { display: block;
margin: 2px 0 0; font-size: 0.9rem;
font-weight: 700;
}
.agent-row__info p {
margin: 4px 0 0;
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 0.78rem; font-size: 0.78rem;
} }
.agent-row__chevron {
color: #9ca3af;
flex-shrink: 0;
}
.orders-block {
padding: 14px 0 8px;
}
.orders-block__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 4px;
}
.link-btn {
border: none;
background: transparent;
color: #6b7280;
font-size: 0.8rem;
font-weight: 500;
cursor: pointer;
padding: 0;
font-family: inherit;
}
.order-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto auto;
gap: 10px;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid var(--color-border);
cursor: pointer;
font-family: inherit;
}
.panel-card--list .order-row:last-of-type,
.orders-block .order-row:last-of-type {
border-bottom: none;
}
.order-row__title {
display: inline-flex;
align-items: center;
gap: 4px;
min-width: 0;
}
.order-row__title strong {
font-size: 0.875rem;
font-weight: 600;
line-height: 1.35;
}
.order-row__chevron {
color: #9ca3af;
flex-shrink: 0;
}
.order-row__amount {
font-size: 0.8125rem;
font-weight: 600;
color: var(--color-text);
white-space: nowrap;
}
.status-pill {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 26px;
padding: 0 10px;
border-radius: 6px;
font-size: 0.7rem;
font-weight: 600;
white-space: nowrap;
}
.status-pill--warning {
background: #fff4e5;
color: #c27803;
}
.status-pill--success {
background: var(--color-success-light);
color: var(--color-success);
}
.status-pill--info {
background: var(--color-info-light);
color: var(--color-info);
}
.empty-inline { .empty-inline {
margin: 8px 0 0; margin: 12px 0 4px;
text-align: center; text-align: center;
color: var(--color-text-muted); color: var(--color-text-muted);
font-size: 0.85rem; font-size: 0.85rem;
} }
.tone {
border-radius: 999px;
padding: 4px 10px;
font-size: 0.72rem;
white-space: nowrap;
}
.tone--warning {
background: var(--color-warning-light);
color: #b45309;
}
.tone--success {
background: var(--color-success-light);
color: var(--color-success);
}
.tone--info {
background: var(--color-info-light);
color: var(--color-info);
}
.edit-btn { .edit-btn {
border: none; border: none;
background: transparent; background: transparent;
color: var(--color-info, #2563eb); color: #3b82f6;
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 600; font-weight: 600;
cursor: pointer; cursor: pointer;
@@ -596,19 +795,20 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
display: flex; display: flex;
gap: 8px; gap: 8px;
overflow-x: auto; overflow-x: auto;
margin-bottom: 8px; margin: 12px 0 8px;
padding-bottom: 4px; padding-bottom: 4px;
} }
.meeting-filter { .meeting-filter {
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
background: var(--color-surface); background: var(--color-bg);
border-radius: 999px; border-radius: 6px;
padding: 6px 12px; padding: 6px 12px;
font-size: 0.72rem; font-size: 0.72rem;
color: var(--color-text-muted); color: var(--color-text-muted);
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
font-family: inherit;
} }
.meeting-filter--active { .meeting-filter--active {
@@ -627,7 +827,20 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
padding: 14px; padding: 14px;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
margin-bottom: 8px; margin-top: 12px;
font-family: inherit;
}
.meeting-row {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 0;
border-bottom: 1px solid var(--color-border);
}
.meeting-row:last-of-type {
border-bottom: none;
} }
.meeting-row__icon { .meeting-row__icon {
@@ -654,11 +867,34 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
color: #6d28d9; color: #6d28d9;
} }
.meeting-row__body {
flex: 1;
min-width: 0;
}
.meeting-row__body strong {
display: block;
font-size: 0.875rem;
}
.meeting-row__body p {
margin: 4px 0 0;
color: var(--color-text-muted);
font-size: 0.78rem;
line-height: 1.45;
}
.meeting-row__date {
font-size: 0.75rem;
color: var(--color-text-muted);
flex-shrink: 0;
}
.cta-bar { .cta-bar {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
padding: 12px var(--page-padding) calc(12px + env(safe-area-inset-bottom, 0)); padding: 12px var(--page-padding) calc(12px + env(safe-area-inset-bottom, 0));
background: linear-gradient(transparent, var(--color-bg) 30%); background: linear-gradient(180deg, rgba(238, 241, 240, 0) 0%, var(--color-bg) 32%);
} }
.cta { .cta {
@@ -668,12 +904,14 @@ function orderStatusTone(order: CrmOrderSummary): 'warning' | 'success' | 'info'
border-radius: 14px; border-radius: 14px;
background: var(--color-primary); background: var(--color-primary);
color: #fff; color: #fff;
font-size: 0.95rem;
font-weight: 700; font-weight: 700;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 8px; gap: 8px;
font-family: inherit;
} }
.empty { .empty {
+30 -1
View File
@@ -11,6 +11,7 @@ import IconFilter from '@/components/icons/IconFilter.vue'
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue' import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
import IconPlus from '@/components/icons/IconPlus.vue' import IconPlus from '@/components/icons/IconPlus.vue'
import IconProfile from '@/components/icons/IconProfile.vue' import IconProfile from '@/components/icons/IconProfile.vue'
import CustomerCreateOverlay from '@/components/customers/CustomerCreateOverlay.vue'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -22,6 +23,7 @@ const loading = ref(true)
const error = ref<string | null>(null) const error = ref<string | null>(null)
const filtered = computed(() => customers.value) const filtered = computed(() => customers.value)
const showCreate = computed(() => route.query.create === '1')
const activeCount = computed(() => customers.value.filter((c) => c.status === 'active').length) const activeCount = computed(() => customers.value.filter((c) => c.status === 'active').length)
const totalPurchase = computed(() => const totalPurchase = computed(() =>
@@ -81,8 +83,29 @@ function statusLabel(s: CustomerStatus) {
return 'جدید' return 'جدید'
} }
function customerListQuery(extra: Record<string, string> = {}) {
return {
...(status.value !== 'all' ? { status: status.value } : {}),
...(q.value ? { q: q.value } : {}),
...extra,
}
}
function onRegisterCustomer() { function onRegisterCustomer() {
router.push('/customers/new') router.push({ path: '/customers', query: { ...customerListQuery(), create: '1' } })
}
function closeCreate() {
router.replace({ path: '/customers', query: customerListQuery() })
}
async function onCustomerCreated(id?: string) {
if (id) {
await router.replace({ path: '/customer', query: { id } })
return
}
closeCreate()
await load()
} }
function cycleStatusFilter() { function cycleStatusFilter() {
@@ -196,6 +219,12 @@ function cycleStatusFilter() {
</div> </div>
</section> </section>
</div> </div>
<CustomerCreateOverlay
:open="showCreate"
@close="closeCreate"
@success="onCustomerCreated"
/>
</div> </div>
</template> </template>
+91 -30
View File
@@ -468,10 +468,6 @@ async function toggleFavorite(productId: string) {
padding-bottom: 8px; padding-bottom: 8px;
} }
.home-scroll > .section:last-child {
margin-top: auto;
}
.hero-banner { .hero-banner {
--hero-image: #8a7a65; --hero-image: #8a7a65;
position: relative; position: relative;
@@ -515,6 +511,11 @@ async function toggleFavorite(productId: string) {
.hero-banner__content { .hero-banner__content {
position: relative; position: relative;
z-index: 1; z-index: 1;
width: auto;
max-width: 100%;
min-width: 0;
box-sizing: border-box;
text-align: start;
animation: hero-copy 0.7s cubic-bezier(0.22, 1, 0.36, 1) both; animation: hero-copy 0.7s cubic-bezier(0.22, 1, 0.36, 1) both;
} }
@@ -592,6 +593,7 @@ async function toggleFavorite(productId: string) {
line-height: 1.7; line-height: 1.7;
max-width: 16rem; max-width: 16rem;
font-weight: 700; font-weight: 700;
overflow-wrap: break-word;
} }
.hero-banner__title span { .hero-banner__title span {
@@ -644,7 +646,7 @@ async function toggleFavorite(productId: string) {
.quick-card { .quick-card {
border: none; border: none;
background: transparent; background: var(--color-surface);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: 12px 6px; padding: 12px 6px;
display: flex; display: flex;
@@ -652,7 +654,7 @@ async function toggleFavorite(productId: string) {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
cursor: pointer; cursor: pointer;
box-shadow: none; box-shadow: 0 2px 10px rgba(16, 24, 40, 0.06);
} }
.quick-card__icon { .quick-card__icon {
@@ -661,8 +663,9 @@ async function toggleFavorite(productId: string) {
border-radius: 12px; border-radius: 12px;
display: grid; display: grid;
place-items: center; place-items: center;
background: var(--color-surface); background: transparent;
box-shadow: 0 2px 12px rgba(16, 24, 40, 0.06); box-shadow: none;
border: none;
} }
.quick-card__image { .quick-card__image {
@@ -683,8 +686,8 @@ async function toggleFavorite(productId: string) {
background: var(--color-surface); background: var(--color-surface);
border-radius: 18px; border-radius: 18px;
padding: 16px; padding: 16px;
box-shadow: 0 2px 12px rgba(16, 24, 40, 0.06); box-shadow: none;
border: 1px solid rgba(230, 232, 230, 0.9); border: 1px solid var(--color-border);
} }
.level-card__top { .level-card__top {
@@ -819,7 +822,8 @@ async function toggleFavorite(productId: string) {
text-align: start; text-align: start;
cursor: pointer; cursor: pointer;
overflow: hidden; overflow: hidden;
box-shadow: 0 2px 10px rgba(16, 24, 40, 0.05); box-shadow: none;
border: 1px solid var(--color-border);
scroll-snap-align: start; scroll-snap-align: start;
} }
@@ -912,7 +916,11 @@ async function toggleFavorite(productId: string) {
@media (min-width: 768px) { @media (min-width: 768px) {
.home-page { .home-page {
padding-top: 8px; max-width: var(--content-max-width);
margin-inline: auto;
width: 100%;
padding: 8px var(--page-padding) 0;
box-sizing: border-box;
} }
.home-desk-head { .home-desk-head {
@@ -938,38 +946,87 @@ async function toggleFavorite(productId: string) {
} }
.home-scroll { .home-scroll {
gap: 22px; display: grid;
padding: 8px 0 24px; grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-areas:
'hero hero level'
'quick quick quick'
'products products products';
gap: 18px 20px;
padding: 8px 0 28px;
align-items: stretch;
min-width: 0;
} }
.hero-banner { .hero-banner {
min-height: 220px; grid-area: hero;
width: 100%;
max-width: none;
min-width: 0;
min-height: 0;
height: auto;
aspect-ratio: 2.1 / 1;
align-self: start;
border-radius: 20px; border-radius: 20px;
} }
.hero-slide { .level-card {
padding: 28px 32px; grid-area: level;
} padding: 20px 22px;
align-self: stretch;
.hero-banner__title { display: flex;
font-size: 1.35rem; flex-direction: column;
max-width: 22rem; justify-content: center;
} }
.quick-grid { .quick-grid {
grid-area: quick;
display: grid; display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 14px; gap: 14px;
} }
.home-scroll > .section {
grid-area: products;
}
.hero-slide {
width: 100%;
box-sizing: border-box;
align-items: flex-end;
justify-content: flex-start;
padding-inline-start: 36px;
padding-inline-end: 28px;
padding-block: 24px;
}
.hero-slide__media {
background-size: cover;
background-position: left center;
background-color: #8a7a65;
}
.hero-banner__content {
max-width: min(52%, 22rem);
}
.hero-banner__title {
font-size: 1.15rem;
max-width: none;
}
.hero-next-enter-from,
.hero-next-leave-to,
.hero-prev-enter-from,
.hero-prev-leave-to {
transform: none;
}
.quick-card { .quick-card {
min-height: 96px; min-height: 96px;
padding: 18px 14px; padding: 18px 14px;
border-radius: 16px; border-radius: 16px;
} box-shadow: 0 3px 12px rgba(16, 24, 40, 0.07);
.level-card {
padding: 20px 22px;
} }
.product-row { .product-row {
@@ -993,12 +1050,16 @@ async function toggleFavorite(productId: string) {
} }
@media (min-width: 1200px) { @media (min-width: 1200px) {
.hero-banner {
aspect-ratio: 2.2 / 1;
}
.hero-banner__title {
font-size: 1.25rem;
}
.product-row { .product-row {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
.hero-banner {
min-height: 240px;
}
} }
</style> </style>
+25 -17
View File
@@ -11,7 +11,7 @@ import { formatNumber } from '@/utils/format'
import { colorImageAt, colorSwatchStyle } from '@/utils/colorSwatch' import { colorImageAt, colorSwatchStyle } from '@/utils/colorSwatch'
import IconChevronBack from '@/components/icons/IconChevronBack.vue' import IconChevronBack from '@/components/icons/IconChevronBack.vue'
import IconCart from '@/components/icons/IconCart.vue' import IconCart from '@/components/icons/IconCart.vue'
import IconCatalog from '@/components/icons/IconCatalog.vue' import quickCatalogRequestIcon from '@/assets/images/quick-catalog-request.webp'
import IconHeart from '@/components/icons/IconHeart.vue' import IconHeart from '@/components/icons/IconHeart.vue'
import IconShare from '@/components/icons/IconShare.vue' import IconShare from '@/components/icons/IconShare.vue'
import IconShoppingBag from '@/components/icons/IconShoppingBag.vue' import IconShoppingBag from '@/components/icons/IconShoppingBag.vue'
@@ -379,7 +379,7 @@ function shareProduct() {
<p class="min-note">حداقل سفارش: {{ formatNumber(minRolls) }} طاقه</p> <p class="min-note">حداقل سفارش: {{ formatNumber(minRolls) }} طاقه</p>
</div> </div>
<button type="button" class="btn-secondary" @click="router.push({ path: '/catalog-request', query: { productId: product.id } })"> <button type="button" class="btn-secondary" @click="router.push({ path: '/catalog-request', query: { productId: product.id } })">
<IconCatalog :size="18" /> <img class="btn-secondary__icon" :src="quickCatalogRequestIcon" alt="" aria-hidden="true" />
درخواست کالیته درخواست کالیته
</button> </button>
</footer> </footer>
@@ -400,7 +400,7 @@ function shareProduct() {
--pd-card-border: 1px solid #eeeef1; --pd-card-border: 1px solid #eeeef1;
--pd-card-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 6px 16px rgba(16, 24, 40, 0.05); --pd-card-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 6px 16px rgba(16, 24, 40, 0.05);
--pd-card-radius: 16px; --pd-card-radius: 16px;
background: #fff; background: var(--color-surface);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
min-height: 100%; min-height: 100%;
@@ -446,7 +446,7 @@ function shareProduct() {
inset-inline-start: 12px; inset-inline-start: 12px;
background: rgba(0, 0, 0, 0.55); background: rgba(0, 0, 0, 0.55);
color: #fff; color: #fff;
border-radius: 999px; border-radius: 6px;
padding: 4px 10px; padding: 4px 10px;
font-size: 0.75rem; font-size: 0.75rem;
} }
@@ -457,7 +457,7 @@ function shareProduct() {
margin: 12px var(--page-padding) 0; margin: 12px var(--page-padding) 0;
padding: 10px; padding: 10px;
overflow-x: auto; overflow-x: auto;
background: #fff; background: var(--color-surface);
border: var(--pd-card-border); border: var(--pd-card-border);
border-radius: var(--pd-card-radius); border-radius: var(--pd-card-radius);
box-shadow: var(--pd-card-shadow); box-shadow: var(--pd-card-shadow);
@@ -470,7 +470,7 @@ function shareProduct() {
height: 52px; height: 52px;
border-radius: 14px; border-radius: 14px;
border: var(--pd-card-border); border: var(--pd-card-border);
background: #fff; background: var(--color-surface);
box-shadow: var(--pd-card-shadow); box-shadow: var(--pd-card-shadow);
cursor: pointer; cursor: pointer;
flex-shrink: 0; flex-shrink: 0;
@@ -499,7 +499,7 @@ function shareProduct() {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 18px; gap: 18px;
background: #fff; background: var(--color-surface);
} }
.title-row { .title-row {
@@ -541,7 +541,7 @@ function shareProduct() {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 6px; gap: 6px;
padding: 14px 8px; padding: 14px 8px;
background: #fff; background: var(--color-surface);
border: var(--pd-card-border); border: var(--pd-card-border);
border-radius: var(--pd-card-radius); border-radius: var(--pd-card-radius);
box-shadow: var(--pd-card-shadow); box-shadow: var(--pd-card-shadow);
@@ -631,7 +631,7 @@ function shareProduct() {
.pd-section--boxed { .pd-section--boxed {
padding: 12px 14px; padding: 12px 14px;
background: #fff; background: var(--color-surface);
border: var(--pd-card-border); border: var(--pd-card-border);
border-radius: var(--pd-card-radius); border-radius: var(--pd-card-radius);
box-shadow: var(--pd-card-shadow); box-shadow: var(--pd-card-shadow);
@@ -704,8 +704,8 @@ button.tag {
min-height: 32px; min-height: 32px;
padding: 0 16px; padding: 0 16px;
border: var(--pd-card-border); border: var(--pd-card-border);
border-radius: var(--pd-card-radius); border-radius: 6px;
background: #fff; background: var(--color-surface);
color: #6b7280; color: #6b7280;
font-size: 0.8125rem; font-size: 0.8125rem;
font-weight: 400; font-weight: 400;
@@ -726,7 +726,7 @@ button.tag--link {
} }
button.tag--link:hover { button.tag--link:hover {
background: #fff; background: var(--color-surface);
} }
.roll-field { .roll-field {
@@ -734,11 +734,11 @@ button.tag--link:hover {
align-items: center; align-items: center;
width: fit-content; width: fit-content;
max-width: 100%; max-width: 100%;
border: 1px solid #eeeef1; border: var(--pd-card-border);
border-radius: 16px; border-radius: 6px;
background: #fff; background: var(--color-surface);
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 6px 16px rgba(16, 24, 40, 0.05); box-shadow: var(--pd-card-shadow);
padding: 14px 16px; padding: 0 16px;
color: #6b7280; color: #6b7280;
font-size: 0.9rem; font-size: 0.9rem;
} }
@@ -780,6 +780,14 @@ button.tag--link:hover {
color: var(--color-primary); color: var(--color-primary);
} }
.btn-secondary__icon {
width: 18px;
height: 18px;
display: block;
object-fit: contain;
flex-shrink: 0;
}
.btn-primary { .btn-primary {
border: none; border: none;
background: var(--color-accent-navy); background: var(--color-accent-navy);
+211 -34
View File
@@ -9,8 +9,19 @@ import SearchField from '@/components/ui/SearchField.vue'
import IconFilter from '@/components/icons/IconFilter.vue' import IconFilter from '@/components/icons/IconFilter.vue'
import IconChevronLeft from '@/components/icons/IconChevronLeft.vue' import IconChevronLeft from '@/components/icons/IconChevronLeft.vue'
import IconHeart from '@/components/icons/IconHeart.vue' import IconHeart from '@/components/icons/IconHeart.vue'
import IconCatalog from '@/components/icons/IconCatalog.vue'
import IconDiamond from '@/components/icons/IconDiamond.vue'
import { useFavoritesStore } from '@/stores/favorites' import { useFavoritesStore } from '@/stores/favorites'
import { colorSwatchStyle } from '@/utils/colorSwatch' import { colorSwatchStyle } from '@/utils/colorSwatch'
import { categoryIconOf, usageCategoryIconKey } from '@/utils/categoryIcons'
const CATEGORY_TAGLINES: Record<string, string> = {
suit: 'شیک، بادوام و رسمی',
trousers: 'مناسب شلوار و مصارف روزانه',
shirt: 'سبک و مناسب پیراهن',
}
const GENERIC_CATEGORY_CTA = 'مشاهده محصولات'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -43,15 +54,81 @@ const title = computed(() => {
return 'همه محصولات' return 'همه محصولات'
}) })
const bannerSubtitle = computed(() => { function splitCategoryTitle(fullTitle: string) {
if (catalogId.value) return 'انتخاب محصول از کالیته' const trimmed = fullTitle.trim()
if (categoryId.value === 'suit') return 'شیک، بادوام و رسمی' const prefix = 'پارچه‌های '
if (categoryId.value === 'trousers') return 'مناسب شلوار و مصارف روزانه' if (trimmed.startsWith(prefix)) {
if (categoryId.value === 'shirt') return 'سبک و مناسب پیراهن' return { lead: 'پارچه‌های', headline: trimmed.slice(prefix.length) }
if (season.value) return 'فیلتر شده بر اساس فصل' }
return 'مشاهده و ثبت سفارش' return { lead: undefined, headline: trimmed }
}
function seasonIconKey(item: SeasonCategory) {
return item.icon || (item.id === 'summer' ? 'bolt' : item.id === 'winter' ? 'feather' : 'diamond')
}
const bannerCopy = computed(() => {
if (categoryId.value) {
const cat = crm.usageCategories.find((c) => c.id === categoryId.value)
const parts = splitCategoryTitle(cat?.title ?? title.value)
const tagline =
CATEGORY_TAGLINES[categoryId.value] ??
(cat?.subtitle && cat.subtitle !== GENERIC_CATEGORY_CTA ? cat.subtitle : undefined)
return { ...parts, tagline }
}
if (season.value) {
const item = seasons.value.find((s) => s.id === season.value)
return {
lead: undefined,
headline: item?.title ?? title.value,
tagline: item?.description || 'فیلتر شده بر اساس فصل',
}
}
if (catalogId.value) {
return {
lead: 'کالیته',
headline: catalogTitle.value || title.value,
tagline: 'انتخاب محصول از کالیته',
}
}
return {
lead: undefined,
headline: title.value,
tagline: q.value ? 'نتایج جستجو' : 'مشاهده و ثبت سفارش',
}
}) })
const bannerIcon = computed(() => {
if (categoryId.value) {
const cat = crm.usageCategories.find((c) => c.id === categoryId.value)
if (cat) return categoryIconOf(usageCategoryIconKey(cat), IconCatalog)
}
if (season.value) {
const item = seasons.value.find((s) => s.id === season.value)
if (item) return categoryIconOf(seasonIconKey(item), IconDiamond)
}
return null
})
const bannerImageUrl = computed(() => {
if (categoryId.value) {
const cat = crm.usageCategories.find((c) => c.id === categoryId.value)
if (cat?.imageUrl) return cat.imageUrl
}
if (season.value) {
const item = seasons.value.find((s) => s.id === season.value)
if (item?.imageUrl) return item.imageUrl
}
if (catalogId.value) {
return products.value.find((item) => item.imageUrl)?.imageUrl
}
return undefined
})
const bannerStyle = computed(() =>
bannerImageUrl.value ? { backgroundImage: `url(${bannerImageUrl.value})` } : undefined,
)
const showSearch = computed(() => !categoryId.value && !catalogId.value) const showSearch = computed(() => !categoryId.value && !catalogId.value)
const sortedProducts = computed(() => { const sortedProducts = computed(() => {
@@ -75,6 +152,10 @@ function cycleSort() {
sortMode.value = sortMode.value === 'newest' ? 'name' : 'newest' sortMode.value = sortMode.value === 'newest' ? 'name' : 'newest'
} }
const sortAriaLabel = computed(() =>
sortMode.value === 'newest' ? 'مرتب‌سازی: جدیدترین' : 'مرتب‌سازی: نام',
)
function applySearch(value: string) { function applySearch(value: string) {
searchDraft.value = value searchDraft.value = value
router.replace({ router.replace({
@@ -168,11 +249,18 @@ function toggleFavorite(productId: string) {
<p v-if="error">{{ error }}</p> <p v-if="error">{{ error }}</p>
<PageSkeleton v-else-if="loading" variant="grid" /> <PageSkeleton v-else-if="loading" variant="grid" />
<template v-else> <template v-else>
<section class="cat-banner"> <section
<div class="cat-banner__icon" aria-hidden="true" /> class="cat-banner"
<div> :class="{ 'cat-banner--photo': !!bannerImageUrl }"
<h2>{{ title }}</h2> :style="bannerStyle"
<p>{{ bannerSubtitle }}</p> >
<div v-if="bannerIcon" class="cat-banner__icon-ring" aria-hidden="true">
<component :is="bannerIcon" :size="26" color="#ffffff" />
</div>
<div class="cat-banner__copy">
<p v-if="bannerCopy.lead" class="cat-banner__lead">{{ bannerCopy.lead }}</p>
<h2 class="cat-banner__title">{{ bannerCopy.headline }}</h2>
<p v-if="bannerCopy.tagline" class="cat-banner__tagline">{{ bannerCopy.tagline }}</p>
</div> </div>
</section> </section>
@@ -199,8 +287,34 @@ function toggleFavorite(productId: string) {
<div class="list-meta"> <div class="list-meta">
<span>تعداد محصولات: {{ sortedProducts.length.toLocaleString('fa-IR') }}</span> <span>تعداد محصولات: {{ sortedProducts.length.toLocaleString('fa-IR') }}</span>
<button type="button" class="list-meta__sort" @click="cycleSort"> <button
مرتب‌سازی: {{ sortMode === 'newest' ? 'جدیدترین' : 'نام' }} type="button"
class="list-meta__sort"
:aria-label="sortAriaLabel"
@click="cycleSort"
>
<span class="list-meta__sort-icon" aria-hidden="true">
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.5 2.5V9.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
<path
d="M2.5 7.5L4.5 9.5L6.5 7.5"
stroke="currentColor"
stroke-width="1.3"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path d="M10.5 9.5V2.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
<path
d="M8.5 4.5L10.5 2.5L12.5 4.5"
stroke="currentColor"
stroke-width="1.3"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</span>
<span class="list-meta__sort-badge">۱</span>
<span class="list-meta__sort-label">مرتب‌سازی</span>
</button> </button>
</div> </div>
@@ -288,35 +402,86 @@ function toggleFavorite(productId: string) {
} }
.cat-banner { .cat-banner {
position: relative;
isolation: isolate;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 12px; justify-content: space-between;
min-height: 96px; gap: 16px;
padding: 16px; min-height: 112px;
padding: 18px 20px;
border-radius: var(--radius-lg); border-radius: var(--radius-lg);
overflow: hidden;
background: background:
linear-gradient(100deg, rgba(var(--color-primary-rgb), 0.75), rgba(var(--color-primary-rgb), 0.35)), linear-gradient(100deg, rgba(var(--color-primary-rgb), 0.75), rgba(var(--color-primary-rgb), 0.35)),
#2f3430; #2f3430;
background-size: cover;
background-position: center top;
background-repeat: no-repeat;
color: #fff; color: #fff;
} }
.cat-banner__icon { .cat-banner--photo::before {
width: 48px; content: '';
height: 48px; position: absolute;
border-radius: 999px; inset: 0;
background: rgba(255, 255, 255, 0.2); z-index: 0;
flex-shrink: 0; background: linear-gradient(
90deg,
rgba(0, 0, 0, 0.58) 0%,
rgba(0, 0, 0, 0.28) 42%,
rgba(0, 0, 0, 0.08) 68%,
transparent 100%
);
pointer-events: none;
} }
.cat-banner h2 { .cat-banner__copy {
margin: 0 0 4px; position: relative;
font-size: 1rem; z-index: 1;
min-width: 0;
flex: 1;
text-align: start;
} }
.cat-banner p { .cat-banner__lead {
margin: 0 0 2px;
font-size: 0.82rem;
font-weight: 500;
line-height: 1.4;
opacity: 0.95;
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.35);
}
.cat-banner__title {
margin: 0 0 6px;
font-size: 1.35rem;
font-weight: 700;
line-height: 1.25;
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.35);
}
.cat-banner__tagline {
margin: 0; margin: 0;
font-size: 0.8rem; font-size: 0.78rem;
opacity: 0.9; font-weight: 400;
line-height: 1.5;
opacity: 0.92;
text-shadow: 0 1px 6px rgba(0, 0, 0, 0.35);
}
.cat-banner__icon-ring {
position: relative;
z-index: 1;
width: 56px;
height: 56px;
border-radius: 999px;
border: 1.5px solid rgba(255, 255, 255, 0.88);
background: rgba(0, 0, 0, 0.16);
display: grid;
place-items: center;
flex-shrink: 0;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
} }
.chips { .chips {
@@ -327,9 +492,9 @@ function toggleFavorite(productId: string) {
.chip { .chip {
border: none; border: none;
border-radius: 999px; border-radius: var(--radius-lg);
padding: 8px 14px; padding: 8px 14px;
background: #ecefee; background: var(--color-bg);
color: var(--color-text); color: var(--color-text);
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
@@ -350,15 +515,27 @@ function toggleFavorite(productId: string) {
} }
.list-meta__sort { .list-meta__sort {
display: inline-flex;
align-items: center;
gap: 4px;
border: none; border: none;
background: transparent; background: transparent;
color: var(--color-primary); color: inherit;
font: inherit; font: inherit;
font-weight: 700;
cursor: pointer; cursor: pointer;
padding: 0; padding: 0;
} }
.list-meta__sort-icon {
display: inline-flex;
flex-shrink: 0;
line-height: 0;
}
.list-meta__sort-badge {
font-variant-numeric: tabular-nums;
}
.product-item { .product-item {
display: grid; display: grid;
grid-template-columns: 112px 1fr; grid-template-columns: 112px 1fr;
@@ -415,7 +592,7 @@ function toggleFavorite(productId: string) {
font-size: 0.65rem; font-size: 0.65rem;
font-weight: 700; font-weight: 700;
padding: 2px 8px; padding: 2px 8px;
border-radius: 999px; border-radius: 6px;
white-space: nowrap; white-space: nowrap;
} }
@@ -33,7 +33,7 @@ async function open(item: AppNotification) {
</script> </script>
<template> <template>
<ProfileSubpageLayout title="اعلان‌ها" subtitle="آخرین خبرهای سفارش‌های شما"> <ProfileSubpageLayout title="اعلان‌ها" subtitle="سفارش‌ها، پیشنهاد محصول و سایر اعلان‌ها">
<div v-if="items.length" class="notif-box"> <div v-if="items.length" class="notif-box">
<div class="notif-box__head"> <div class="notif-box__head">
<span class="notif-box__count"> <span class="notif-box__count">
@@ -75,7 +75,7 @@ async function open(item: AppNotification) {
v-else v-else
icon="vuesax-linear:notification-bing" icon="vuesax-linear:notification-bing"
title="اعلانی ندارید" title="اعلانی ندارید"
message="وقتی فاکتور سفارشتان صادر شود یا وضعیت چک و پرداخت تغییر کند، همین‌جا خبردار می‌شوید." message="وقتی فاکتور سفارشتان صادر شود، پیشنهاد محصولی برایتان ثبت شود یا وضعیت پرداخت تغییر کند، همین‌جا خبردار می‌شوید."
> >
<BaseButton variant="outline" @click="router.push('/orders')">سفارش‌های من</BaseButton> <BaseButton variant="outline" @click="router.push('/orders')">سفارش‌های من</BaseButton>
</EmptyState> </EmptyState>
@@ -129,7 +129,7 @@ async function open(item: AppNotification) {
} }
.notif-row--unread { .notif-row--unread {
background: var(--color-elevated); background: var(--color-tab-inactive-bg);
} }
.notif-row__icon { .notif-row__icon {
@@ -93,7 +93,7 @@ const ordersCount = computed(() => profile.value?.ordersCount ?? orderStore.orde
const salesVolume = computed(() => profile.value?.salesVolumeToman ?? 0) const salesVolume = computed(() => profile.value?.salesVolumeToman ?? 0)
const profit = computed(() => profile.value?.profitToman ?? 0) const profit = computed(() => profile.value?.profitToman ?? 0)
const levelProgress = computed( const levelProgress = computed(
() => profile.value?.levelProgress ?? auth.user?.levelProgress ?? 72, () => profile.value?.levelProgress ?? auth.user?.levelProgress ?? 0,
) )
const unreadLabel = computed(() => const unreadLabel = computed(() =>
notificationStore.unreadCount > 9 ? '9+' : String(notificationStore.unreadCount), notificationStore.unreadCount > 9 ? '9+' : String(notificationStore.unreadCount),
@@ -257,9 +257,11 @@ onMounted(async () => {
</svg> </svg>
</div> </div>
<p class="loyalty__text"> <p class="loyalty__text">
شما در سطح {{ levelLabel }} قرار دارید. تا سطح <span>شما در سطح {{ levelLabel }} قرار دارید.</span>
{{ nextLevelLabel }} فقط <span class="loyalty__text-next">
{{ formatNumber(pointsToNext) }} تومان فروش دیگر نیاز دارید. تا سطح {{ nextLevelLabel }} فقط
{{ formatNumber(pointsToNext) }} تومان فروش دیگر نیاز دارید.
</span>
</p> </p>
</div> </div>
<div class="loyalty__bar"> <div class="loyalty__bar">
@@ -605,11 +607,16 @@ onMounted(async () => {
} }
.loyalty__text { .loyalty__text {
font-weight: bold;
margin: 0; margin: 0;
font-size: 0.8125rem; font-size: 0.75rem;
line-height: 1.75; line-height: 1.75;
color: var(--color-text); color: var(--color-text);
text-align: start; text-align: center;
}
.loyalty__text-next {
display: block;
} }
.loyalty__bar { .loyalty__bar {
+1 -1
View File
@@ -288,7 +288,7 @@ const chartBars = computed(() => {
.btn-outline { .btn-outline {
border: 1px solid var(--color-border); border: 1px solid var(--color-border);
background: #fff; background: var(--color-surface);
} }
.btn-primary { .btn-primary {

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