From 1ed04ec706346b8fd0f726b09e5c1547324211fb Mon Sep 17 00:00:00 2001 From: Marwan Alwali Date: Mon, 27 Jan 2025 19:24:42 +0300 Subject: [PATCH] update --- .../__pycache__/settings.cpython-311.pyc | Bin 7310 -> 7513 bytes inventory/__pycache__/forms.cpython-311.pyc | Bin 32637 -> 32295 bytes inventory/__pycache__/mixins.cpython-311.pyc | Bin 3600 -> 2391 bytes inventory/__pycache__/models.cpython-311.pyc | Bin 85837 -> 88748 bytes inventory/__pycache__/urls.cpython-311.pyc | Bin 23951 -> 23872 bytes inventory/__pycache__/utils.cpython-311.pyc | Bin 23190 -> 23390 bytes inventory/__pycache__/views.cpython-311.pyc | Bin 167313 -> 174321 bytes inventory/mixins.py | 24 +- inventory/signals.py | 3 +- inventory/utils.py | 4 +- requirements.txt | 48 +- static/css/appt-common.css | 452 +++++++++ ...jumaih-Automotive-logo_kM7B61x_AROA7K7.png | Bin 0 -> 10227 bytes static/js/app_admin/staff_index.js | 870 ++++++++++++++++++ static/js/appointments.js | 370 ++++++++ static/js/js-utils.js | 8 + static/js/modal/error_modal.js | 19 + static/js/modal/show_modal.js | 25 + .../administration/display_appointment.html | 98 ++ .../email_change_verification_code.html | 41 + templates/administration/manage_day_off.html | 152 +++ templates/administration/manage_service.html | 51 + .../administration/manage_staff_member.html | 87 ++ .../manage_staff_personal_info.html | 49 + .../administration/manage_working_hours.html | 183 ++++ templates/administration/service_list.html | 78 ++ templates/administration/staff_index.html | 409 ++++++++ templates/administration/staff_list.html | 81 ++ templates/administration/user_profile.html | 281 ++++++ .../appointment_client_information.html | 165 ++++ templates/appointment/appointments.html | 134 +++ templates/appointment/default_thank_you.html | 36 + .../appointment/enter_verification_code.html | 39 + .../appointment/rescheduling_thank_you.html | 79 ++ templates/appointment/set_password.html | 104 +++ templates/appointment/thank_you.html | 53 ++ templates/base.html | 8 +- .../admin_new_appointment_email.html | 74 ++ templates/email_sender/reminder_email.html | 97 ++ templates/email_sender/reschedule_email.html | 93 ++ templates/email_sender/thank_you_email.html | 290 ++++++ templates/inventory/car_inventory.html | 2 +- templates/modal/confirm_modal.html | 23 + templates/modal/error_modal.html | 21 + templates/modal/event_details_modal.html | 38 + templates/sales/invoices/invoice_detail.html | 12 +- templates/sales/invoices/invoice_preview.html | 163 ++-- test.txt | 12 + 48 files changed, 4649 insertions(+), 127 deletions(-) create mode 100644 static/css/appt-common.css create mode 100644 static/images/logos/vendors/Aljumaih-Automotive-logo_kM7B61x_AROA7K7.png create mode 100644 static/js/app_admin/staff_index.js create mode 100644 static/js/appointments.js create mode 100644 static/js/js-utils.js create mode 100644 static/js/modal/error_modal.js create mode 100644 static/js/modal/show_modal.js create mode 100644 templates/administration/display_appointment.html create mode 100644 templates/administration/email_change_verification_code.html create mode 100644 templates/administration/manage_day_off.html create mode 100644 templates/administration/manage_service.html create mode 100644 templates/administration/manage_staff_member.html create mode 100644 templates/administration/manage_staff_personal_info.html create mode 100644 templates/administration/manage_working_hours.html create mode 100644 templates/administration/service_list.html create mode 100644 templates/administration/staff_index.html create mode 100644 templates/administration/staff_list.html create mode 100644 templates/administration/user_profile.html create mode 100644 templates/appointment/appointment_client_information.html create mode 100644 templates/appointment/appointments.html create mode 100644 templates/appointment/default_thank_you.html create mode 100644 templates/appointment/enter_verification_code.html create mode 100644 templates/appointment/rescheduling_thank_you.html create mode 100644 templates/appointment/set_password.html create mode 100644 templates/appointment/thank_you.html create mode 100644 templates/email_sender/admin_new_appointment_email.html create mode 100644 templates/email_sender/reminder_email.html create mode 100644 templates/email_sender/reschedule_email.html create mode 100644 templates/email_sender/thank_you_email.html create mode 100644 templates/modal/confirm_modal.html create mode 100644 templates/modal/error_modal.html create mode 100644 templates/modal/event_details_modal.html create mode 100644 test.txt diff --git a/car_inventory/__pycache__/settings.cpython-311.pyc b/car_inventory/__pycache__/settings.cpython-311.pyc index 398c131a749606ca371b47c2eefb6c13cad4c9b1..165fdde1c4faf57ca202d6e7c55e46b17cff4152 100644 GIT binary patch delta 918 zcmZ8f%~MiQ6n_sfiUO67QbrU71VvHtTNHi3Lr~x$?~!$^UT-{zq@WXy3pO~d8)up~ zlN+X`O|>fe3z})uCRYvdH`GkKT6Td&$(i|`d+zUi-ZS@X;rpWYwML_+h`{-I=9~3} zwwr$b-r=UVRnUuOFk>I|VL$Yv1uWPL12~YOVGsv1G+GI4Wd&H#4mKPDJ33$pRp3A; zIB^(;$vJ`};6fLSrf=bB@g_x)>@oQZZSNYpP*Bu6f~J7G&Klh<>^P3YMBP)Gxa!%Z zVG<|7gC3Y7J*Np~2xifvpwicI>VhWK37WF2!084*)5>QQFo&}+PjVM<4i<497jQ94 z6YV8>soVx$T!y6*QOoE9AFjX(uEHuZz@Qq<=qI6pEKP#_WFbLh%Zi2-YFKOJ7A`Bn zj}ZtoMu=P;0fK~8;Cc(o;s%5;3TqgHFvhXOVFEWXi9G62D61fXDOe}iXcWEMk{(4R z#0cW#T^wnhz%9Io_YrU#AK=6CHP~#lebkbdL=kv=3<4^7YFLsyWQFSB6DePL+xGc} zs@9&)N!bJWP-B$OG@odfys0(C3c2GWH(Pi3rJenB9*f!rm8nziox@y~3`rF`_oVE; zbg+7y&hJSF?t)a*XY++4i*%ApTk?gcMa$7iHeV@eU$x2Ox<7QP;?x`HJ~I;taH+T; zGASX<#)U|L5h7e%jIlzP3(8)@50Q;Ak*FBvc%i0l+6gWx)HF>y#j~Oio$8;e;HW(7&d_QbHQ>CY z%+(&V{IkbO*3o5}beX6w->IgmF`ufdnogoR4dkk`opw}jyDlkx)#$jSbib}q+U`o! S^qX!crok&mdwt3er1oE!P4r{{ delta 756 zcmZ9J$xl;p5Xax|(XbRtYzu(_Vxq)odh()Y4<;U!6NyLv0HcYC)AmFs`A+8dnVH`td^8a&1fYT+NcF?YSk{F{h(Bm zxT?*3FIw%MV_A~kvT{c546W$MqwVM935B{)MBOZf<%J?kS~0xG zf_G=-Y+FW8miOj!U8WxNQ7`xQp`ZFOfI$pJ1^Z$4BdG!!z$gtKvKxYrhA~DX7^hK8 zkPlYW!q26XQNhK2e)tpuDLETWXQLTj&893gh9He+uMj+c1)5+gBa}z|n4?L|(-amc zfJGcGA&dx?u>vQqq>5-7S80Ydo85FZuRRD0g;;a^u6aIs0c*5~b!^}ouHy!7f>MRF zls)=ZUT+w;DS|t+49lA2xvTc^H(Jx53ci#yh5icfY0gTh!sj~d?7;)h9X z*HD>w%%`R0=11F!SHX^I=m}*jv8yR@H5yaysap>;14%|kG7;01Ui}QGxwp zT;1JQjj27&xUa^Pq-yRs{)m+{oaMrNS^g{iv*Nkzbj$`rfsOF`W@vRKvKfp77Q*JE zsyEhmJnPKFla_SHWnEa>r0NFqRkcef=I82O>m|t^5RYwVw#sx(&2Lfiy~6!Xa-2DR MW@Vk9-O0a`@E`~Uy| diff --git a/inventory/__pycache__/forms.cpython-311.pyc b/inventory/__pycache__/forms.cpython-311.pyc index b7d0f1a615bc0a91be90ff2c732aec68981abc49..eb66006bc4c985a5f2ae60581c33b24038b3a1b9 100644 GIT binary patch delta 9466 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 R9jgHcT0r{ED3ugG{s(5PnC<`o delta 9156 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 K?qt2#n*R^_^g8?i diff --git a/inventory/__pycache__/mixins.cpython-311.pyc b/inventory/__pycache__/mixins.cpython-311.pyc index a098233c53981a1b90f7ea45b0a127e67fc75a8b..6bcef338f68806ff9c0bab6fca9171b9c8d0604d 100644 GIT binary patch delta 569 zcmbOrb6tpUIWI340}$}XPfK6LI+1Vo5rn%Wor2pc{+N1BnKP4_pjNYD=WAD;ix=G`gs0az)YP0|OgpCL@Hr$Rpd~HG}o; zWJA{J;ui$8Kky>tK7+jKr^!0`0H>a^IFP-Pp-2Kq6^VfeHWbk%y*g8jaCMZBi4T4NEpzGRB8K_-fWrlSW@WXO^W@eBfm6chCJj zKl7bidFrFirY}OF1_JW+^PkLr&eof5y(ZugWN!rEJ+eV`5~mu83mT1kG+~v*y+txd zHP0%Gi>~Gc%~vEhXx#rgQA8(nLDQyE3DbPpxM5^+3+oRe7v{1&EzcQxN;Bm|Mw6F$ zVsX*P%*)n-o|cV_oRBr+y1Z;y3-TY%Y~eSg^0x4$w~eo!vvn>5sNbe-Sd5 z>=Qzp{R17eEn!N~L>RU=`zFBawR;+R!g3k=Cm}ZO+O8Wlk8N?lj(KKI=F+0h9j2;A#;{bCOK^|kh9qu(x-gAp+ z-n;-PekL9@4@vjsj0VV@E3aJFIX5)jWv8sF^SWhNdLgZG#PXm}pUkrDjED;#yI{r(RTIgW@ z6e++N3pN2^{$k`r+q(azztYmZ)6%_lsnnvBTa==>D|LO^SeE+B(ojVj-H}GWRqtyh zX}T;;7el*^QkBraoAkGE$Ia<-xTg{x+6fPB4`00>0>JOpEBngu)lyh3ht=Zc-R5vH zh-CA?8)9D(2d1zRTyAsp*$M5_e^yVWR8^t1=#XCLYh~cW{Rr61G{7T!h_$`ZB;reO z2xUR+u9E)&#>CbtIUewi?|&7S=^5Swh7^G#iyc4YF=1I8yu$)E(L0&9+spnj8neIg zpFfAyGz0&u7kb`@pdj=k;M@NdaSN^%Q4D7h6uXa&`S2`{*%}+~b63vd*qy{@;BPjO p&T303eUgvC5KWt__RlQV&twW=9fdz}?&0Hw7pNT#414j$e*wsHqKE(h diff --git a/inventory/__pycache__/models.cpython-311.pyc b/inventory/__pycache__/models.cpython-311.pyc index 17a6662eb99cac5b534815f4c4c45452f47b817f..68d5f23050abb4db37d69d84ea6155c84ab01bb0 100644 GIT binary patch delta 10973 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 X(}7m-w_u}sOT;zCfAc$yVxj*RMoW)R delta 8747 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 diff --git a/inventory/__pycache__/urls.cpython-311.pyc b/inventory/__pycache__/urls.cpython-311.pyc index df997e16b068a38ebdf40556edcc969a6cee6fd3..2f40588cd5197dd51f9bc7af3ee1a7001f948d1d 100644 GIT binary patch delta 3037 zcmZ{ldrVu`9mjp}b-2dZ-~i!uv9ZHV2w(#vEboSgpI{z_V2pVMupv&t2J;Moj;!sv zWhG7WlUlM!E7PPlQlqsMT9&M>O4LrNru|_$uVdXGTUupX_sE=%Y1KAu`<;6X)2hyb zFW=Aaaen8VYn$iZAnQLQ>X+5(6dAtmc0NDy@fCG0k-fhEK9}S#gH|qEVG7947OaI7 zPb)|j_pT_?KEFk5P1M1agv^>u74Id7u$gwQLj)o0s-?9`{!V#c3j` zhvqa|HODN*s?^@M}yb01kz8<$7oJp z-QxtcURV-)If_3@+&^VOl4U zC+RY8qj`!Nr5>+PZ-O?x9^Tehz<2aI@(ir&o5z8T(WHPI#{&t zKPAQLk`l4}m5x|Jb@X^sT%M%Z^+<-YMI~OJBo#Wg3yU^+b&N0ERO{l&9IZszq7ru| zIUwC(#F14FIymT{2LVSJ>biq&b<1JeC9M{LZfBW%5*s{Dnz-mRpx$)Sh7X*yIklmh z?Yd8%*kK9OIz)ND+yQe9wBmaWw0cwWnnvmmOFGzCRxS-W%}Je6Wx>jHOke$T{fYPW zCpPpaWBQZp;V9?X;5;$T6CdCl@K&RVP4ly!O@1$wyTt4OC#@9PiAsmhhgw$_&g*oU zQNQM*yLr<^5B@8c302)h^_Zl=CYpUw(w{Zat4M05THZ|S+nTG`Qwu48cm%l7oPp6d zn`zId&Gbw{3ssw>g|7Zy3%#-5OOec0THhe)lBBm<>CPUs(%v*T&05{mhuuat zZy_1}=r&X?GP3cBJFa@o8awFNR2Re*a{6phwy4DIymS%GaMx|*TNrpDt4-8)G3eV~ zq6}8uxqLs1o`umik$;@QC|qmX$B!`x!99d9gK_w4n^-czAi`FPDh;dEvAu_Gk3~7x z2Iq=#uJ|OYm;p<>NjuA6VSBI<9=3~tMHaOw6DQJ3PpoKDZT$S()vlC|)Vf`H;Qv;- zS*35Ml1}2gS#%iOr_B5agK>BUVS>RtyoGR{fdvk9n0aeT;ua z-$s15XF`ET%MY>4DEt_E#u&_8^_clt=4;@~9wT3?l2&%XX^+S^F=&QmENfxlg?BtA zzKcO0e1@5R24}$Bna>9qjDov!pK^>rNCm&Pn@Yos=2VH#lX2A=D>@RbaYwnf4X!Q5 zwZ+e|l6lzdG-}T?u%sr=QU;e=Mc$g4IQwBQZi;~yLf*pCE(U$9KB}yEO^kiX8EqYk za{dj@ALIP-epcd#=e_&*0E1z8*K6QM7>vT7yk_kfgPHBgrT9%JDrZ@=Mh(6$17E9_ z_R#=OcA5D`2B+W-LI;CB_;Z)Jq@O`Zoj7#fiZv!w-qu7p`vzx^arStaRg8n(XXGas zEWiZ9A_E&-N3d(8c|GtWpNa2fFbIG5nUzBfMm6}^H}PYP=3uzntUbq|E=`*L3*G>~ z>=yZY_9f+l_q)w}69X^sJz_~010Sp3Q0i6{vBJtb>L_R1;A}C@7Vl;iJ=6e|o z!*Wj^Kf>S)yo#A1g9to8m}Ia3`Mr231~#zv8dL1)^iWHxM;@5y&Et~szuA8P5D07= delta 2922 zcmY+FdrVvR703DEYq+*C#xc)udD!8Gr!R>T+R=s(0@xT#c)tv3pkOEvY_NgwNV+h2(5o3Lq3nIEJ4jJstOP7pf$3O@9fB*dGAo1kVr9c&-if`Z zsP{DXmV;=LSq1fVnj}^UQ^*c@6V*xXmPvwDlLF7iNz6sgix^c?^btli@Ohlf+{C43 ziPb`vSvDNmp6d$Dbrt8TgCCk@Ru3OyzF|B6I_2NMd?StF+##_h_)4;Bm(Xh_@0aMc zP{b?}{9%UzH5Q4rk@H=PWN6>k)nV7E_Gego zlq&9H<~fQA6YvBR@B|Yi)=kbTMm_LJf{Q&*+?FV@UW(pIlpvU>z(aJrLO z7!APdNiutZ`0q&)yq}~%ak9jQ$T^GAFf1jz*l~D}EVB`aOK~wDiOo_pCP{|5?Qu7$ZVT&9s<&tQ2S%s1v;S1HrbpqWD(|Ap35<3H zj9m6I@r4Yl@%)oH7FeGS7BW(_dYt7I$_trR_9{h<7+rwH%-pi?e68O6rih%R^A~^0J@dkp02L^rP@MxD4 z&h3hWnyfU9%EMWC^jyrUP@l_pvp$YAnkT)!KL3-J)$F3EWO9Jz#!E(?JT@`f*E8;& z;hAvl@}3-9v?a+O80hg0&h(5A`hDSf`wN}M^soDHeUAk`EKIO`!{yb>|0*mLmb>?V zU&}5nM?Lc>a#>e$L9qPM)U zsEc?^flJU|KA?|g5fS(s5uAnF<$d@Ca-bp`wXY%%^-@JH>Pkhi)hU`HHz^){)| zSzw~Erx*>sA*R8f+U0}OjNHSKVK;TLC6rclJFVpRX(j7vrJ=OaHAmZ~CQ=)<6@l_d zmCf==v-=| z#AQW2=29EZyVT}ilYU8>U#&Vl)oS)tir=UBA!$NQK{O58Ex@!x*7OKY7_G5$uTOw$ z4!h1PP=$|3J z`f;t=_McjH1NI{{5b5`hs0&>s{em>9PIb!a)Cv4`>Q*n4bF)tE+9J)aSL37g>as4> ztM$Ji=W~h^8`Qaq8*=&iIpA`GO?2W9z9sbp4IDghvq2U+IdsEcv8;!~aoEu)iz6I- zP};a#^m7=65ri=g0eG!Z&I@uF7wL6a(^;2_Lc5;6F|sDO*9CV-aBoiViYa)|Xg5!D zIK|61L@VSp$>!7Ct-hrOq(Nl5Bc%qKV; zGKDYCN|#;6zXhw|Y^NgDaA<{HOxXYGP8K>@P-muji7ZLl+y#9`H Vk17XLd1_vNMnA6)Xz@SB{{c(9Sls{s diff --git a/inventory/__pycache__/utils.cpython-311.pyc b/inventory/__pycache__/utils.cpython-311.pyc index 6aa2a8a8b53fe6329752558fa1f05525d522d14a..24d1001ec3ce3142de9372b3ae3bdb9e2b119a35 100644 GIT binary patch delta 8646 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 t>Dqlf55JGBz0(whZ~K|R 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 OYG9$<{$Qw1>VE+BBcvMu diff --git a/inventory/__pycache__/views.cpython-311.pyc b/inventory/__pycache__/views.cpython-311.pyc index e52deaf812a26310db20fd31ddab39642fcd9e43..04480929bd33fe9678b1b8abe2620c58fbd9d5e9 100644 GIT binary patch delta 49611 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 N(_cM8Pg+42{|!95#Yq4F delta 44109 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 VL^eiO_E`dQ@jhi&sEah_{{?y}KrH|O diff --git a/inventory/mixins.py b/inventory/mixins.py index b7762382..966a8666 100644 --- a/inventory/mixins.py +++ b/inventory/mixins.py @@ -2,17 +2,16 @@ from django import forms from django.utils.translation import get_language - class AddClassMixin: """ - Mixin for adding classes to form fields and wrapping them in a div with class 'form-floating'. + Mixin for adding classes to a model. """ def add_class_to_fields(self): """ - Adds the class to the fields of the form and wraps them in a div with class 'form-floating'. + Adds the class to the fields of the model. + :return: class names form-control or form-select """ for field_name, field in self.fields.items(): - # Add classes to the field if isinstance(field.widget, forms.Select): existing_classes = field.widget.attrs.get('class', '') field.widget.attrs['class'] = f"{existing_classes} form-select form-select-sm".strip() @@ -20,23 +19,6 @@ class AddClassMixin: existing_classes = field.widget.attrs.get('class', '') field.widget.attrs['class'] = f"{existing_classes} form-control form-control-sm".strip() - # Wrap the field in a div with class 'form-floating' - field.widget.attrs['wrapper_class'] = 'form-floating' - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.add_class_to_fields() - - def __getitem__(self, name): - """ - Overrides the __getitem__ method to wrap the field in a div with class 'form-floating'. - """ - field = super().__getitem__(name) - wrapper_class = field.field.widget.attrs.pop('wrapper_class', None) - if wrapper_class: - field = forms.utils.safety.mark_safe(f'

{field}
') - return field - class LocalizedNameMixin: """ diff --git a/inventory/signals.py b/inventory/signals.py index 4422d3ae..f550dff8 100644 --- a/inventory/signals.py +++ b/inventory/signals.py @@ -689,7 +689,8 @@ def create_item_model(sender, instance, created, **kwargs): item_type=ItemModel.ITEM_TYPE_MATERIAL, uom_model=uom, coa_model=coa, - additional_info={} + + # additional_info={} ) product = entity.get_items_all().filter(name=instance.vin).first() diff --git a/inventory/utils.py b/inventory/utils.py index 2abf4681..c0b28e3b 100644 --- a/inventory/utils.py +++ b/inventory/utils.py @@ -481,4 +481,6 @@ def to_dict(obj): obj_dict[key] = str(value) else: obj_dict[key] = str(value) - return obj_dict \ No newline at end of file + return obj_dict + + diff --git a/requirements.txt b/requirements.txt index ae0ef94c..4deb40fc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ aiohappyeyeballs==2.4.4 aiohttp==3.11.11 -aiohttp-retry==2.8.3 +aiohttp-retry==2.9.1 aiosignal==1.3.2 alabaster==1.0.0 albucore==0.0.23 -albumentations==2.0.0 +albumentations==2.0.1 annotated-types==0.7.0 anyio==4.8.0 arabic-reshaper==3.0.0 @@ -33,7 +33,7 @@ ctranslate2==4.5.0 cycler==0.12.1 Cython==3.0.11 decorator==5.1.1 -desert==2020.11.18 +desert==2022.9.22 dill==0.3.9 distro==1.9.0 dj-rest-auth==7.0.1 @@ -53,13 +53,17 @@ django-filter==24.3 django-formtools==2.5.1 django-ledger==0.7.3 django-money==3.5.3 +django-next-url-mixin==0.4.0 django-nine==0.2.7 django-nonefield==0.4 +django-ordered-model==3.7.4 django-phonenumber-field==8.0.0 django-picklefield==3.2 +django-plans==1.2.0 django-prometheus==2.3.1 django-q2==1.7.6 django-sekizai==4.1.0 +django-sequences==3.0 django-silk==5.3.2 django-sms==0.7.0 django-sslserver==0.22 @@ -72,12 +76,13 @@ djangorestframework_simplejwt==5.4.0 djangoviz==0.1.1 docutils==0.21.2 easy-thumbnails==2.10 +emoji==2.14.1 et_xmlfile==2.0.0 -Faker==33.3.1 -filelock==3.16.1 +Faker==35.0.0 +filelock==3.17.0 fire==0.7.0 Flask==3.1.0 -fonttools==4.55.3 +fonttools==4.55.6 frozenlist==1.5.0 fsspec==2024.12.0 gprof2dot==2024.6.6 @@ -85,11 +90,11 @@ graphqlclient==0.2.4 greenlet==3.1.1 h11==0.14.0 h2==4.1.0 -hpack==4.0.0 +hpack==4.1.0 hstspreload==2025.1.1 httpcore==1.0.7 httpx==0.28.1 -hyperframe==6.0.1 +hyperframe==6.1.0 idna==3.10 imageio==2.37.0 imagesize==1.4.1 @@ -110,7 +115,7 @@ lxml==5.3.0 Markdown==3.7 markdown-it-py==3.0.0 MarkupSafe==3.0.2 -marshmallow==3.25.1 +marshmallow==3.26.0 matplotlib==3.10.0 mccabe==0.7.0 mdurl==0.1.2 @@ -121,10 +126,10 @@ mypy-extensions==1.0.0 networkx==3.4.2 newrelic==10.4.0 nltk==3.9.1 -libquadmath==2.2.2 +numpy==2.2.2 oauthlib==3.2.2 ofxtools==0.9.5 -openai==1.59.8 +openai==1.60.0 opencv-contrib-python==4.11.0.86 opencv-python==4.11.0.86 opencv-python-headless==4.11.0.86 @@ -135,8 +140,8 @@ packaging==24.2 pandas==2.2.3 pango==0.0.1 pdfkit==1.0.0 -phonenumbers==8.13.53 -pillow==11.1.0 +phonenumbers==8.13.42 +pillow==10.4.0 platformdirs==4.3.6 prometheus_client==0.21.1 propcache==0.2.1 @@ -164,7 +169,7 @@ pyobjc-framework-Cocoa==11.0 pyobjc-framework-Quartz==11.0 pyparsing==3.2.1 pyperclip==1.9.0 -pyphen==0.17.0 +pyphen==0.17.2 pypng==0.20220715.0 PyRect==0.2.0 PyScreeze==1.0.1 @@ -174,6 +179,7 @@ python-bidi==0.6.3 python-dateutil==2.9.0.post0 python-docx==1.1.2 python-openid==2.2.5 +python-stdnum==1.20 python3-saml==1.16.0 pytweening==1.2.0 pytz==2024.2 @@ -194,11 +200,12 @@ rich==13.9.4 rubicon-objc==0.5.0 sacremoses==0.1.1 scikit-image==0.25.0 -libomp runtime library==1.6.1 -libquadmath==1.15.1 -selenium==4.27.1 +scikit-learn==1.6.1 +scipy==1.15.1 +selenium==4.28.1 sentencepiece==0.2.0 shapely==2.0.6 +simsimd==6.2.1 six==1.17.0 sniffio==1.3.1 snowballstemmer==2.2.0 @@ -207,8 +214,11 @@ soupsieve==2.6 SQLAlchemy==2.0.37 sqlparse==0.5.3 stanza==1.10.1 +stringzilla==3.11.3 +suds==1.2.0 +swapper==1.3.0 sympy==1.13.1 -tablib==3.7.0 +tablib==3.8.0 termcolor==2.5.0 threadpoolctl==3.5.0 tifffile==2025.1.10 @@ -223,7 +233,7 @@ trio-websocket==0.11.1 twilio==9.4.3 typing-inspect==0.9.0 typing_extensions==4.12.2 -tzdata==2024.2 +tzdata==2025.1 Unidecode==1.3.8 upgrade-requirements==1.7.0 urllib3==2.3.0 diff --git a/static/css/appt-common.css b/static/css/appt-common.css new file mode 100644 index 00000000..c0a616fc --- /dev/null +++ b/static/css/appt-common.css @@ -0,0 +1,452 @@ +.djangoAppt_main-container { + + max-width: 1200px; + + background-color: rgba(248, 249, 250, 0.4); + border-radius: 5px; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.1); +} + +.djangoAppt_body-container { + margin: 0 auto; + max-width: 1120px; + padding: 0 15px; +} + +.djangoAppt_page-body { + display: flex; + flex-direction: row; + margin-top: 50px; +} + +.djangoAppt_appointment-calendar { + flex: 3; + padding: 20px; + + border-radius: 5px; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.1); +} + +.djangoAppt_service-description { + flex: 1; + margin-left: 20px; + padding: 20px; + background-color: #fff; + border-radius: 5px; + box-shadow: 0 0 2px rgba(0, 0, 0, 0.1); +} + +.djangoAppt_second-part { + margin-top: 10px; +} + +.djangoAppt_calendar-and-slot { + margin-top: 20px; + display: flex; +} + +.djangoAppt_service-description-content { + margin-top: 20px; + color: black; +} + +.djangoAppt_item-name { + color: black; + font-weight: bold; + font-size: 20px; + margin-bottom: 10px; +} + +.djangoAppt_calendar { + flex: 3; +} + +.djangoAppt_slot { + flex: 2; +} + +.djangoAppt_appointment-calendar-title-timezone { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.djangoAppt_title { + flex: 1; + font-weight: bold; + font-size: 20px; +} + +.djangoAppt_timezone-details { + flex: 1; + text-align: right; + font-size: 16px; + color: #333; +} + +.fc-day { + font-size: 12px; +} + +.fc-daygrid-day-frame { + height: 20px; +} + +a { + color: #0c042c; +} + +.djangoAppt_slot { + margin-left: 20px; +} + +.djangoAppt_slot-list { + columns: 2; + -webkit-columns: 2; + -moz-columns: 2; + margin-top: 10px; + padding-left: 10px !important; +} + +#slot-list li { + list-style-type: none; + text-align: center; +} + +.djangoAppt_appointment-slot { + border: 1px solid #ccc; + background-color: rgba(0, 48, 124, 0.95); + color: #fff; + padding: 7px; + margin-bottom: 6px; + cursor: pointer; + border-radius: 4px; +} + +.djangoAppt_appointment-slot:hover { + background-color: #fff; + color: rgba(42, 42, 42, 0.82); +} + +.selected { + background-color: #fff; + color: rgba(42, 42, 42, 0.82); +} + +.djangoAppt_next-available-date { + font-size: 16px; + font-weight: bold; + color: #333; + margin-top: 10px; + margin-left: 10px; + padding: 8px; + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 5px; + width: fit-content; +} + +/* Change the color of the buttons for the calendar */ +.fc-button { + background-color: rgba(0, 48, 124, 0.95) !important; + border-color: rgba(2, 76, 157, 0.85) !important; + color: #fff !important; +} + +/* Change the color of the buttons when hovered */ +.fc-button:hover { + background-color: #025bbb !important; + border-color: #0056b3; +} + +/* Change the color of the buttons when active or focused */ +.fc-button:active, .fc-button:focus { + background-color: #0759b2 !important; + border-color: #145294; +} + +.djangoAppt_date_chosen { + margin-left: 5px; + padding-left: 5px; + font-size: 18px; + color: #333; + font-weight: bold; +} + +.djangoAppt_btn-request-next-slot { + margin-left: 10px; + padding: 8px !important; + margin-top: -30px; +} + +.djangoAppt_no-availability-text { + margin-left: 5px; + padding-left: 5px; + color: #f00; + font-weight: bold; +} + +.disabled-day { + background-color: #ECECEC; /* light gray */ + opacity: 0.5; + pointer-events: none; /* makes it unclickable */ +} + +/* responsive */ + +/* CSS for screens larger than 1200px */ +@media (min-width: 1200px) { + .djangoAppt_page-body { + flex-direction: row; + } + + .djangoAppt_appointment-calendar { + flex: 3; + padding: 20px; + } + + .djangoAppt_service-description { + flex: 1; + margin-left: 20px; + } + + .djangoAppt_calendar { + flex: 3; + } + + .djangoAppt_slot { + flex: 2; + } + + .djangoAppt_slot { + margin-left: 20px; + } +} + +/* Select design */ + +#staff_id { + width: 100%; + padding: 2px 4px; + border: 1px solid rgba(255, 227, 227, 0.89); + border-radius: 5px; + background-color: #d4eaf5; + color: #333; + appearance: none; /* Remove default arrow icon in some browsers */ + -webkit-appearance: none; /* For Webkit browsers */ + -moz-appearance: none; /* For Firefox */ + cursor: pointer; + outline: none; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); + transition: background-color 0.3s; + position: relative; + font-size: 16px; +} + +#staff_id:hover { + background-color: #f6c6c6; +} + +#staff_id:focus { + background-color: #d1dbff; +} + +/* Add a custom arrow icon using pseudo-elements */ +#staff_id::-ms-expand { + display: none; +} + +#staff_id::after { + content: '\25BC'; /* Unicode arrow character */ + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + pointer-events: none; /* Make sure clicks pass through */ + color: #333; +} + +/* Styling the options when hovered */ +#staff_id option:hover { + background-color: #f0f0f0; + color: #333; +} + +/* Styling the options when they are active (clicked or selected with keyboard) */ +#staff_id option:active, #staff_id option:checked { + background-color: #e0e0e0; + color: #333; +} + +/* This changes the background color of the option elements when the select is open */ +#staff_id option { + background-color: #f8f8f8; + color: #333; +} + +/* For Firefox - to change the background color of the dropdown */ +#staff_id:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #333; +} + + +/* CSS for screens smaller than 1200px */ +@media (max-width: 1199px) { + .djangoAppt_page-body { + flex-direction: column; + } + + .djangoAppt_appointment-calendar { + flex: 1; + padding: 10px; + } + + .djangoAppt_service-description { + flex: 1; + margin-left: 0; + margin-top: 20px; + } + + .djangoAppt_calendar { + flex: 1; + } + + .djangoAppt_slot { + flex: 1; + } + + .djangoAppt_slot { + margin-left: 10px; + } +} + +/* CSS for screens smaller or equal to 768px */ +@media (max-width: 768px) { + .djangoAppt_main-container { + padding: 8px; + } + + .djangoAppt_body-container { + padding: 8px; + } + + .djangoAppt_appointment-calendar { + flex: 1; + padding: 10px; + } + + .djangoAppt_service-description { + flex: 1; + margin-left: 0; + margin-top: 20px; + } + + .djangoAppt_calendar-and-slot { + display: grid; + } + + .djangoAppt_slot { + margin-top: 40px; + } + + .djangoAppt_slot-list, .djangoAppt_date_chosen, .djangoAppt_no-availability-text { + margin-left: 0; + padding-left: 0; + } + + .djangoAppt_btn-request-next-slot { + margin-left: 0; + } + + /* Reduce font size for smaller screens */ + .djangoAppt_title, .djangoAppt_item-name, .djangoAppt_date_chosen, .djangoAppt_next-available-date { + font-size: 16px; + } + + .djangoAppt_timezone-details { + font-size: 14px; + } + + .fc-day { + font-size: 10px; + } +} + +/* CSS for screens smaller or equal to 768px */ +@media (max-width: 450px) { + .djangoAppt_main-container { + padding: 3px; + } + + .djangoAppt_body-container { + padding: 3px; + } + + .djangoAppt_appointment-calendar { + flex: 1; + padding: 5px; + } + + .djangoAppt_service-description { + flex: 1; + margin-left: 0; + margin-top: 20px; + } + + .djangoAppt_calendar-and-slot { + display: grid; + } + + .djangoAppt_slot { + margin-top: 40px; + } + + .djangoAppt_slot-list, .djangoAppt_date_chosen, .djangoAppt_no-availability-text { + margin-left: 0 !important; + padding-left: 0 !important; + } + + .djangoAppt_btn-request-next-slot { + margin-left: 0; + } + + /* Reduce font size for smaller screens */ + .djangoAppt_title, .djangoAppt_item-name, .djangoAppt_date_chosen, .djangoAppt_next-available-date { + font-size: 13px; + } + + .djangoAppt_timezone-details, .error-message { + font-size: 13px; + } + + .fc-day { + font-size: 11px; + } + + .fc-toolbar-title { + font-size: 14px !important; + } + + .fc { + font-size: 13px !important; + } + + .fc, .fc-button { + vertical-align: center !important; + } + + .djangoAppt_appointment-slot { + padding: 5px; + font-size: 13px; + } + + .djangoAppt_service-description { + font-size: 13px !important; + } +} + +.selected-cell { + background-color: #aaddff; /* or any color you prefer */ +} \ No newline at end of file diff --git a/static/images/logos/vendors/Aljumaih-Automotive-logo_kM7B61x_AROA7K7.png b/static/images/logos/vendors/Aljumaih-Automotive-logo_kM7B61x_AROA7K7.png new file mode 100644 index 0000000000000000000000000000000000000000..04ee0afab90cd8e708128ceef09a5fecb64efad6 GIT binary patch literal 10227 zcmd6NcTm$^xMu8pDq^ToBodl{^d{X8LvPYT2Px8f57h!1x}by(3Wgp)A@rg^=tTsA zv>+`Ak(SUxNtW-Yc;3VKX2ACR^OvutMF0SBgW;cc0g#=02LRwlYC--s3C-P{w@TsZ zhjISePJj=|2O5B!om~#TT5QFw5bpyS1;%-JZ2Df>zkTi6hfe~qi~RyE-Jo7&fBS6V zC(CQvflmTov9WqDF8}qU*wa6jH^cfz#q_0+VCQ3N(=1kCf#?@OAxWW%=JFPg=E99g zkuZPaZB`2#a1i6?>P0w6}qCgchLAaV#Bk%!Nl^Z`%WIi$U%HF5mqBt7|C8 z?3a6}DAsud2F^0?3t;Y?t8pH0#iLlP3qtGSq$Xgkb)RKO+s_K0H&I-LZKDi7U#ZV+ zt9mjn8sIOT+Jxm!%kqdjf?Y3Td1&am*^ou$WRJ!eZ^%%y$)5}Ch6_b3g&e_1k>f23 ztqU5NG_+S=4b4dD<%Qr94!c6^_vh|VOM+ic>VI?)Fj)BH7r7*`JM|=eAUfeNO^gAU zKqEOd?)xQk_tcWsCFZ3=zAi~%Rt6ME_8cnm^+)%>D>?f@b9VnS53^w#9RS<$Wgv<) zoS^ht0*nd%?3ON7+#i-X#q1(%Kao$+hPyOtfzAwvrGmtGZR+1Fe^XGSi{j z0?)wX@k_xik&7L%72JI^B%F%{)*#Kcx`0P3t=L3`y_a8a`E)GW)GeFMEa&H^y3)O= z%4~)4Vgfbq<}i^jT?sl>ZIB|<@4=&&(8zg@fhxKQO`;kvIJWZ`#QVRd8?~Ze>c2c> zw>CpwsIj$Zf|<%o>bzJb=?Y3e)#h4_JmU~?G2S=HAl8G^B}hEP9~??*OSW@Lv0(%q*Q>}JVtJfE+r*7#ej%l0a)u&8lvCwq<}ztT3^B) zOXj_8LXaZhPc*eyPR_d^&bvs9o`{j&+ki}sG(I;(*tSpE+5Pxe3KcKo#5^_^cXzvb zQ&0hdjTx*@V`1AV$p-KvEX9_icgiLYbD4CZQRR+eSj1cB=^(SqPiZYY=u(nL!p;a) zeVv`~8Y#VV`{P$D%|aJz>LoltAdcMJe47w8W(M_`y9K$N1-aHC!T{`?MMrdC1P3Kh%I%r^hik7`%)dyK8Gq&8JJCPMp6^DU| z5^VlPjYr3cFhz=;A!%u#&Ot<7=>Z7ZuDX7OnXf8ozC08DlPSlnT&Qc_v{yN|RG0h| zDKE~4%qV$yIC z?CrhVQ}7mSCxUq0wb`=El^pLcF=HMRe?2KGZ}ol%?W=C@x};<=WFvzISbnoaNhgj0 z?Cz$YI}tTvnw#8@{FKux828493hN+C26tf8?eYNb$XADIK%UQ+*St z25X6v*7FcU;KS@xAqy(5P|Jn|-NhG9n%4y^T?saHwCPG^A3hA; z>n)h2en-?T9bIIyl4NJIhd80m$VJ~8qJxizsV7@!bYYA(Qdi;!*;eJaB86zD$|Ed1 z^Ba=(TV_$BZGk%m$|rj)+lE4Q!0Xj>&T5%(SCmyY=l)qkRM@E<<+!bZF72AeC8Fx| zb*ezl{H+(JJKdZkzgK@tkw-*tEktCVF1q9&7cDfN{iz>;OzX9!gw}#}J%m*c>E8LA z4lfp*9la_z+Yo0}Q#aNbJ(Mzi&h%^j4-a|z)i^cN8QZTy8KTVR)r&hz8hh4)E0J%= z`}_;hCu1irbxgD@_d4#~i@%R$B%JsZoSqD^58Iur;M&@-d{=*}2YC&5Y*Q;v_@jwu z4WPRv6YUdf9?uC=*p%e)=)+eb9cQFkjmYT!ZTpL8AP}#X39ELbEV1xMA9r0#;XLRI z&n|j%;j6Y}d~5X1?>{PMlP%HYgx);!Hz$zU8?*3BBMJ$e2b&F1L`n)S6yo(*d$EiA zvH?7?(46`wBZYcea_x|gVQsD$`BR+(g>80OZ}G`H+ju$=?#rFbF7p~+p!_>}8U3p3 zy(RmBn?JYyY93;Pclw|CMsX_P{&rE96vb1+2!`9D3yQwj`ox*i*%TRYCp}?7MXQ5~ zU7yxcb?%~Qe&hQ-kXKyY5psGEr&DQ0nx~*Su_~RJWT&_DD!L=|I(2_(++^>+Pgr>q z9qvyRYZ{_;`DQ_VJZTBKb$aILGS+(+Yw3O#UPAF(P;c#Q$hJoBr_`ADFSoZvue7pQ z)4W0>e}=)9&&IF0*yhk~d}*fu)gf8?T0dI0ECCPR8QHGny&4~na-czp0iNH!*9pMB zxwWzQ*zDqe1}T!a6Jr)NVxXl-&yp7vwY2{Cwk4 zjBw|%zCzgIfj~<(~a^yYJ{H(^+$(%~w zs-i55DxS>I2Fw^hq+7#M@k3g>0Uwu|%D%dRizEq!tBJqv2ylT7Zzu|cQMhK4pjh(K zTHR!PkaGsU%}kptS2#T5Il?xAn1)H}pIVQq4dyETCi_rVrc~s^ba$^NK|^_O+b8(? zb7MP5OCuWFfd=7mg0%Bpn38ldn?kYtAG!cz!!D{!?n-hxVo|GNaMHE%ha!`sV;>Xy z@z}tC!b080EG17MJ6K|cBc;KV0rWdwV!%|GF55c7_7It6$pN(Z!9#mNTu#v1k7l5y z)pzW*HNH4f`Jf6)3#J!^tf90TuaeJ{=6GT2$}T-W!*Cx=g2R>Q%b|#1A1WODP$z@P7U6d z;euc@lTh5UtU%e`N*JX=S-?z^4d@}=#NJP&^t&X|fAL-lZh*?M;bXg`jJj=nugGG>gy8E&t*M-==&{s;@~ zZDde0(Q-G{TC`ZJuSjf`Y=>C|tX}amzB-)6;iX!ef|ju)Z#lP=@MVNxfW^4RxRGZk#1__|5d_rwn;*Pnq5lqTe?Ll{XTy)T0Ss zR(Q>7{Ip@EfXva>p}fU{o5no!)6j%$L?zlVmsRbCE!wmAj@fR(&0O>U_w-Bgn=85X z?O}l8JNE4R`-4rYH}QtFjg|dPPQxRys@1?J)#rFmW)-EkW`g|mM`A5@J&ncn=B;ujj?M3AJs91X1LcK0PGmbkJY1_# zLXE7OjNN(JrDiN&tR)c5Z)^}ip8YY#S9HgvxVJ6PG6W^mT}kh;AcsDaB1{k10ew&s zJ+pFQKU*p@b(NGFW}DIT!JKv_B6rW@T;fs)-AOcwfcrWUlyI|RvHEfAvuDJDacFYl zNTtUqOgddQ6`du%yysL9pDhay7-tLYve8|~Lm#fYoI@g26OU<|&(KetoFyu!lSQDm zpdHt*_|`}ZQT5$KtJIM&m*}JEHOiRF41d4r6cgXS#FzTnu@v0h)0}M_b>A;+%O>cG zr2CjFqC0KOGcU90WMNIn!28P+u8K#s4;O2@>Gjq#!qvctt`{e}3MJHi^qcLwx0VJM z@R5k9KUr;CYXwSL->+NA|Fd%z2l#7kMMW2$wUddHO^5~YNF=M;=ctQz3U_&lxWy0D z#wj1x!i{S)vGOyL<61uqepFP0rT&f)p4Yc3;Ok*+I)XjiYL1dNeW{h2=B;z+sJFYS z92Z__F4PlpW$D+Ye=nmF-SgGNe5tQ7TK z?{als{0LVBativ1KZqyCbJqM-aRtbH(V)d@tTsGv?Lhvsf7ukoMAtgK|4op`0LO6H z+oL&NyJ?cpd@QHdTI;s@DRF*&9sz#S^V9P0nAoq)R*| zHfA#-eZppE(LU8MYo9zilN->m&mquHbVE9B8zEUj=MI}v@Jff|Z)DCk9gp;~ema*1 z_Juj`X8|hj?!0_XbEC_)xAk~vYdd-mpH$Ck&IQKrl2ur*%fsK>4Ms4+3xusn7w~sP?->k1f3)Qsl# zCnd>fuoNzfUsOMr$G~R7G2JZhMwq;W?5?xnTZJW07fOdtRlx6inHh{x&w5~JB9pdT z7jm(lex&aXJ_s z7#Z0auzoVIWchKJ8r12A_f_*8zSsG5Ka@3Sur!)mpvZ?Irm=Bekg9H^8F#o^aZ-dV zGHTO^LV+N6Dw&;+CyQ{h)X|m&*z(_LH#H|u25djPYA+sgC4Fw0rS2sh+C~QZyqys$ ze_*Y0J|I`4q(1}~x|6g`i9a50+kAs>jDFe|wgCY}ld&qNLpENF$c$?4%Ip8wYF3=- z*P$2WBsX3*=uMh0tV0^=9iQ#Iwv#X#{A#Fx7eTMH zrY!AGe6jG(Xv#%fVp}->UX%B2`x^g5Xh_Jcs1iEpyO&y6K3x6{!$Q)8q(UgoEL*1! zBXnfg`*c=x-kmiPVOca1C#KNb)z(%!uc=fUy$qG*TdOL~uAx7l#}e}UAo#Qe6uuDv z!j0xm#mDuf`)k8=AhuZ%g>TdNz^iF)yzE}y3$BO89hK!>L62g-4Y;WtuWOEvkUj9< zdQ3jiGwAs^$xRuwkvh$GrWv7Ez&D}EniOMoR_WZw5b(0h`%o@0SDE+2(`u0HoO%ER zPD4gJe=qy*VPx@@lV)9Lb`dutor<_PFpnBEf7$e`5>rH#iMqUO7!(@TM)WXMjfXcL zd1?+}*1C+w(zBM&-IZKYcG1Yj+{8-#ilDP7OQ_OQBv+@ZL!Cr?rICreT3jhMB;GV& zIXfMSiRE$t3sQtC6Nro%qX8(BF1OLja;or4PxEfy7cPZ`C_BBL9zjI?Gq8hFHYesS zzu<;sD>F2P(Rkls+E;kAXmYwp12c0TLmu%2%YV7+7$3C)!D4gVNqBSq>S*L2{nRl;K^~2b_1da1GY;x2;=p37L&P zT+JEXZZJxqu>!$sZr|D8GNg`1NgUxlQV_<9R-&r~gFgou#5QlY_5}|{P-1gLRVHEN zEvWlAp`6QF4q|?fLvDM{zmqrTNXtHIqMSoDE1g?N z=uNU!c_{R6Fu;GMpMUJm*b4nM_tglmC3)q<_XEjhIW_O3yK!j0ZI51&;z)ZAorHdz z{;}ReSV0QQYHE(vM1wyrRmCWX%r=`Jscaw#VkeQ}O?5L}O zjU%D>GgK{75yZnnUQxrIq>*@J$hvK2;#{}YfgzaZIJag048>Vb_!@e-FF;ir; zWCjmo(8y+9<;_6+jJw#YAS|7*5&i(A`I(vFM!#>!5IXVKkCL$s;QM;eurQku*vQG) ztyr|4MHIq=JWY$Z3Ivk==9dWj=sKsMDVYh&~cVy5(mdBW}$qv@^Ja&~TM;1G{vr1&&WOI0Nj*X_g{|I2~CY!h7zru)k z7NU?8yoUb@=m`J;;Aj31w*9|45w^q8ze-ZYEwfhSsKQ%Acc*Mp^j^?diD2TvC`VLg zb&JRjPbe^B>0W2i89T2O^FaV18>@Gh4SDyhrlP9T%^2QHz+={<4FlZns7I0iCt1 z_$=sIhBe0E?74GnUz6I6x)G7~e!1{s(FVk9ygO?UsYSv7-ZQBNQjFsJS*#Y>aMH1@HT8!Cc}B1 zcY61$L>1=)5ZE)z{2bZd;|3eWtHrn;ZH+F>VytpC;=byljlCQ7gCfC8Ml{ShYoI@$ zS}l=I&LcfK8&{G;ESvq!Y+vY9q?g3S#X2=AR24K0l}p)(RM(xkR%wcJsohL>PPXIN zRfQNG7ea`v5EJqfxomE^h8uRgHHQ*Q8sP5_|hpj!yMuoHjO7?Zfv zV$)uWoa1oJLM82|h^@_SqJA%jv~03+BQM5`Bp96zt@V%oAtEi@ajPJAUotrkA z*G%n8^ozT25s$e~nFvrgJg{U&)$9kB=!-AKZ-PzuJM!|;BmmR=2K!x|)`Ic$&^5~YzgNZ#R0uo0eUtvD_T%*yJIacU_FaqUCXLt&l>ojk`IXU& zxRDIorpSt-_eMYV=AKK}3tTct&{QGWQFgFy(Nxr%l0%i>hD%~GQZd4$?m(C7ll@U8 z(lyXD7q|nK5hh#8#0Q?uJ8IaPKPp;iJ?YF7HcIs2=X#nC|Bc(+GE*iEO({1WF4<0u z%Iwm%{e7MzXzRw|;@c_g4egXBmo249WR&_1Ws%!Yc^?{_d7jIE;RT_1yND?2mi`IP z{CDr!9OB;+Yq^p=bF;?(q;d3RDL7G1%BbwEV~tCN3CcvF#x2Un$-{mNv`5i6?yW8S ze)HJzrrCDHdTgG`(a|Q0cS9~ZXZmuHUV@l&Jyssi;_Y)*+lDRi&PiVEh-%2N2+4ZW zXUl~?8m}+NWK9vV`4= zKuLx%Hx=GOgSkygMf6Um0cdX<#K#wpfABFf$mSZl>C4o@?;BCl_Itb=Vl!n~3RKwc zHRxbOMk(rcQ>?<7g{%+ga!cr9tPZa3;r*cff!x*z$>Tnh3^1B_8j!5} zh_K=nw13rcd3=!G;Z0_Z&DMnJlF6Xcy{|?%<_@?gqX_w=SJPR}pRbrTftOA?BQXkK2V_}T6_iC9o;09XwxLy-L!V@>@#g_6_=oNTY+4zzH?dgH@oKsN z%H8LZ^s{%wf-q_BC_xs#Vm;CF(}6@=qlKNBW5b{6%BD#vRWoZ}zwa#Do`vQeN&(xE zJsD~uzYxE#J6+)E-VY4aAnvI4UCdt0KTj3S|4i(sGHvbtwp+N zy!Y`=7#5NaWCeb{iyDQ92zlv16x~Bs*17Z&h-rg5uc@HCETmfd`zNdF?v z-$L&Vp(EIz*}+H=YsgKFfP@1CoUz|DTP_*!&kLt53lx;uUACKZO`gD3{z3kPelp@q zxSc9G$_OmT=YPX~uY)E`>rGR{_;5qF!U0pOosl=IG36SWZ(UIYJHHEG)5LlW z8NahOYygYjZv@Lblk$_V`3k>ZdYBAb^=8d;pXAo{s6|gERc)zr05Z8Gif7k{8qtoE zm0>aB#^>c44VF_jz~@Qd=t8;a*YetvnewA9GGVm1jZ)eS&Y&3mIQQNALvvA69Z zfsWxGF5xH59v5ox^(|Hkvfk$f7XMURoSV$&reow2aMIAu6mt45HiOSpDe0WQ=Jz?8<)m9sQ4vutX|>mTT~zxedkzD+VuI$|_j4quF&Mfj zPJUT}QhOGybSk7rMZFZ{iV2~3U&HZVRYg>7*jek>hlXHOU~gllrCQT!LR1XM_PUm1wATEbdRa#hY_#n#2?#Ne_@={TI3Vzmlr{pU!na ay { + setTimeout(initializeCalendar, 50); + }); +}); + +const AppStateProxy = new Proxy(AppState, { + set(target, property, value) { + console.log(`Setting ${property} to ${value}`) + // Check if the property being changed is 'isCreating' + if (value === true) { + attachEventListeners(); // Attach event listeners if isCreating becomes true + // (didn't check if property is isCreating, since AppStateProxy is only set with it) + } + target[property] = value; // Set the property value + return true; // Indicate successful setting + } +}); + +function attachEventListeners() { + // Checks if the DOM is already loaded + if (document.readyState === "complete" || document.readyState === "interactive") { + // DOM is already ready, attach event listeners directly + attachEventListenersToDropdown(); + } else { + // If the DOM is not yet ready, then wait for the DOMContentLoaded event + document.addEventListener('DOMContentLoaded', function () { + attachEventListenersToDropdown(); + }); + } +} + +function attachEventListenersToDropdown() { + const staffDropdown = document.getElementById('staffSelect'); + if (staffDropdown && !staffDropdown.getAttribute('listener-attached')) { + staffDropdown.setAttribute('listener-attached', 'true'); + staffDropdown.addEventListener('change', async function () { + const selectedStaffId = this.value; + const servicesDropdown = document.getElementById('serviceSelect'); + const services = await fetchServicesForStaffMember(selectedStaffId); + updateServicesDropdown(servicesDropdown, services); + }); + } +} + + +function initializeCalendar() { + const formattedAppointments = formatAppointmentsForCalendar(appointments); + const calendarEl = document.getElementById('calendar'); + AppState.calendar = new FullCalendar.Calendar(calendarEl, getCalendarConfig(formattedAppointments)); + AppState.calendar.setOption('locale', locale); + AppState.calendar.render(); +} + +function formatAppointmentsForCalendar(appointments) { + return appointments.map(appointment => ({ + id: appointment.id, + title: appointment.service_name, + start: appointment.start_time, + end: appointment.end_time, + client_name: appointment.client_name, + backgroundColor: appointment.background_color, + })); +} + +function updateCalendarConfig() { + AppState.calendar.setOption('headerToolbar', getHeaderToolbarConfig()); + AppState.calendar.setOption('height', getCalendarHeight()); +} + +function mobileCheck() { + return window.innerWidth < Constants.MOBILE_WIDTH; +} + +function tabletCheck() { + return window.innerWidth < Constants.TABLET_WIDTH; +} + +function getEventDisplayedStyle() { + if (mobileCheck()) { + return "list-item"; + } + return "block"; +} + +function getCalendarConfig(events) { + return { + initialView: 'dayGridMonth', + headerToolbar: getHeaderToolbarConfig(), + navLinks: true, + editable: true, + dayMaxEvents: true, + height: getCalendarHeight(), + aspectRatio: 1.0, + themeSystem: 'bootstrap5', + nowIndicator: true, + bootstrapFontAwesome: { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right' + }, + defaultView: mobileCheck() ? "basicDay" : "dayGridMonth", + selectable: true, + events: events, + eventDisplay: getEventDisplayedStyle(), + timeZone: timezone, + eventClick: async function (info) { + AppState.eventIdSelected = info.event.id; + await showEventModal(info.event.id, false, false); + }, + dateClick: function (info) { + // Retrieve events for the clicked date + const dateEvents = appointments + .filter(event => moment(info.date).isSame(event.start_time, 'day')) + .sort((a, b) => new Date(a.start_time) - new Date(b.start_time)); + + // Display events in a list below the calendar + displayEventList(dateEvents, info.date); + }, + + selectAllow: function (info) { + }, + dayCellClassNames: function (info) { + const day = info.date.getDay(); + if (day === 0 || day === 6) { // 0 = Sunday, 6 = Saturday + return 'highlight-weekend'; + } + return ''; // Return empty string for regular days + }, + eventDrop: async function (info) { + await validateAndUpdateAppointmentDate(info.event, info.revert); + }, + eventDidMount: function (info) { + // If it is a mobile view, we change the event to a dot + if (mobileCheck()) { + // Find the fc-daygrid-event-dot class within the event element + // and change its style to display as a dot + const dotEl = info.el.querySelector('.fc-daygrid-event-dot') || document.createElement('span'); + dotEl.classList.add('fc-daygrid-event-dot'); + dotEl.style.borderRadius = '50%'; + dotEl.style.backgroundColor = info.event.backgroundColor; + + // Clear the inner HTML of the event element and append the dot + info.el.innerHTML = ''; + info.el.appendChild(dotEl); + } + }, + dayCellDidMount: function (dayCell) { + // Check if the day is in the past + const currentDate = new Date(); + currentDate.setHours(0, 0, 0, 0); // Reset time part to compare only dates + + if (dayCell.date >= currentDate && !tabletCheck()) { + // Attach right-click event listener only if the day is not in the past + if (AppState.isUserStaffAdmin) { + dayCell.el.addEventListener('contextmenu', function (event) { + event.preventDefault(); + handleCalendarRightClick(event, dayCell.date); + }); + } + } + }, + }; +} + +function displayEventList(events, date) { + let eventListHtml = '

' + eventsOnTxt + ' ' + moment(date).format('MMMM Do, YYYY') + '

'; + eventListHtml += '
'; + + events.forEach(function (event) { + eventListHtml += `
${event.service_name}
`; + eventListHtml += `
${moment(event.start_time).format('h:mm a')} - ${moment(event.end_time).format('h:mm a')}
`; + eventListHtml += '
'; + }); + + const date_obj = new Date(date.toISOString()) + + if (events.length === 0) { + eventListHtml += `
` + noEventTxt + `
`; + } + + eventListHtml += ``; + + const eventListContainer = document.getElementById('event-list-container'); + eventListContainer.innerHTML = eventListHtml; + + // Add click event listeners to each event item + const eventItems = eventListContainer.getElementsByClassName('event-list-item-appt'); + for (let item of eventItems) { + item.addEventListener('click', function () { + const eventId = this.getAttribute('data-event-id'); + AppState.eventIdSelected = eventId; + showEventModal(eventId, false, false).then(r => r); + }); + } +} + + +function getHeaderToolbarConfig() { + if (window.matchMedia('(max-width: 767px)').matches) { + // Mobile configuration + return { + left: 'title', right: 'prev,next,dayGridMonth,timeGridDay' + }; + } else if (window.matchMedia('(max-width: 991px)').matches) { + // Tablet configuration + return { + left: 'prev,today,next', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay' + }; + } else { + // Desktop configuration + return { + left: 'prevYear,prev,today,next,nextYear', + center: 'title', + right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek', + prevYear: 'prevYear', + nextYear: 'nextYear' + }; + } +} + +function getCalendarHeight() { + if (window.innerWidth <= Constants.MOBILE_WIDTH_SMALL) return '400px'; + if (window.innerWidth <= Constants.MOBILE_WIDTH) return '450px'; + if (window.innerWidth <= Constants.SMALL_TABLET_WIDTH) return '600px'; + if (window.innerWidth <= Constants.TABLET_WIDTH) return '650px'; + if (window.innerWidth <= Constants.MEDIUM_WIDTH) return '767px'; + return '850px'; +} + +function setUserStaffAdminFlag() { + return fetch(isUserStaffAdminURL) + .then(response => response.json()) + .then(data => { + if (data.is_staff_admin) { + AppState.isUserStaffAdmin = true; + } else { + console.error(data.message || "Error fetching user details."); + AppState.isUserStaffAdmin = false; + } + }) + .catch(error => { + console.error("Error checking user's staff admin status: ", error); + AppState.isUserStaffAdmin = false; + }); +} + +function handleCalendarRightClick(event, date) { + if (!AppState.isUserStaffAdmin) { + showErrorModal(notStaffMemberTxt) + return; + } + const contextMenu = document.getElementById("customContextMenu"); + contextMenu.style.top = `${event.pageY}px`; + contextMenu.style.left = `${event.pageX}px`; + contextMenu.style.display = 'block'; + + const newAppointmentOption = document.getElementById("newAppointmentOption"); + newAppointmentOption.onclick = () => createNewAppointment(date); + + // Hide context menu on any click + document.addEventListener('click', () => contextMenu.style.display = 'none', {once: true}); +} + +function goToEvent() { + // Get the event URL + const event = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected)); + if (event && event.url) { + closeModal() + window.location.href = event.url; + } else { + console.error("Event not found or doesn't have a URL."); + } +} + +function closeModal() { + const modal = document.getElementById("eventDetailsModal"); + const editButton = document.getElementById("eventEditBtn"); + const submitButton = document.getElementById("eventSubmitBtn"); + const closeButton = modal.querySelector(".btn-secondary[data-dismiss='modal']"); + const cancelButton = document.getElementById("eventCancelBtn"); + + // Reset the modal buttons to their default state + editButton.style.display = ""; + closeButton.style.display = ""; + submitButton.style.display = "none"; + cancelButton.style.display = "none"; + + // Reset the editing flag + AppStateProxy.isEditingAppointment = false; + + // Close the modal + $('#eventDetailsModal').modal('hide'); +} + + +// ################################################################ // +// Generic // +// ################################################################ // + + +async function cancelEdit() { + // Retrieve the appointment that matches the eventIdSelected + const appointment = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected)); + if (!appointment) { + return; + } + + // Extract only the time using Moment.js + const endTime = moment(appointment.end_time).format('HH:mm:ss'); + + // Find the modal, end time label, and end time input + const modal = document.getElementById("eventDetailsModal"); + const endTimeLabel = modal.querySelector("label[for='endTime']"); + const endTimeInput = modal.querySelector("input[name='endTime']"); + + // Set and display the end time label and input + endTimeInput.value = endTime; + endTimeLabel.style.display = ""; + endTimeInput.style.display = ""; + + // Re-show the event modal with the original data + await showEventModal(appointment.id, false, false); + toggleEditMode(); // Turn off edit mode +} + +function confirmDeleteAppointment(appointmentId) { + const deleteURL = deleteAppointmentURLTemplate + const data = {appointment_id: appointmentId}; + + fetch(deleteURL, { + method: 'POST', headers: { + 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'), + }, body: JSON.stringify(data) + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then(data => { + $('#eventDetailsModal').modal('hide'); + let event = AppState.calendar.getEventById(appointmentId); + if (event) { + event.remove(); + } + showErrorModal(data.message, successTxt); + closeConfirmModal(); // Close the confirmation modal + + // Remove the deleted appointment from the global appointments array + appointments = appointments.filter(appointment => appointment.id !== appointmentId); + + // Refresh the event list for the current date + const currentDate = AppState.calendar.getDate(); + const dateEvents = appointments + .filter(event => moment(currentDate).isSame(event.start_time, 'day')) + .sort((a, b) => new Date(a.start_time) - new Date(b.start_time)); + displayEventList(dateEvents, currentDate); + }) + .catch(error => { + console.error('Error:', error); + showErrorModal(updateApptErrorTitleTxt); + }); +} + +function deleteAppointment() { + showModal(confirmDeletionTxt, confirmDeletionTxt, deleteBtnTxt, null, () => confirmDeleteAppointment(AppState.eventIdSelected)); +} + +function fetchServices(isEditMode = false) { + let url = isEditMode && AppState.eventIdSelected ? `${fetchServiceListForStaffURL}?appointmentId=${AppState.eventIdSelected}` : fetchServiceListForStaffURL; + return fetch(url) + .then(response => response.json()) + .then(data => data.services_offered) + .catch(error => console.error("Error fetching services: ", error)); +} + +function fetchStaffMembers(isEditMode = false) { + let url = isEditMode && AppState.eventIdSelected ? `${fetchStaffListURL}?appointmentId=${AppState.eventIdSelected}` : fetchStaffListURL; + return fetch(url) + .then(response => response.json()) + .then(data => data.staff_members) + .catch(error => console.error("Error fetching staff members: ", error)); + +} + +async function populateServices(selectedServiceId, isEditMode = false) { + const services = await fetchServices(isEditMode); + if (!services) { + showErrorModal(noServiceOfferedTxt) + } + const selectElement = document.createElement('select'); + services.forEach(service => { + const option = document.createElement('option'); + option.value = service.id; // Accessing the id + option.textContent = service.name; // Accessing the name + if (service.id === selectedServiceId) { + option.defaultSelected = true; + } + selectElement.appendChild(option); + }); + return selectElement; +} + +async function populateStaffMembers(selectedStaffId, isEditMode = false) { + const staffMembers = await fetchStaffMembers(isEditMode); + if (!staffMembers) { + showErrorModal(noStaffMemberTxt) + } + const selectElement = document.createElement('select'); + staffMembers.forEach(staff => { + const option = document.createElement('option'); + option.value = staff.id; // Accessing the id + option.textContent = staff.name; // Accessing the name + if (staff.id === selectedStaffId) { + option.defaultSelected = true; + } + selectElement.appendChild(option); + }); + return selectElement; +} + +// Function to fetch services for a specific staff member +async function fetchServicesForStaffMember(staffId) { + const url = `${fetchServiceListForStaffURL}?staff_member=${staffId}`; + try { + const response = await fetch(url); + if (!response.ok) throw new Error('Network response was not ok'); + const data = await response.json(); + return data.services_offered || []; + } catch (error) { + console.error("Error fetching services: ", error); + return []; // Return an empty array in case of error + } +} + +// Function to update services dropdown options +function updateServicesDropdown(dropdown, services) { + // Clear existing options + dropdown.innerHTML = ''; + + // Populate with new options + services.forEach(service => { + const option = new Option(service.name, service.id); // Assuming service object has id and name properties + dropdown.add(option); + }); +} +/* +function getCSRFToken() { + const metaTag = document.querySelector('meta[name="csrf-token"]'); + if (metaTag) { + return metaTag.getAttribute('content'); + } else { + console.error("CSRF token meta tag not found!"); + return null; + } +} +*/ +async function validateAndUpdateAppointmentDate(event, revertFunction) { + const updatedStartTime = event.start.toISOString(); + const updatedEndTime = event.end ? event.end.toISOString() : null; + + const data = { + appointment_id: event.id, start_time: updatedStartTime, date: event.start.toISOString().split('T')[0] + }; + + try { + const validationResponse = await fetch(validateApptDateURL, { + method: 'POST', headers: { + 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'), + }, body: JSON.stringify(data) + }); + + if (validationResponse.ok) { + await updateAppointmentDate(event, revertFunction); + } else { + const responseData = await validationResponse.json(); + showErrorModal(responseData.message); + + revertFunction(); + } + } catch (error) { + console.error('Failed to validate data:', error); + revertFunction(); + } +} + +async function updateAppointmentDate(event, revertFunction) { + const updatedStartTime = event.start.toISOString().split('T')[1]; + const updatedDate = event.start.toISOString().split('T')[0]; + + const data = { + appointment_id: event.id, start_time: updatedStartTime, date: updatedDate, + }; + + try { + const response = await fetch(updateApptDateURL, { + method: 'POST', headers: { + 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'), + }, body: JSON.stringify(data) + }); + + const responseData = await response.json(); + if (response.ok) { + showErrorModal(responseData.message, successTxt) + } else { + console.error('Failed to update appointment date. Server responded with:', response.statusText); + showErrorModal(responseData.message, updateApptErrorTitleTxt); + revertFunction(); + } + } catch (error) { + console.error('Failed to send data:', error); + revertFunction(); + } +} + +// ################################################################ // +// Create new Appt // +// ################################################################ // +function createNewAppointment(dateInput) { + let date; + if (typeof dateInput === 'string' || dateInput instanceof String) { + date = new Date(dateInput); + } else { + date = dateInput; + } + + const day = date.getDate().toString().padStart(2, '0'); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); // getMonth() returns 0-11 + const year = date.getFullYear(); + const formattedDate = `${year}-${month}-${day}`; + const defaultStartTime = `${formattedDate}T09:00:00`; + + showCreateAppointmentModal(defaultStartTime, formattedDate).then(() => { + }); +} + +async function showCreateAppointmentModal(defaultStartTime, formattedDate) { + const servicesDropdown = await populateServices(null, false); + let staffDropdown = null; + if (isUserSuperUser) { + staffDropdown = await populateStaffMembers(null, false); + staffDropdown.id = "staffSelect"; + staffDropdown.disabled = false; // Enable dropdown + attachEventListenersToDropdown(); // Attach event listener + } + servicesDropdown.id = "serviceSelect"; + servicesDropdown.disabled = false; // Enable dropdown + + document.getElementById('eventModalBody').innerHTML = prepareCreateAppointmentModalContent(servicesDropdown, staffDropdown, defaultStartTime, formattedDate); + + adjustCreateAppointmentModalButtons(); + AppStateProxy.isCreating = true; + $('#eventDetailsModal').modal('show'); +} + +function adjustCreateAppointmentModalButtons() { + document.getElementById("eventSubmitBtn").style.display = ""; + document.getElementById("eventCancelBtn").style.display = "none"; + document.getElementById("eventEditBtn").style.display = "none"; + document.getElementById("eventDeleteBtn").style.display = "none"; + document.getElementById("eventGoBtn").style.display = "none"; +} + +// ################################################################ // +// Show Event Modal // +// ################################################################ // + +// Extract Appointment Data +async function getAppointmentData(eventId, isCreatingMode, defaultStartTime) { + if (eventId && !isCreatingMode) { + const appointment = appointments.find(app => Number(app.id) === Number(eventId)); + if (!appointment) { + showErrorModal(apptNotFoundTxt, errorTxt); + return null; + } + return appointment; + } + return { + id: null, + service_name: '', + start_time: defaultStartTime, + end_time: '', + client_name: '', + client_email: '', + client_phone: '', + client_address: '', + additional_info: '', + want_reminder: false, + background_color: '', + timezone: '', + }; +} + +// Populate Services Dropdown +async function getServiceDropdown(serviceId, isEditMode) { + const servicesDropdown = await populateServices(serviceId, !isEditMode); + servicesDropdown.id = "serviceSelect"; + servicesDropdown.disabled = !isEditMode; + return servicesDropdown; +} + +// Populate Staff Dropdown +async function getStaffDropdown(staffId, isEditMode) { + const staffDropdown = await populateStaffMembers(staffId, !isEditMode); + staffDropdown.id = "staffSelect"; + staffDropdown.disabled = !isEditMode; + return staffDropdown; +} + +// Show Event Modal +async function showEventModal(eventId = null, isEditMode, isCreatingMode = false, defaultStartTime = '') { + const appointment = await getAppointmentData(eventId, isCreatingMode, defaultStartTime); + if (!appointment) return; + + const servicesDropdown = await getServiceDropdown(appointment.service_id, isEditMode); + let staffDropdown = null; + if (isUserSuperUser) { + staffDropdown = await getStaffDropdown(appointment.staff_id, isEditMode); + attachEventListenersToDropdown(); // Attach event listener + } + + document.getElementById('eventModalBody').innerHTML = generateModalContent(appointment, servicesDropdown, isEditMode, staffDropdown); + adjustModalButtonsVisibility(isEditMode, isCreatingMode); + $('#eventDetailsModal').modal('show'); +} + +// Adjust Modal Buttons Visibility +function adjustModalButtonsVisibility(isEditMode, isCreatingMode) { + const editButton = document.getElementById("eventEditBtn"); + const submitButton = document.getElementById("eventSubmitBtn"); + const deleteButton = document.getElementById("eventDeleteBtn"); + const goButton = document.getElementById("eventGoBtn"); + + editButton.style.display = !isEditMode && !isCreatingMode ? "" : "none"; + submitButton.style.display = isCreatingMode || isEditMode ? "" : "none"; + deleteButton.style.display = !isEditMode && !isCreatingMode ? "" : "none"; + goButton.style.display = isCreatingMode ? "none" : ""; +} + +// ################################################################ // +// Edit Logic // +// ################################################################ // + +function toggleEditMode() { + const modal = document.getElementById("eventDetailsModal"); + const appointment = appointments.find(app => Number(app.id) === Number(AppState.eventIdSelected)); + AppStateProxy.isCreating = false; // Turn off creating mode + + // Proceed only if an appointment is found + if (appointment) { + AppStateProxy.isEditingAppointment = !AppState.isEditingAppointment; // Toggle the editing state + updateModalUIForEditMode(modal, AppState.isEditingAppointment); + } else { + console.error("Appointment not found!"); + } +} + +function updateModalUIForEditMode(modal, isEditingAppointment) { + const inputs = modal.querySelectorAll("input"); + const staffDropdown = document.getElementById("staffSelect"); + const servicesDropdown = document.getElementById("serviceSelect"); + const editButton = document.getElementById("eventEditBtn"); + const submitButton = document.getElementById("eventSubmitBtn"); + const closeButton = modal.querySelector(".btn-secondary[data-dismiss='modal']"); + const cancelButton = document.getElementById("eventCancelBtn"); + const deleteButton = document.getElementById("eventDeleteBtn"); + const goButton = document.getElementById("eventGoBtn"); + const endTimeLabel = modal.querySelector("label[for='endTime']"); + const endTimeInput = modal.querySelector("input[name='endTime']"); + + // Toggle input and dropdown enable/disable state + inputs.forEach(input => input.disabled = !isEditingAppointment); + staffDropdown.disabled = !isEditingAppointment; + servicesDropdown.disabled = !isEditingAppointment; + + // Toggle visibility of UI elements + toggleElementVisibility(editButton, !isEditingAppointment); + toggleElementVisibility(submitButton, isEditingAppointment); + toggleElementVisibility(cancelButton, isEditingAppointment); + toggleElementVisibility(deleteButton, !isEditingAppointment); + toggleElementVisibility(closeButton, !isEditingAppointment); + toggleElementVisibility(endTimeLabel, !isEditingAppointment); // Show end time in view mode + toggleElementVisibility(endTimeInput, !isEditingAppointment); // Show end time in view mode + toggleElementVisibility(goButton, !isEditingAppointment); +} + +function toggleElementVisibility(element, isVisible) { + if (element) { + element.style.display = isVisible ? "" : "none"; + } +} + +// ################################################################ // +// Submit Logic // +// ################################################################ // + +async function submitChanges() { + const modal = document.getElementById("eventDetailsModal"); + const formData = collectFormDataFromModal(modal); + + if (!validateFormData(formData)) return; + + const response = await sendAppointmentData(formData); + if (response.ok) { + const responseData = await response.json(); + if (AppState.isCreating) { + addNewAppointmentToCalendar(responseData.appt[0]); + } else { + updateExistingAppointmentInCalendar(responseData.appt); + } + + AppState.calendar.render(); + } else { + const responseData = await response.json(); + showErrorModal(responseData.message); + } + closeModal(); + +} + +// Collect form data from modal +function collectFormDataFromModal(modal) { + const inputs = modal.querySelectorAll("input"); + const serviceId = modal.querySelector("#serviceSelect").value; + let staffId = null; + + if (isUserSuperUser) { + // If the user is a superuser, get the staff ID from the dropdown + const staffDropdown = modal.querySelector("#staffSelect"); + if (staffDropdown) { + staffId = staffDropdown.value; + } + } + + const data = { + isCreating: AppState.isCreating, + service_id: serviceId, + appointment_id: AppState.eventIdSelected + }; + + if (staffId) { + data.staff_member = staffId; + } + + inputs.forEach(input => { + if (input.name !== "date") { + let key = input.name.replace(/([A-Z])/g, '_$1').toLowerCase(); + data[key] = input.value; + } + }); + + if (AppState.isCreating) { + data["date"] = modal.querySelector('input[name="date"]').value; + } + + // Special handling for checkbox + const wantReminderCheckbox = modal.querySelector('input[name="want_reminder"]'); + if (!wantReminderCheckbox.checked) { + data['want_reminder'] = 'false'; + } else { + data['want_reminder'] = 'true'; + } + + return data; +} + +// Validate form data +function validateFormData(data) { + return validateEmail(data["client_email"]); +} + +// Validate email +function validateEmail(email) { + const emailInput = document.querySelector('input[name="clientEmail"]'); + const emailError = document.getElementById("emailError"); + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(emailInput.value)) { + emailInput.style.border = "1px solid red"; + emailError.textContent = "Invalid email address, yeah."; + emailError.style.color = "red"; + emailError.style.display = "inline"; + return false; + } else { + emailInput.style.border = ""; + emailError.textContent = ""; + emailError.style.display = "none"; + return true; + } +} + +// Send appointment data to server +async function sendAppointmentData(data) { + const headers = { + 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-CSRFToken': getCookie('csrftoken'), + }; + + return fetch(updateApptMinInfoURL, { + method: 'POST', headers: headers, body: JSON.stringify(data) + }); +} + +// Add new appointment to calendar +function addNewAppointmentToCalendar(newAppointment) { + const newEvent = formatAppointmentsForCalendar([newAppointment])[0]; + appointments.push(newAppointment); + AppState.calendar.addEvent(newEvent); +} + +// Update existing appointment in calendar +function updateExistingAppointmentInCalendar(appointment) { + let eventToUpdate = AppState.calendar.getEventById(AppState.eventIdSelected); + if (eventToUpdate) { + updateEventProperties(eventToUpdate, appointment); + } + // update appointment in appointments array + const index = appointments.findIndex(app => Number(app.id) === Number(AppState.eventIdSelected)); + if (index !== -1) { + appointments[index] = appointment; + } +} + +// Update event properties +function updateEventProperties(event, appointment) { + event.setProp('title', appointment.service_name); + event.setStart(moment(appointment.start_time).format('YYYY-MM-DDTHH:mm:ss')); + event.setEnd(appointment.end_time); + event.setExtendedProp('client_name', appointment.client_name); + event.setProp('backgroundColor', appointment.background_color); +} diff --git a/static/js/appointments.js b/static/js/appointments.js new file mode 100644 index 00000000..1df6b5d3 --- /dev/null +++ b/static/js/appointments.js @@ -0,0 +1,370 @@ +const calendarEl = document.getElementById('calendar'); +let nextAvailableDateSelector = $('.djangoAppt_next-available-date') +const body = $('body'); +let nonWorkingDays = []; +let selectedDate = rescheduledDate || null; +let staffId = $('#staff_id').val() || null; +let previouslySelectedCell = null; +let isRequestInProgress = false; + +const calendar = new FullCalendar.Calendar(calendarEl, { + initialView: 'dayGridMonth', + initialDate: selectedDate, + timeZone: timezone, + headerToolbar: { + left: 'title', + right: 'prev,today,next', + }, + height: '400px', + themeSystem: 'bootstrap', + nowIndicator: true, + bootstrapFontAwesome: { + close: 'fa-times', + prev: 'fa-chevron-left', + next: 'fa-chevron-right', + prevYear: 'fa-angle-double-left', + nextYear: 'fa-angle-double-right' + }, + selectable: true, + dateClick: function (info) { + const day = info.date.getDay(); // Get the day of the week (0 for Sunday, 6 for Saturday) + if (nonWorkingDays.includes(day)) { + return; + } + + // If there's a previously selected cell, remove the class + if (previouslySelectedCell) { + previouslySelectedCell.classList.remove('selected-cell'); + } + + // Add the class to the currently clicked cell + info.dayEl.classList.add('selected-cell'); + + // Store the currently clicked cell + previouslySelectedCell = info.dayEl; + + selectedDate = info.dateStr; + getAvailableSlots(info.dateStr, staffId); + }, + datesSet: function (info) { + highlightSelectedDate(); + }, + selectAllow: function (info) { + const day = info.start.getDay(); // Get the day of the week (0 for Sunday, 6 for Saturday) + if (nonWorkingDays.includes(day)) { + return false; // Disallow selection for non-working days + } + return (info.start >= getDateWithoutTime(new Date())); + }, + dayCellClassNames: function (info) { + const day = info.date.getDay(); + if (nonWorkingDays.includes(day)) { + return ['disabled-day']; + } + return []; + }, +}); + +calendar.setOption('locale', locale); + +$(document).ready(function () { + staffId = $('#staff_id').val() || null; + calendar.render(); + const currentDate = rescheduledDate || moment.tz(timezone).format('YYYY-MM-DD'); + getAvailableSlots(currentDate, staffId); +}); + +function highlightSelectedDate() { + setTimeout(function () { + const dateCell = document.querySelector(`.fc-daygrid-day[data-date='${selectedDate}']`); + if (dateCell) { + dateCell.classList.add('selected-cell'); + previouslySelectedCell = dateCell; + } + }, 10); +} + +body.on('click', '.djangoAppt_btn-request-next-slot', function () { + const serviceId = $(this).data('service-id'); + requestNextAvailableSlot(serviceId); +}) + +body.on('click', '.btn-submit-appointment', function () { + const selectedSlot = $('.djangoAppt_appointment-slot.selected').text(); + const selectedDate = $('.djangoAppt_date_chosen').text(); + if (!selectedSlot || !selectedDate) { + alert(selectDateAndTimeAlertTxt); + return; + } + if (selectedSlot && selectedDate) { + const startTime = convertTo24Hour(selectedSlot); + const APPOINTMENT_BASE_TEMPLATE = localStorage.getItem('APPOINTMENT_BASE_TEMPLATE'); + // Convert the selectedDate string to a valid format + const dateParts = selectedDate.split(', '); + const monthDayYear = dateParts[1] + "," + dateParts[2]; + const formattedDate = new Date(monthDayYear + " " + startTime); + + const date = formattedDate.toISOString().slice(0, 10); + const endTimeDate = new Date(formattedDate.getTime() + serviceDuration * 60000); + const endTime = formatTime(endTimeDate); + const reasonForRescheduling = $('#reason_for_rescheduling').val(); + const form = $('.appointment-form'); + let formAction = rescheduledDate ? appointmentRescheduleURL : appointmentRequestSubmitURL; + form.attr('action', formAction); + if (!form.find('input[name="appointment_request_id"]').length) { + form.append($('', { + type: 'hidden', + name: 'appointment_request_id', + value: appointmentRequestId + })); + } + form.append($('', {type: 'hidden', name: 'date', value: date})); + form.append($('', {type: 'hidden', name: 'start_time', value: startTime})); + form.append($('', {type: 'hidden', name: 'end_time', value: endTime})); + form.append($('', {type: 'hidden', name: 'service', value: serviceId})); + form.append($('', {type: 'hidden', name: 'reason_for_rescheduling', value: reasonForRescheduling})); + form.submit(); + } else { + const warningContainer = $('.warning-message'); + if (warningContainer.find('submit-warning') === 0) { + warningContainer.append('

' + selectTimeSlotWarningTxt + '

'); + } + } +}); + +$('#staff_id').on('change', function () { + staffId = $(this).val() || null; // If staffId is an empty string, set it to null + let currentDate = null + if (selectedDate == null) { + currentDate = moment.tz(timezone).format('YYYY-MM-DD'); + } else { + currentDate = selectedDate; + } + fetchNonWorkingDays(staffId, function (newNonWorkingDays) { + nonWorkingDays = newNonWorkingDays; // Update the nonWorkingDays array + calendar.render(); // Re-render the calendar to apply changes + + // Fetch available slots for the current date + getAvailableSlots(currentDate, staffId); + }); +}); + + +function fetchNonWorkingDays(staffId, callback) { + if (!staffId || staffId === 'none') { + nonWorkingDays = []; // Reset nonWorkingDays + calendar.render(); // Re-render the calendar + callback([]); + return; // Exit the function early + } + let ajaxData = { + 'staff_member': staffId, + }; + + $.ajax({ + url: getNonWorkingDaysURL, + data: ajaxData, + dataType: 'json', + success: function (data) { + if (data.error) { + console.error('Error fetching non-working days:', data.message); + callback([]); + } else { + nonWorkingDays = data.non_working_days; + calendar.render(); + callback(data.non_working_days); + } + } + }); +} + +function getDateWithoutTime(dt) { + dt.setHours(0, 0, 0, 0); + return dt; +} + +function convertTo24Hour(time12h) { + const [time, modifier] = time12h.split(' '); + let [hours, minutes] = time.split(':'); + + if (hours === '12') { + hours = '00'; + } + + if (modifier.toUpperCase() === 'PM') { + hours = parseInt(hours, 10) + 12; + } + + return `${hours}:${minutes}`; +} + +function formatTime(date) { + const hours = date.getHours(); + const minutes = date.getMinutes(); + return (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes); +} + +function getAvailableSlots(selectedDate, staffId = null) { + // Update the slot list with the available slots for the selected date + const slotList = $('#slot-list'); + const slotContainer = $('.slot-container'); + const errorMessageContainer = $('.error-message'); + + // Clear previous error messages and slots + slotList.empty(); + errorMessageContainer.find('.djangoAppt_no-availability-text').remove(); + + // Remove the "Next available date" message + nextAvailableDateSelector = $('.djangoAppt_next-available-date'); // Update the selector + nextAvailableDateSelector.remove(); + + // Correctly check if staffId is 'none', null, or undefined and exit the function if true + // Check if 'staffId' is 'none', null, or undefined and display an error message + if (staffId === 'none' || staffId === null || staffId === undefined) { + console.log('No staff ID provided, displaying error message.'); + const errorMessage = $('

' + noStaffMemberSelectedTxt + '

'); + errorMessageContainer.append(errorMessage); + // Optionally disable the "submit" button here + $('.btn-submit-appointment').attr('disabled', 'disabled'); + return; // Exit the function early + } + + let ajaxData = { + 'selected_date': selectedDate, + 'staff_member': staffId, + }; + fetchNonWorkingDays(staffId, function (nonWorkingDays) { + // Check if nonWorkingDays is an array + if (Array.isArray(nonWorkingDays)) { + // Update the FullCalendar configuration + // calendar.setOption('hiddenDays', nonWorkingDays); + } else { + // Handle the case where there's an error or no data + // For now, we'll just log it, but you can handle it more gracefully if needed + console.error('Failed to get non-working days:', nonWorkingDays); + } + }); + + // Send an AJAX request to get the available slots for the selected date + if (isRequestInProgress) { + return; // Exit the function if a request is already in progress + } + isRequestInProgress = true; + $.ajax({ + url: availableSlotsAjaxURL, + data: ajaxData, + dataType: 'json', + success: function (data) { + if (data.available_slots.length === 0) { + const selectedDateObj = moment.tz(selectedDate, timezone); + const selectedD = selectedDateObj.toDate(); + const today = new Date(); + today.setHours(0, 0, 0, 0); + + if (selectedD < today) { + // Show an error message + errorMessageContainer.append('

' + dateInPastErrorTxt + '

'); + if (slotContainer.find('.djangoAppt_btn-request-next-slot').length === 0) { + slotContainer.append(``); + } + // Disable the 'submit' button + $('.btn-submit-appointment').attr('disabled', 'disabled'); + } else { + errorMessageContainer.find('.djangoAppt_no-availability-text').remove(); + if (errorMessageContainer.find('.djangoAppt_no-availability-text').length === 0) { + errorMessageContainer.append(`

${data.message}

`); + } + // Check if the returned message is 'No availability' + if (data.message.toLowerCase() === 'no availability') { + if (slotContainer.find('.djangoAppt_btn-request-next-slot').length === 0) { + slotContainer.append(``); + } + } else { + $('.djangoAppt_btn-request-next-slot').remove(); + } + } + } else { + // remove the button to request for next available slot + $('.djangoAppt_no-availability-text').remove(); + $('.djangoAppt_btn-request-next-slot').remove(); + const uniqueSlots = [...new Set(data.available_slots)]; // remove duplicates + for (let i = 0; i < uniqueSlots.length; i++) { + slotList.append('
  • ' + uniqueSlots[i] + '
  • '); + } + + // Attach click event to the slots + $('.djangoAppt_appointment-slot').on('click', function () { + // Remove the 'selected' class from all other appointment slots + $('.djangoAppt_appointment-slot').removeClass('selected'); + + // Add the 'selected' class to the clicked appointment slot + $(this).addClass('selected'); + + // Enable the submit button + $('.btn-submit-appointment').removeAttr('disabled'); + + // Continue with the existing logic + const selectedSlot = $(this).text(); + $('#service-datetime-chosen').text(data.date_chosen + ' ' + selectedSlot); + }); + } + // Update the date chosen + $('.djangoAppt_date_chosen').text(data.date_chosen); + $('#service-datetime-chosen').text(data.date_chosen); + isRequestInProgress = false; + }, + error: function() { + isRequestInProgress = false; // Ensure the flag is reset even if the request fails + } + }); +} + +function requestNextAvailableSlot(serviceId) { + const requestNextAvailableSlotURL = requestNextAvailableSlotURLTemplate.replace('0', serviceId); + if (staffId === null) { + return; + } + let ajaxData = { + 'staff_member': staffId, + }; + $.ajax({ + url: requestNextAvailableSlotURL, + data: ajaxData, + dataType: 'json', + success: function (data) { + // If there's an error, just log it and return + let nextAvailableDateResponse = null; + let formattedDate = null; + if (data.error) { + nextAvailableDateResponse = data.message; + } else { + // Set the date in the calendar to the next available date + nextAvailableDateResponse = data.next_available_date; + const selectedDateObj = moment.tz(nextAvailableDateResponse, timezone); + const nextAvailableDate = selectedDateObj.toDate(); + formattedDate = new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }).format(nextAvailableDate); + } + + // Check if the .next-available-date element already exists + nextAvailableDateSelector = $('.djangoAppt_next-available-date'); // Update the selector + let nextAvailableDateText = null; + if (data.error) { + nextAvailableDateText = nextAvailableDateResponse; + } else { + nextAvailableDateText = `Next available date: ${formattedDate}`; + } + if (nextAvailableDateSelector.length > 0) { + // Update the content of the existing .next-available-date element + nextAvailableDateSelector.text(nextAvailableDateText); + } else { + // If the .next-available-date element doesn't exist, create and append it + const nextDateText = `

    ${nextAvailableDateText}

    `; + $('.djangoAppt_btn-request-next-slot').after(nextDateText); + } + } + }); +} diff --git a/static/js/js-utils.js b/static/js/js-utils.js new file mode 100644 index 00000000..ba791270 --- /dev/null +++ b/static/js/js-utils.js @@ -0,0 +1,8 @@ +document.addEventListener('DOMContentLoaded', function () { + const messageElements = document.querySelectorAll('.alert-dismissible'); + setTimeout(function () { + messageElements.forEach(function (element) { + element.style.display = 'none'; + }); + }, 5000); +}); diff --git a/static/js/modal/error_modal.js b/static/js/modal/error_modal.js new file mode 100644 index 00000000..a64887f9 --- /dev/null +++ b/static/js/modal/error_modal.js @@ -0,0 +1,19 @@ +let errorModalInstance = null; + +function showErrorModal(message, title = errorTxt) { + // Insert the error message into the modal + document.getElementById('errorModalLabel').textContent = title; + document.getElementById('errorModalMessage').textContent = message; + + // Show the modal + if (!errorModalInstance) { + errorModalInstance = new bootstrap.Modal(document.getElementById('errorModal')); + } + errorModalInstance.show(); +} + +function closeErrorModal() { + if (errorModalInstance) { + errorModalInstance.hide(); + } +} \ No newline at end of file diff --git a/static/js/modal/show_modal.js b/static/js/modal/show_modal.js new file mode 100644 index 00000000..7dac8b7c --- /dev/null +++ b/static/js/modal/show_modal.js @@ -0,0 +1,25 @@ +function showModal(title, body, actionText, actionUrl, actionCallback) { + // Set the content of the modal + document.getElementById('modalLabel').innerText = title; + document.getElementById('modalBody').innerText = body; + const actionBtn = document.getElementById('modalActionBtn'); + actionBtn.innerText = actionText; + + // Determine the type of action: callback function or URL + if (actionCallback) { + actionBtn.onclick = () => { + actionCallback(); + closeModal(); // Close the modal after action + }; + } else if (actionUrl) { + actionBtn.href = actionUrl; + } + + // Display the modal + $('#confirmModal').modal('show'); +} + + +function closeConfirmModal() { + $('#confirmModal').modal('hide'); +} \ No newline at end of file diff --git a/templates/administration/display_appointment.html b/templates/administration/display_appointment.html new file mode 100644 index 00000000..0071e007 --- /dev/null +++ b/templates/administration/display_appointment.html @@ -0,0 +1,98 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + + +{% endblock %} +{% block title %} + {{ page_title }} +{% endblock %} +{% block description %} + {{ page_description }} +{% endblock %} +{% block body %} +
    +
    +
    +
    +

    {{ page_title }}

    +
    +
    + + {% trans 'Date' %}: {{ appointment.get_date }} +
    +
    + + {% trans 'Start time' %}: {{ appointment.get_start_time|time:"g:i A" }} +
    +
    + + {% trans 'End time' %}: {{ appointment.get_end_time|time:"g:i A" }} +
    +
    + + {% trans 'Service' %}: {{ appointment.get_service_name }} +
    +
    + + {% trans 'Client' %}: {{ appointment.get_client_name }} +
    +
    + + {% trans 'Email' %}: {{ appointment.client.email }} +
    +
    + + {% trans 'Phone' %}: {{ appointment.phone }} +
    +
    + + {% trans 'Wants reminder' %}: {{ appointment.want_reminder }} +
    +
    + + {% trans 'Client address' %}: {{ appointment.address }} +
    +
    + + {% trans 'Additional Information' %}: {{ appointment.additional_info }} +
    +
    + + {% trans 'Is paid' %}: {{ appointment.is_paid_text }} +
    +
    + + {% trans 'Service price' %}: {{ appointment.get_appointment_amount_to_pay_text }} +
    +
    +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +
    +
    +{% endblock %} +{% block customJS %} + + + + + + +{% endblock %} diff --git a/templates/administration/email_change_verification_code.html b/templates/administration/email_change_verification_code.html new file mode 100644 index 00000000..d080e5a8 --- /dev/null +++ b/templates/administration/email_change_verification_code.html @@ -0,0 +1,41 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + +{% endblock %} +{% block title %} + {% trans 'Enter Verification Code' %} +{% endblock %} +{% block description %} + {% trans 'Enter Verification Code' %} +{% endblock %} +{% block body %} +
    +
    +
    +

    {% trans 'Enter Verification Code' %}

    +

    {% trans "We've sent a verification code to your email. Please enter it below" %}:

    +
    + {% csrf_token %} + + +
    +
    +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +{% endblock %} +{% block customJS %} + +{% endblock %} diff --git a/templates/administration/manage_day_off.html b/templates/administration/manage_day_off.html new file mode 100644 index 00000000..0e8c7411 --- /dev/null +++ b/templates/administration/manage_day_off.html @@ -0,0 +1,152 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} + +{% block customCSS %} + + + +{% endblock %} + +{% block body %} +
    +
    +
    +

    {% trans "Manage Days Off" %}

    + +
    + {% csrf_token %} + + {% if error_message %} + + {% endif %} + + {% if days_off_form.staff_member %} +
    + + {{ days_off_form.staff_member }} +
    + {% endif %} + + +
    + + + + +
    + + +
    + + + + +
    + + +
    + + +
    + + + +
    +
    + {% if days_off_form.errors %} +
    + {{ days_off_form.errors }} +
    + {% endif %} +
    + +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    + {% include 'modal/error_modal.html' %} +
    +
    +
    +{% endblock %} + +{% block customJS %} + + + + + + +{% endblock %} diff --git a/templates/administration/manage_service.html b/templates/administration/manage_service.html new file mode 100644 index 00000000..187cc410 --- /dev/null +++ b/templates/administration/manage_service.html @@ -0,0 +1,51 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} + +{% block customCSS %} + + +{% endblock %} + +{% block body %} +
    +{% endblock %} + +{% block customJS %} + +{% endblock %} diff --git a/templates/administration/manage_staff_member.html b/templates/administration/manage_staff_member.html new file mode 100644 index 00000000..4c7ce226 --- /dev/null +++ b/templates/administration/manage_staff_member.html @@ -0,0 +1,87 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} + +{% block customCSS %} + +{% endblock %} + +{% block body %} +
    +
    +
    +

    {% trans 'Staff Appointment Information' %}

    +
    + {% csrf_token %} + {% if form.user %} +
    + {{ form.user.label_tag }} + {{ form.user.errors }} + {{ form.user }} +
    +
    + + {% translate 'User not found' %} ? {% translate 'Create staff member manually' %} + +
    + {% endif %} + +
    + {{ form.services_offered.label_tag }} + {{ form.services_offered.errors }} + {{ form.services_offered }} +
    {% trans 'Hold down “Control”, or “Command” on a Mac, to select more than one.' %} +
    + +
    + {{ form.slot_duration.label_tag }} + {{ form.slot_duration }} + {{ form.slot_duration.help_text }} +
    + +
    + {{ form.lead_time.label_tag }} + {{ form.lead_time }} + {{ form.lead_time.help_text }} +
    + +
    + {{ form.finish_time.label_tag }} + {{ form.finish_time }} + {{ form.finish_time.help_text }} +
    + +
    + {{ form.appointment_buffer_time.label_tag }} + {{ form.appointment_buffer_time }} + {{ form.appointment_buffer_time.help_text }} +
    + +
    + {{ form.work_on_saturday }} + {{ form.work_on_saturday.label_tag }} +
    + +
    + {{ form.work_on_sunday }} + {{ form.work_on_sunday.label_tag }} +
    + + +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +
    +
    +{% endblock %} + +{% block customJS %} + +{% endblock %} diff --git a/templates/administration/manage_staff_personal_info.html b/templates/administration/manage_staff_personal_info.html new file mode 100644 index 00000000..a9c07358 --- /dev/null +++ b/templates/administration/manage_staff_personal_info.html @@ -0,0 +1,49 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} + +{% block customCSS %} + +{% endblock %} + +{% block body %} +
    +
    +
    +

    {% trans 'Staff Personal Information' %}

    +
    + {% csrf_token %} + +
    + {{ form.first_name.label_tag }} + {{ form.first_name }} +
    + +
    + {{ form.last_name.label_tag }} + {{ form.last_name }} +
    + +
    + {{ form.email.label_tag }} + {{ form.email }} +
    + + +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +
    +
    +{% endblock %} + +{% block customJS %} + +{% endblock %} diff --git a/templates/administration/manage_working_hours.html b/templates/administration/manage_working_hours.html new file mode 100644 index 00000000..819f383d --- /dev/null +++ b/templates/administration/manage_working_hours.html @@ -0,0 +1,183 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} + +{% block customCSS %} + + + + + +{% endblock %} + +{% block body %} +
    +
    +
    +

    {% trans "Manage Working Hours" %}

    +
    + {% csrf_token %} + + {% if working_hours_form.staff_member %} +
    + + {{ working_hours_form.staff_member }} +
    + {% endif %} + +
    + + {{ working_hours_form.day_of_week }} +
    + +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    + +
    +
    +
    +
    +
    + + + + + +
    + {% include 'modal/error_modal.html' %} +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +
    +
    +{% endblock %} +{% block customJS %} + + + + + + + + + +{% endblock %} + diff --git a/templates/administration/service_list.html b/templates/administration/service_list.html new file mode 100644 index 00000000..4251649c --- /dev/null +++ b/templates/administration/service_list.html @@ -0,0 +1,78 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} + +{% block title %} + {% trans 'Service List' %} +{% endblock %} +{% block description %} + {% trans 'Service List' %}. +{% endblock %} +{% block body %} + +
    +
    +
    +

    {% trans 'Service List' %}

    +
    +
    + + + + + + + + + + + {% for service in services %} + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans 'Name' %}{% trans 'Duration' %}{% trans 'Price' %}{% trans 'Action' %}
    {{ service.name }}{{ service.get_duration }}{{ service.get_price_text }} +
    + + + + {% translate "Are you sure you want to delete this service?" as d_modal_message %} + {% if request.user.is_superuser %} + + + + + + + {% endif %} +
    +
    {% trans 'No service found' %}.
    +
    +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    + +{% endblock %} + +{% block customJS %} + +{% endblock %} diff --git a/templates/administration/staff_index.html b/templates/administration/staff_index.html new file mode 100644 index 00000000..e1683d2d --- /dev/null +++ b/templates/administration/staff_index.html @@ -0,0 +1,409 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customMetaTag %} + +{% endblock %} +{% block customCSS %} + + + +{% endblock %} +{% block title %} + {{ page_title }} +{% endblock %} +{% block description %} + {{ page_description }} +{% endblock %} +{% block body %} +
    +
    +
    +
    +
    +
    + + {% include 'modal/event_details_modal.html' %} + {% include 'modal/error_modal.html' %} + +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +
    + + + {% include 'modal/confirm_modal.html' %} +{% endblock %} + + +{% block customJS %} + + + + + + + + + + + + +{% endblock %} diff --git a/templates/administration/staff_list.html b/templates/administration/staff_list.html new file mode 100644 index 00000000..70fb7142 --- /dev/null +++ b/templates/administration/staff_list.html @@ -0,0 +1,81 @@ +{% extends 'base.html' %} +{% load i18n %} +{% load static %} +{% block customCSS %} + + + +{% endblock %} +{% block title %} + Staff Members List +{% endblock %} +{% block description %} + {% trans 'List of all staff members' %}. +{% endblock %} +{% block body %} + +
    +
    +
    +

    {% trans 'Staff Members' %}

    + +
    +
    + + + + + + + + + + {% for staff_member in staff_members %} + + + + + + {% empty %} + + + + {% endfor %} + + {% trans "PS: Remove means, deleting the staff status of the user. The user account is still active." %} + + +
    {% trans 'Name' %}{% trans 'Email' %}{% trans 'Details' %}
    {{ staff_member.get_staff_member_name }}{{ staff_member.user.email|default:"N/A" }} + {% trans 'View Profile' %} + {% trans 'Remove' %} +
    {% trans 'No staff members found' %}.
    +
    +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    + +{% endblock %} + +{% block customJS %} + +{% endblock %} diff --git a/templates/administration/user_profile.html b/templates/administration/user_profile.html new file mode 100644 index 00000000..c762c671 --- /dev/null +++ b/templates/administration/user_profile.html @@ -0,0 +1,281 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + + + + +{% endblock %} +{% block title %} + {{ page_title }} +{% endblock %} +{% block description %} + {{ page_description }} +{% endblock %} +{% block body %} +
    +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    + {% translate "Confirm Deletion" as modal_title %} + {% translate "Delete" as delete_btn_modal %} + +

    {% trans 'Personal Information' %}

    + +
    +

    {% trans 'First name' %}: {{ user.first_name|default:user.username }}

    +

    {% trans 'Last name' %}: {{ user.last_name|default:"N/A" }}

    +

    {% trans 'Email' %}: {{ user.email|default:"N/A" }}

    +
    + + + +
    + + +
    +

    {% trans 'Appointment Information' %}

    + + {{ service_msg }} + + {% if staff_member %} +
    +

    + {% trans 'Slot duration' %}: {{ staff_member.get_slot_duration_text }} + +

    +

    {% trans 'General start time' %}: {{ staff_member.get_lead_time }}

    +

    {% trans 'General end time' %}: {{ staff_member.get_finish_time }}

    +

    + {% trans 'Weekend days you work' %}: {{ staff_member.get_weekend_days_worked_text }} +

    + +

    + {% trans 'Appointment buffer time' %}: {{ staff_member.get_appointment_buffer_time_text }} + +

    + +
    + + + + {% else %} +
    +

    {% trans 'No staff member information yet for this user' %}.

    +
    + + + + {% endif %} + +
    + + +
    +

    {% trans 'Days Off' %}

    + + + + + {% trans "Days off are days you're not working, you need to set them for holidays as well so clients don't book you those days." %} + +
    + + + + + + + + + + + {% for day_off in days_off %} + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans 'Start date' %}{% trans 'End date' %}{% trans 'Description' %}{% trans 'Action' %}
    {{ day_off.start_date }}{{ day_off.end_date }}{{ day_off.description }} +
    + {% if superuser %} + + + + {% else %} + + + + {% endif %} + {% translate "Are you sure you want to delete this working hours?" as d_modal_message %} + {% if superuser %} + + + + {% else %} + + + + {% endif %} +
    +
    {% trans 'No days off have been set' %}.
    +
    +
    + + +
    +

    {% trans 'Working Hours' %}

    + + + + + {% trans "Note: If you are a staff member, your working hours will be used to determine when you are available for appointments." %} + +
    + + + + + + + + + + + {% for working_hour in working_hours %} + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans 'Day' %}{% trans 'Start time' %}{% trans 'End time' %}{% trans 'Action' %}
    {{ working_hour.get_day_of_week_str }}{{ working_hour.start_time|time:"g:i A" }}{{ working_hour.end_time|time:"g:i A" }} +
    + {% if superuser %} + + + + {% else %} + + + + {% endif %} + {% translate "Are you sure you want to delete this working hours?" as w_modal_message %} + {% if superuser %} + + + + {% else %} + + + + {% endif %} +
    +
    {% trans 'No working hours have been set' %}.
    +
    +
    + + +
    +

    {% trans 'Service Offered' %}

    + + {% if not superuser %} + {% trans "To add/modify a new service, make a request to an admin." %} + {% trans "Changes made in one service will change it for every staff member." %} + {% endif %} + +
    + + + + + + + + + + + + {% for service in services_offered %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
    {% trans 'Name' %}{% trans 'Description' %}{% trans 'Duration' %}{% trans 'Price' %}{% trans 'Down payment' %}
    {{ service.name }}{{ service.description|default:"N/A" }}{{ service.get_duration }}{{ service.get_price_text }}{{ service.get_down_payment_text }}
    {% trans 'No service offered yet' %}.
    +
    +
    +
    + + {% include 'modal/confirm_modal.html' %} +
    +{% endblock %} +{% block customJS %} + + + + + + + + + +{% endblock %} + diff --git a/templates/appointment/appointment_client_information.html b/templates/appointment/appointment_client_information.html new file mode 100644 index 00000000..acac34a0 --- /dev/null +++ b/templates/appointment/appointment_client_information.html @@ -0,0 +1,165 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + +{% endblock %} +{% block title %} + {% translate 'Client Information' %} - {{ ar.get_service_name }} +{% endblock %} +{% block description %} + {% blocktranslate with service_name=ar.get_service_name %} + Your appointment request for {{ service_name }} has been submitted. + Please provide your information to create an account and complete the payment process. + {% endblocktranslate %} +{% endblock %} +{% block body %} +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + +
    + {% csrf_token %} + +
    +
    {% trans "Service Details" %}
    + +
    + +
    +
    {{ ar.get_service_name }}
    +
    + {{ ar.date }} {% trans "at" %} {{ ar.start_time }} +
    +
    {{ ar.service.get_duration }}
    +
    + +
    + {% if ar.is_a_paid_service %} + {% if APPOINTMENT_PAYMENT_URL %} +
    +
    {% trans "Payment Details" %}
    +
    +
    {% trans "Total" %}
    +
    ${{ ar.get_service_price }}
    +
    +
    + + {% if ar.accepts_down_payment %} + + {% endif %} +
    +
    + {% else %} + + {% endif %} + {% else %} + + {% endif %} +
    +
    +
    +
    +{% endblock %} +{% block customJS %} + +{% endblock %} diff --git a/templates/appointment/appointments.html b/templates/appointment/appointments.html new file mode 100644 index 00000000..10cb6359 --- /dev/null +++ b/templates/appointment/appointments.html @@ -0,0 +1,134 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + + +{% endblock %} +{% block title %} + {{ page_title }} +{% endblock %} +{% block description %} + {{ page_description }} +{% endblock %} +{% block body %} +
    +
    +
    +

    + {% if page_header %}{{ page_header }}{% else %}{{ service.name }}{% endif %}

    + + {% trans "Check out our availability and book the date and time that works for you" %} + +
    + +
    +
    +
    +
    + {% trans "Select a date and time" %} +
    +
    + {% trans "Timezone" %}: {{ timezoneTxt }} +
    +
    +
    +
    +
    +
    +
    +
    {{ date_chosen }}
    +
    +
    +
      + +
    +
    + +
    +
    + {% if rescheduled_date %} +
    + + +
    + {% endif %} +
    +
    +
    + {% csrf_token %} +
    + + +
    + +
    {% trans "Service Details" %}
    +
    +
    +

    {{ service.name }}

    +

    {{ date_chosen }}

    +

    {{ service.get_duration }}

    +

    {{ service.get_price_text }}

    + +
    +
    +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +
    +{% endblock %} + +{% block customJS %} + + + + + + + + +{% endblock %} diff --git a/templates/appointment/default_thank_you.html b/templates/appointment/default_thank_you.html new file mode 100644 index 00000000..7d066b6c --- /dev/null +++ b/templates/appointment/default_thank_you.html @@ -0,0 +1,36 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + +{% endblock %} +{% block title %} + {{ page_title }} +{% endblock %} +{% block description %} + {{ page_description }} +{% endblock %} +{% block body %} +
    +
    +

    {% trans "See you soon" %} !

    +

    {% trans "We've successfully scheduled your appointment! Please check your email for all the details" %}.

    +

    {% trans "Appointment details" %}:

    +
      +
    • {% trans 'Service' %}: {{ appointment.get_service_name }}
    • +
    • {% trans 'Appointment Date' %}: {{ appointment.get_appointment_date }}
    • +
    • {% trans 'Appointment Time' %}: {{ appointment.get_start_time }}
    • +
    • {% trans 'Duration' %}: {{ appointment.get_service_duration }}
    • +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +{% endblock %} +{% block customJS %} + +{% endblock %} \ No newline at end of file diff --git a/templates/appointment/enter_verification_code.html b/templates/appointment/enter_verification_code.html new file mode 100644 index 00000000..6b157a11 --- /dev/null +++ b/templates/appointment/enter_verification_code.html @@ -0,0 +1,39 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} + +{% block customCSS %} + +{% endblock %} + +{% block title %}{% trans 'Enter Verification Code' %}{% endblock %} +{% block description %}{% trans 'Enter Verification Code' %}{% endblock %} + +{% block body %} +
    +
    +

    {% trans 'Enter Verification Code' %}

    +

    {% trans "We've sent a verification code to your email. Please enter it below" %}:

    + +
    + {% csrf_token %} +
    + + +
    + +
    + + {% if messages %} + {% for message in messages %} +
    + {{ message }} +
    + {% endfor %} + {% endif %} +
    +
    +{% endblock %} + +{% block customJS %} +{% endblock %} \ No newline at end of file diff --git a/templates/appointment/rescheduling_thank_you.html b/templates/appointment/rescheduling_thank_you.html new file mode 100644 index 00000000..715c917a --- /dev/null +++ b/templates/appointment/rescheduling_thank_you.html @@ -0,0 +1,79 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + +{% endblock %} +{% block title %} + {% trans "Rescheduling Successful" %} +{% endblock %} +{% block description %} + {% trans "Your appointment rescheduling was successful. Please confirm via email." %} +{% endblock %} +{% block body %} +
    +
    +
    +

    {% trans "Rescheduling Successful" %}

    +

    {% trans "Your appointment rescheduling request has been successfully submitted. Please check your email and click on the confirmation link to finalize the rescheduling process." %}

    + {% trans "Go to Homepage" %} +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +{% endblock %} +{% block customJS %} + +{% endblock %} \ No newline at end of file diff --git a/templates/appointment/set_password.html b/templates/appointment/set_password.html new file mode 100644 index 00000000..8c39db2e --- /dev/null +++ b/templates/appointment/set_password.html @@ -0,0 +1,104 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + +{% endblock %} +{% block title %} + {% trans 'Reset Your Password' %} +{% endblock %} +{% block description %} + {% trans 'Reset Your Password' %} +{% endblock %} +{% block body %} +
    +

    {% trans 'Reset Your Password' %}

    + + {% if messages %} +
      + {% for message in messages %} + {{ message }} + {% endfor %} +
    + {% endif %} + + +
    + {% csrf_token %} + {{ form.as_p }} + +
    +{% endblock %} +{% block customJS %} + +{% endblock %} \ No newline at end of file diff --git a/templates/appointment/thank_you.html b/templates/appointment/thank_you.html new file mode 100644 index 00000000..335760b8 --- /dev/null +++ b/templates/appointment/thank_you.html @@ -0,0 +1,53 @@ +{% extends BASE_TEMPLATE %} +{% load i18n %} +{% load static %} +{% block customCSS %} + +{% endblock %} +{% block title %}{{ page_title }}{% endblock %} +{% block description %}{{ page_description }}{% endblock %} +{% block body %} +
    +

    {{ page_title }}

    +

    + {{ page_message }} +

    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +{% endblock %} +{% block customJS %} + +{% endblock %} diff --git a/templates/base.html b/templates/base.html index 7599a954..f0adc4ae 100644 --- a/templates/base.html +++ b/templates/base.html @@ -46,7 +46,9 @@ {% endif %} - + {% block customCSS %} {% endblock %} @@ -56,13 +58,13 @@
    {% include 'header.html' %}
    -
    + {% block content %} {% endblock content%} {% block body %} {% endblock body%} -
    + {% include 'footer.html' %}
    diff --git a/templates/email_sender/admin_new_appointment_email.html b/templates/email_sender/admin_new_appointment_email.html new file mode 100644 index 00000000..4d75d8a1 --- /dev/null +++ b/templates/email_sender/admin_new_appointment_email.html @@ -0,0 +1,74 @@ +{% load i18n %} + + + + + + {% translate 'Appointment Request Notification' %} + + + + + + diff --git a/templates/email_sender/reminder_email.html b/templates/email_sender/reminder_email.html new file mode 100644 index 00000000..0c58b6c7 --- /dev/null +++ b/templates/email_sender/reminder_email.html @@ -0,0 +1,97 @@ +{% load i18n %} + + + + + + {% translate 'Appointment Reminder' %} + + + + + + diff --git a/templates/email_sender/reschedule_email.html b/templates/email_sender/reschedule_email.html new file mode 100644 index 00000000..00d3b08f --- /dev/null +++ b/templates/email_sender/reschedule_email.html @@ -0,0 +1,93 @@ +{% load i18n %} + + + + + {% trans "Appointment Reschedule Confirmation" %} + + + + + + + diff --git a/templates/email_sender/thank_you_email.html b/templates/email_sender/thank_you_email.html new file mode 100644 index 00000000..1f9b23db --- /dev/null +++ b/templates/email_sender/thank_you_email.html @@ -0,0 +1,290 @@ +{% load i18n %} +{% load static %} + + + + + + + + + +
    + {% if pre_header %} + {{ pre_header }} + {% endif %} +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + +
    + {% comment %}{% endcomment %} +
    +

    {{ main_title }}

    +
    + + + + + + + +
    + {{ month_year }} +
    + {{ day }} +
    +
    + +

    + {% trans 'Thank you for choosing us.' %} +

    +
    + +
    + + + {% if account_details %} + + + + + + + {% if activation_link %} + + + + {% endif %} + + + + + {% endif %} + {% if more_details %} + + + + {% endif %} + {% if reschedule_link %} + + + + {% endif %} + + + + +
    +

    Hi {{ first_name }},

    +

    {{ message_1 }}

    +
    +

    {{ message_2 }}

    +
    +

    {% trans 'Account Activation' %}

    +

    + {% blocktranslate with link=activation_link %} + To activate your account and set your password, please use the following secure + link: Set Your Password. Please + note that this link will expire in 2 days for your security. + {% endblocktranslate %} +

    +
    +

    {% trans "Account Information" %}

    +
    +
      + {% for key, value in account_details.items %} +
    • {{ key }}: {{ value }}
    • + {% endfor %} +
    +
    + +
    +

    {% trans "Appointment Details" %}

    +
    +
      + {% for key, value in more_details.items %} +
    • {{ key }}: {{ value }}
    • + {% endfor %} +
    +
    + +
    +

    {% trans 'Rescheduling' %}

    +

    + {% translate 'If your plans change and you need to reschedule your appointment, you can easily do so by following this link: ' %} + + {% translate 'Reschedule Appointment' %} + +

    +
    +

    {% trans 'Support' %}

    +

    + {% blocktranslate %} + Should you have any inquiries or require further assistance, our support team is here to + help. You can reach us anytime. + {% endblocktranslate %} +

    +

    + {% trans "We look forward to serving you and ensuring that your experience with us is both rewarding and satisfactory." %} +

    +

    {% trans "Warm regards" %},

    +

    {% trans "The Team" %}

    +
    + +
    + + + + + +
    + © {{ current_year }} {{ company }}. {% trans "All rights reserved" %}. +
    + +
    + + + diff --git a/templates/inventory/car_inventory.html b/templates/inventory/car_inventory.html index 8355d1bf..c6a7bbc5 100644 --- a/templates/inventory/car_inventory.html +++ b/templates/inventory/car_inventory.html @@ -34,7 +34,7 @@
    -

    {{ cars.first.id_car_make.get_local_name }}{{ cars.first.id_car_model.get_local_name }}

    +

    {{ cars.first.id_car_make.get_local_name }} {{ cars.first.id_car_model.get_local_name }}

    {{ cars.first.id_car_serie.name }}, {{ cars.first.id_car_trim.name }}

    diff --git a/templates/modal/confirm_modal.html b/templates/modal/confirm_modal.html new file mode 100644 index 00000000..da3daab8 --- /dev/null +++ b/templates/modal/confirm_modal.html @@ -0,0 +1,23 @@ +{% load i18n %} + + diff --git a/templates/modal/error_modal.html b/templates/modal/error_modal.html new file mode 100644 index 00000000..0213a1bd --- /dev/null +++ b/templates/modal/error_modal.html @@ -0,0 +1,21 @@ +{% load i18n %} + + diff --git a/templates/modal/event_details_modal.html b/templates/modal/event_details_modal.html new file mode 100644 index 00000000..5f141122 --- /dev/null +++ b/templates/modal/event_details_modal.html @@ -0,0 +1,38 @@ +{% load i18n %} + + diff --git a/templates/sales/invoices/invoice_detail.html b/templates/sales/invoices/invoice_detail.html index d65994a9..b17f5159 100644 --- a/templates/sales/invoices/invoice_detail.html +++ b/templates/sales/invoices/invoice_detail.html @@ -55,7 +55,7 @@ -
    +

    {% trans 'Invoice' %}

    @@ -82,8 +82,8 @@

    {% trans 'Paid Amount' %}

    -

    ${{invoice.amount_paid}}

    -
    Owned ${{invoice.get_amount_open}}
    +

    {{invoice.amount_paid}} {{ _("SAR") }}

    +
    {{ _("Owned") }} {{invoice.get_amount_open|floatformat}} {{ _("SAR") }}
    {{invoice.get_progress_percent}}%
    @@ -135,9 +135,9 @@

    {% trans 'Due Amount' %}

    {% if invoice.is_paid %} -

    ${{invoice.amount_due}}

    +

    {{invoice.amount_due}} {{ _("SAR") }}

    {% else %} -

    ${{invoice.amount_due}}

    +

    {{invoice.amount_due}} {{ _("SAR") }}

    {% endif %}
    @@ -259,7 +259,7 @@
    - +
    {% endblock %} {% block customJS %} diff --git a/templates/sales/invoices/invoice_preview.html b/templates/sales/invoices/invoice_preview.html index b077abc9..55facc0a 100644 --- a/templates/sales/invoices/invoice_preview.html +++ b/templates/sales/invoices/invoice_preview.html @@ -1,3 +1,4 @@ +{% load i18n static%} @@ -44,6 +45,13 @@ .invoice-details { margin-bottom: 2.5rem; } + .invoice-details-en { + text-align: left; + } + .invoice-details-ar { + text-align: right; + + } .invoice-details p { margin: 0.75rem 0; color: #555; @@ -119,80 +127,84 @@
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    invoice

    -

    Thank you for choosing us. We appreciate your business!

    + +

    Invoice / فاتورة

    +

    -
    -

    invoice Number: #{{invoice.invoice_number}}

    -

    Date: {{invoice.date_in_review}}

    -

    Customer: {{invoice.customer.customer_name}}

    -

    Email: {{invoice.customer.email}}

    -

    Terms: {{invoice.terms|title}}

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + Invoice Number + + {{invoice.invoice_number}} + + رقم الفاتورة +
    + Date + + {{invoice.date_in_review}} + + التاريخ +
    + Customer + + {{invoice.customer.customer_name}} + + العميل +
    + Email + + {{invoice.customer.email}} + + البريد الالكتروني +
    + Terms + + {{invoice.get_terms_display}} + + طريقة الدفع +
    + +
    -
    +
    - - - - + + + + @@ -201,7 +213,7 @@ - + {% endfor %} @@ -210,28 +222,29 @@
    -

    VAT ({{vat}}%): ${{vat_amount}}

    -

    Additional Services: +

    VAT/ضريبة القيمة المضافة ({{vat}}%): {{vat_amount}} {{ _("SAR") }}

    +

    Additional Services/ الخدمات الإضافية
    {% for service in additional_services %} - {{service.name}} - ${{service.price}}
    + {{service.name}} - {{service.price}} {{ _("SAR") }}
    {% endfor %}

    -

    Total Amount: ${{total}}

    +

    Total/الإجمالي {{total}} {{ _("SAR") }}

    - + @@ -245,9 +258,9 @@ // Options for html2pdf.js const options = { margin: 0, // No margin - filename: 'invoice.pdf', // Name of the downloaded file + filename: '{{invoice.invoice_number}}.pdf', // Name of the downloaded file image: { type: 'jpeg', quality: 0.98 }, // Image quality - html2canvas: { + html2canvas: { scale: 2, // Increase scale for better quality scrollX: 0, // Ensure no horizontal scroll offset scrollY: 0, // Ensure no vertical scroll offset diff --git a/test.txt b/test.txt new file mode 100644 index 00000000..929c2e93 --- /dev/null +++ b/test.txt @@ -0,0 +1,12 @@ + + + +from django.core.mail import send_mail + +send_mail( + 'Test Email Subject', + 'This is a test email message.', + 'info@tenhal.sa', # From email + ['recipient@example.com'], # To email + fail_silently=False, +) \ No newline at end of file
    ItemQuantityUnit PriceTotalItem
    الصنف
    Quantity
    العدد
    Unit Price
    سعر الوحدة
    Total
    الإجمالي
    {{item.info.make}} {{item.quantity}} {{item.finances.selling_price}}{{item.total}}{{item.total}}
    +
    +
    +

    {{ page_title }}

    +
    + {% csrf_token %} + {{ form.as_p }} + {% if btn_text %} + + {% else %} + {% if request.user.is_superuser and service.id %} + + {% endif %} + {% endif %} +
    +
    + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
    +
    +
    +