From 307bdef2e1f64230e3f468d9edb00ce45d23a9bd Mon Sep 17 00:00:00 2001 From: dvirlabs Date: Sun, 3 May 2026 04:56:48 +0300 Subject: [PATCH] Update app --- .../models/__pycache__/model.cpython-314.pyc | Bin 1442 -> 1481 bytes .../__pycache__/product.cpython-314.pyc | Bin 2048 -> 2064 bytes backend/app/models/model.py | 1 + backend/app/models/product.py | 2 +- .../__pycache__/categories.cpython-314.pyc | Bin 4727 -> 5032 bytes .../__pycache__/contact.cpython-314.pyc | Bin 1296 -> 1436 bytes .../__pycache__/models.cpython-314.pyc | Bin 6660 -> 6950 bytes .../routers/__pycache__/users.cpython-314.pyc | Bin 2587 -> 2763 bytes backend/app/routers/categories.py | 6 +- backend/app/routers/contact.py | 3 +- backend/app/routers/models.py | 6 +- backend/app/routers/users.py | 6 +- .../schemas/__pycache__/model.cpython-314.pyc | Bin 2849 -> 3024 bytes .../__pycache__/product.cpython-314.pyc | Bin 4835 -> 5001 bytes backend/app/schemas/model.py | 3 + backend/app/schemas/product.py | 12 +- .../services/__pycache__/cart.cpython-314.pyc | Bin 5281 -> 5417 bytes .../__pycache__/order.cpython-314.pyc | Bin 4962 -> 5104 bytes .../__pycache__/product.cpython-314.pyc | Bin 5134 -> 5434 bytes backend/app/services/cart.py | 3 +- backend/app/services/order.py | 3 +- backend/app/services/product.py | 6 +- backend/migrations/003_add_stock_to_model.sql | 2 + .../004_make_product_stock_nullable.sql | 3 + frontend/src/components/ProductCard.jsx | 22 +- frontend/src/components/SearchBar.jsx | 2 +- frontend/src/pages/Admin.jsx | 319 +++++++++++++++++- frontend/src/pages/Cart.jsx | 10 +- frontend/src/pages/Checkout.jsx | 4 +- frontend/src/pages/Models.jsx | 2 +- frontend/src/pages/Orders.jsx | 4 +- frontend/src/pages/ProductDetail.jsx | 22 +- 32 files changed, 380 insertions(+), 61 deletions(-) create mode 100644 backend/migrations/003_add_stock_to_model.sql create mode 100644 backend/migrations/004_make_product_stock_nullable.sql diff --git a/backend/app/models/__pycache__/model.cpython-314.pyc b/backend/app/models/__pycache__/model.cpython-314.pyc index 06324835821f76ed1ba4563479499ed6b6b24956..9da8f69ee9fa9d95075f6daaf11f8503d5974379 100644 GIT binary patch delta 245 zcmZ3)eUh73n~#@^0SNxNf6Hv$$ji*g=rUQ5@k~8i5K|DVITJ{VfuTe&mQ|46^oG=+_|xFkP0dvYoB5ysV%typq7d4wjIF9^B9qcwR6izTDp|Hb&Bx2d00cX{zGXhx$g9H0$g|mqQIwG*h$)CWh{v3HvM-aoIByW2ITJ`3 z14D^)EUO|ztW*$CY;rSGY$8HNIaWoHA&B3E5vo!Z%ohj}3=)FtRs)L(2Qeavse{Et zP{cHXL`yV-xRn_+#U?v4*Gl?nG8Ksd&Ar8*lA4xSnp5JY$yg)-6qtO9*<~|7%V$PL W)yd3ki`hapm|Wqp*u07@j1d3{1u7o^ delta 210 zcmbOr&>+C8&Bx2d00er+zGR-;$g9H0$hFysQIwG_jvR>S;6fun; z;S$Xt5oHEV(aFxtwLF@Px7brs(-KQ_O57&jWOiZn+bqlSnUPU>vLM@H7RM_*YMXbl Hg)ssE%d{tY diff --git a/backend/app/models/model.py b/backend/app/models/model.py index 05f1aa7..0fde269 100644 --- a/backend/app/models/model.py +++ b/backend/app/models/model.py @@ -13,6 +13,7 @@ class Model(Base): brand = Column(String(100), nullable=False) base_price = Column(DECIMAL(10, 2), nullable=True) sizes = Column(JSON, nullable=True) + stock = Column(Integer, nullable=True) description = Column(String, nullable=True) created_at = Column(TIMESTAMP, default=datetime.utcnow) diff --git a/backend/app/models/product.py b/backend/app/models/product.py index e068d9a..bc97490 100644 --- a/backend/app/models/product.py +++ b/backend/app/models/product.py @@ -19,7 +19,7 @@ class Product(Base): brand = Column(String) sizes = Column(JSON) # ["S", "M", "L", "XL", ...] colors = Column(JSON) # ["Red", "Blue", ...] - stock = Column(Integer, default=0) + stock = Column(Integer, nullable=True, default=None) images = Column(JSON) # Array of image URLs is_featured = Column(Boolean, default=False) is_on_sale = Column(Boolean, default=False) diff --git a/backend/app/routers/__pycache__/categories.cpython-314.pyc b/backend/app/routers/__pycache__/categories.cpython-314.pyc index dc2d0e951b1807f14fbfecc507f72b93c73f565a..a46a7855114f9298b4c94e6c238699458ae34ab2 100644 GIT binary patch delta 1013 zcmZWoPiPZa9DXyC$>jee)0CvyYUn*(i&znqoy=AzDyPu z7TSW~pHtyo_9Ey_5kx71c=NE!9-CU4GWOVuc$r|aH`(7Wu^{*k-}k-u`@@^x_hx(V zlXR#*6!Z~TN5?*w{`I~LZA(A;xLA__Y=pWU0eI6RJ0kFwn*_p52^33)emJlRRr6$$ z!)jc4qLCrKt6WsoAu?usxeeN7@7H%RirY0gC|t~{6XvXB0x zz~%gy$R`PNLNWHmAD-iFUwPIrX9`BGTzN`V$(C;DRo${EJqW#Qmr*KO6hFrnbi=US z#o2PXWZ4omuTislLltQTf;5IOh-ty1Zkg9->7NBdw{+WY{9f2Osfv)0N~L;wXcwl+ zMRv?9pFR#6_tmQa^JFVP4*dLL;`LvRfw7f=vBmR^Xu2LvuSV5n?YD+jsB49Hnm#|i zp(N@`|KC#pY})%_cptX-snueegt=%FVIvZC1mMliUPlCmfqTP_gvx`X_lDU6-!ASs zd*XZ5I~j!P*2;65@ZI1$VDKy=#}v(_iNjw_8jrwuBTV;qM7}w#hdmKpkvarKvT7Ap@37nbR%#Qwdae1oL z$WE_hrxynsN~W%4UJorRnN{V~a%FCtkXzhYzS$-RzL1~bf$;DX(y#pd=z6@j5m)PR zbuHe1KX6an@W_jvN1bb)bWF&tF$eq!3m;3I8g$ws?ue##v{tlK4KH^R+ zHG|JM?g)D*9}`F5Zoe$OmydD@d~>!?saX^+P0u4>m>yySkvMmmor}DY;kW!G5dM(E YgK`o~R#Gy&96USe@SvO&%PNQe2lt%xDF6Tf delta 712 zcmX|9O=uHA6rS15W_Rzy4GD5I7VIG9Kw6fNtQp!$aPe6i0yEcO#$L6M>O2=&16nZxC+CY=7_qU=uCk zfsRUen%o~?>+euXAnZQ2t5&aCHeadL zY1$b=m;*iv%e(-v ziTXYi8tV+5dUosO((|RQWuWx8{A(`BN@k8q4v~U~dknh4VTXsjAC5Z$WEfvo9g#7K z86hil%zFZ_(5&|(jj6{1CBEdE8w_4h*nl%C$_EkJ;NYU=MPPs9=6uTp>&)Ii3(m0$Ru<10CQ-3fq3;LABAY8O!Z^reUSL^o=@RB(rd83w6tA znzjgf#j*&=u)ebZDUrk&&ocP$+mVg7uv#}7HCwn}X|xSfgbHv^r(6DrC&xCh%qR4- z|MJuiExD(qJ6ifv=9`xOuI2W$d`HXgY738jkA$wIJe6LBcco;HN26CUT?_2s^V<{3 zdyF&mtC|(&;ZxadU+^4`upeXdt+qvSz{nK_?3kRUw}&)bq#Hv!3cR!f9&xh{2h7oE UE~G19KhrKc955$A+o3am05$NTW&i*H diff --git a/backend/app/routers/__pycache__/contact.cpython-314.pyc b/backend/app/routers/__pycache__/contact.cpython-314.pyc index 906e5871148358ca7bb4808626e45414a5ec4022..079ec25de32fd714b1ba311a9b24152d98615e0f 100644 GIT binary patch delta 441 zcmbQhHHVv5n~#@^0SIJ$zGd#-$Xmt~&BiG9kpW0tQ8N1gqW8Fixt~F*l9@mx6tDtm zW+49j1V}V6#4tj|8NwKX7)+ol)uDnZf(#5YlTR?ov-$FeF$AzqKFcH}QNR?#4$~CG zsLv3_K~ zl+xUSl?_w?*MXALZn#@H) zKqW=OAaz_RN%6U<#l?x~sl^~Cs;N!pVUbmk2QopvEw%v?4Gd2NK^tvruQT z9*{XXm1UjSF3A+z<(x7~QZj(Orv#nh6^S>W&P-&(`}&Ye;;_oO|B8pEq;QoqK^7O?+(u z??kX#F8!X^S>Z1_J3ZLFj{vsBQqux`xnViTie7x(^a!7Mhk@99Y;}4=ytV zb2nFiIR$)D%6r2oWD78dyHmMC6})9AjL37b%o-GctcmVNqSHI{uWra*kHXe&Y(=wd z7!MSaZD*mt2Jm5pN(5&mWQOzFvb2=CrK4`P3Mm|U<{A|Z=k$!KO%1Eb>DgCEDIgC1 zw9`HwjYks+U2h@vybs&R0PpR{2TsHi2B(e;XY=AVxYF%?pt$Z>VoTa!6k1-@~0*LhE$Upypb6kNe_;E8B`bhwgh)tII(mW zfb@izdA$P~$*yY2dJ*M(1IsYoo-rLK3WOLZApNz2MRYf4r& zFTcmvR=4Gk7`-#;@JY!jtwWEI70(9&e$)dCV(Lu>nAt?;OR5DJ9eUPkGQi9lvRo4W E3kF;8+5i9m delta 766 zcmYLHT}TvB6ux(UcE@#RXU28iar0*(S(*wJ@vqP_#Za+_XG!zw_O5&OI~tJNJ3_-A?sL zOpOq<)|H)n^tCz~Ihmxj6@sv-9|%PBTZ$IQ=yyUSs#n6uSQ-yZi~QK0Dd=%8ZHEM) z%9YZuh(?Y71>>PMVw0=^cakY(kV<4uvMID1weZqVMVd|UM5vkAG@FDkkvP0EYT$d+ zkzNoh3YXQx@K9|SlUWyjg{3%jBlwDQGw*u&Ygc_GH#ISPJ?9i$855aICY@$Yyd*l! zoJr5wO|k)=#N~d1NbHHBC)PA;>Gq@f2lM6RA>>eweeD_K1?>n63NLxVUYDBSYc*y&abks7wf>SFVEziLe7_OOcsic8^#dIg?jxHtT>n37i<*Mr*OH8 z2n!_8iK^WnS%l@9uGY_H%Z6#Knbuor)wIfH|AslVW)7{J!;8^-@}{CK$&a0NrKKX0 z`qLCf;>*-7iM1an?SqerZuuOxyZ}<&H#$@LQFn{V=P}@v@@5C^9YE@fb48EwKVWA$ z@Os8;STBb(hrcsc`f&8cf^&jRK)L>%h7WXGBtlytAPYL-X{trTLGjqJKtL8`U^Qj` E0dG^d9{>OV diff --git a/backend/app/routers/__pycache__/users.cpython-314.pyc b/backend/app/routers/__pycache__/users.cpython-314.pyc index 73554f4bc9e121ca98141d4c8560b85461b654e0..ea95dfcb605261ddb35b9882bbb02b1f72fd5185 100644 GIT binary patch delta 570 zcmbO&a$1y6n~#@^0SGL7zGa%QZRB%cQ4nSn_{abx?g&bMUgSfH{mgfE})8G6$>RpwSH77o$G`Ao`lkpaBYDIERX-aB*Xgv~AW0b& zgB+};rlz1Uc{QtSy)KX^1jNNPK%#-+3lD=MqX**&?hB!b*Bz2BIV4TczAmJ3QAlG$ z$ps;eD?&CGGK#NfR9wuc_`ty6!Q{hqA*13m14AaGFH?i}4GHDz5}Fq!G_Of$ck*tI$MNKn~#@^0SJCC`;zIwx{=R;MM02J;3ET&xFab2ff-0Y5fb~r3Z%cVF>nfe zWEY&A$0Dn>38<%mA&en_5u_Ux1Th3LnnT3{m?tx`sw$hru)@@Y3x~0oFhh9q4Bo;F z44e#d3}H-@|Fg)j$paYyoRhOzl|`9CG#PL4rdA~9l%}M{m*y3xmT0m~-pi_|##$r` z)LtY4BIJO?E!MQm)SQ%CtYwKgrK!aro7B|QChM`u+G_&YLO@*X4k>{EC7iBFxODP#u-@U|pTM&q^csiuM`i{|SH{WP*|sujY|dr( zW8~HbngF)0NNw^l4nTJjJ$&B0*?IHccV=F+ECqeL>wI2@ z-{#5nsn_$q&pa{z(+DNG?H*-=JShcPRi+Y19c&R@Y8pY`*;FrBM>2c@J6 z?|B41Xo_sG1r1!%psHAQe*7lvliu)Y*pM#t)&}N>pB!42^^%WqUaDh_;b+Fv_GP(i zwH_VcDA@utYhcoKOcG7-(lvY{h(3!WhC~P{JX>(nj~L>|3@fm&8tXC(XNnz0;dn8k zBRaYYXUmlK0TU7ElcSBNFgT5|mzm9z%$BbBVN8yaSZ|fs8WdZLVtp_x$N3i&t3y$K z4rRH4tB_S3kqLu|8cb&@LdHb|2+ag~mQWC;BwEOJ1W0Jb3Ka#du&A`Fe)L%P;FEIg z$PQ%@5lYjBFzZA+*-?T{*oA;iExK1U(YAv?H4z4(6H#5eZgLWw%lE*d8Vhc(l@kOT zY8?C72`9sKfc(^{GkGha0-nqRKpE#c=d- zeI=|P+O$^w7At%d?rIm|BX7U~(oE?*zx3~TX_Nx}3XYnGY=fCz_^$PY9px+Dagj;e znTb@|#z#7x2*7|RocK=xO=hg=e9p@Br&8%$E^lL&REkP&J%Sm})%#oB z36*G=N)3O8Hd^wNqmlVrm*voE6K?*tYQ)sGl?ro;e!>oIic<`MUJW{Bl^&ozyjk zWBgsWTHVsW^7C82jNmMr;6*9SRus!!bXdwPvs9SmS^f&<`37i`Pn9$H%3wQgFr*16 zNpJZa7>=26Ao|>R7Ta~E_jJbjKE>W)rUu)NDfsNj7lzT%;0SpUMilBHxalB<@beLC zvTq7Yo6T#jq|v<6N~W9Fdlc>g6H!=o#sgD$uZ-79%;H66Nw_bnZ z1~=q|+|f^qD7`v`VCv#D+4Lp&f?DV3~mTJ+KFV|O>t9L{W zH#R4d;vs8A<4?T(Yv<6hNY$fBgS2zt+<(KM%nR^cxd~O(2PIX}T^FRH(r*(}g$;Eq z)YcY7rEFD}kO>mCYcAX=8e%$;K zecDw1CxIaDVwB)`FIsBWOgP{I+!Si%XUxq_HVxPh1l$ma!O zzAy%3#vo>Kh8&I({vehTfiQ*=!O78#x{TW6Y zjEp*yfACM_6`ANVQ+TT9Rc^V9EOL`43dqZeOmyzGy~rZ*QJR5~@uLg_ujs^(naWcm Quky%WWRc%|L_nSi0Oc}JJpcdz delta 321 zcmeBFf2_)@&Bx2d00er+zGN=g$jijY=rdV>QC%g7se}WFIfEEWxPX{DjKP>Oh*_K= zhogiih^2%#jG=^Yayp|fQyb&t{p@m+w=mA09Lyv*`6mbWw8{B|G zl5!2+H^gNo_wz_@W@o?8#xxJ&G&a8FKzkQ*2v45R#4-6MpAIhr&~dyV#|1M+Lu7h5 jIX3(9J2EoLPVN_&h;X2;AjE;vAP34I9Jo14P@V|@KMGXs diff --git a/backend/app/schemas/model.py b/backend/app/schemas/model.py index 722f59d..0fc3de4 100644 --- a/backend/app/schemas/model.py +++ b/backend/app/schemas/model.py @@ -10,6 +10,7 @@ class ModelCreate(BaseModel): brand: str base_price: Optional[Decimal] = None sizes: Optional[List[str]] = [] + stock: Optional[int] = None description: Optional[str] = None @@ -19,6 +20,7 @@ class ModelUpdate(BaseModel): brand: Optional[str] = None base_price: Optional[Decimal] = None sizes: Optional[List[str]] = None + stock: Optional[int] = None description: Optional[str] = None @@ -29,6 +31,7 @@ class ModelResponse(BaseModel): brand: str base_price: Optional[Decimal] sizes: Optional[List[str]] + stock: Optional[int] description: Optional[str] created_at: datetime diff --git a/backend/app/schemas/product.py b/backend/app/schemas/product.py index 6015b63..7e2029a 100644 --- a/backend/app/schemas/product.py +++ b/backend/app/schemas/product.py @@ -14,10 +14,10 @@ class ProductCreate(BaseModel): model_id: Optional[int] = None gender: str # men, women brand: str - sizes: List[str] + sizes: Optional[List[str]] = [] colors: Optional[List[str]] = [] - stock: int - images: List[str] + stock: Optional[int] = None + images: Optional[List[str]] = [] is_featured: bool = False is_on_sale: bool = False override_price: Optional[Decimal] = None @@ -55,10 +55,10 @@ class ProductResponse(BaseModel): model_id: Optional[int] gender: str brand: str - sizes: List[str] + sizes: Optional[List[str]] colors: Optional[List[str]] - stock: int - images: List[str] + stock: Optional[int] + images: Optional[List[str]] is_featured: bool is_on_sale: bool override_price: Optional[Decimal] diff --git a/backend/app/services/__pycache__/cart.cpython-314.pyc b/backend/app/services/__pycache__/cart.cpython-314.pyc index 038f8c2903dfdefb6b49613d6b4a864df0531ee4..ded38cbce3628ed5f4159368ca94dac5c7938949 100644 GIT binary patch delta 705 zcmZ8fOK1~O6rG!7K27tPNyjFRrm;yUVQQOzX@dkmNj5eTu>=`Jgb*^74jK$E<6bx7 zQh5b;VnMK3D3)}uLBXvRMF>(TxVIuzbme_XgU|=>+sI-{kUBOBn64_f z;2N^jfoLflcbqd_%TbzkYr>@R9SUZ!F1EjkQ&c`$(O*xwFHw}o*pIq zx28Z}h{b}i07EUtnL>gy+1+RjR|X4Rc~e{9$fd7EvYOeRmzC(`7221y8S zTLSu8dYRSdHmeaxT#a66kd!BxG&(qHM4rIFzeD|Pu3ROcoyoHV`i7+r2XbjljFg(Q1 z{6z*i25EXRFvFilH%r@r&#*|#!5oz6uA+9=f^z^m?a5JLP>`nIBPMMG1PD`PF zuXQJ;-J8XFtYF-PxVSId0w3tM)|Pmz`}6V=O#DUBmSy2;2YPp14LZGGB;09SGqSLN zkBsG$@u1foD2L2$0zVk1y@_qGvALZfv0AN`N+p${@?dH`2s(qU4m;F2Qdr$S5_tZ^ zOUKHOt;SRPd2ToN(P|8D?KMZu7cJ5-l^jDoJWLiqUZjC~_{G6>O&G z645{n`{}Y=A+L`+>3z70Z<0l5k=G6%XNpi=rWS?al>aAMU@eMu4d2b2kvC}H!lRkD sOZ*U%5l*4_c*R{+X5eo-c|Bugb1Qh*-?*-B;}?5T((iEGz;rhM2QUP9>Hq)$ diff --git a/backend/app/services/__pycache__/order.cpython-314.pyc b/backend/app/services/__pycache__/order.cpython-314.pyc index f6681f46addd06d18d0dab5ff69415477f0abad8..4604d3ed39b4633a1ef4858a42e6e9b7bf2b2f89 100644 GIT binary patch delta 882 zcmYjQO-~a+7@jG+-R*vMTNK>NN5O8}P`&~LQ%k`Th!VlFjmrhol(vZ^5XeyOc=Jf| z>d_byh!+nUP9`4w0hkC9Ylw-7e*g@iS7)~6W0HN|XWn_{dEeRD{cW8@wJuGS5FL*n z{arMCAGEw@vIiS~5QUd>CoF8Qx#5%ShCT6c*7>5lf>XA!*ubzpR2td*^c@dL{l)IR zh8a(*|k4Qe70(9w(fAf zoFip0hgM|=We4E75Y**CT+_E9nj2zvt(5Y9H2DglRb_tonw{T~vo~Rj3#!;rTqE_u zNK!A2U_@_*tk4JB-bP-ral(W*LKD2zLvZXBU_)rc3dlkWZh^QE#VME-B68}YOg2sX z8p8M8gSkzOU(pHHvvIv=xf8_$5E7@9BWZc1R9?!L)|XeIERNz%*bz5x9SP40YlTXM zkak+)h$}4?D}N|BTcm2%#dlL?yg(Pu5SYf1rZB?jJ4eO9W8>4U`drdc(teZx95 zVqbGltGR{!+`_k9X=CaqB;>x`qWlEo2z*qMI0ro}m0Tn$^(Ra;tF{jttkTiAvQt_F=4p_^@$f;xTe4j#OC zawmnaH%}AiQP6@Q48)5dC^8ify9nYRurY^t()UtB^k5*L@8^BLpYP{=Umn>^e>JuJ znksYj`8NN(^GtfGHTanv=vy3x`{y@Vc+_>mn&O1F(x)@_yRakJW{piB!#|HoBU^8` zL|j+z^rw8M`C%(!$u?Mu9kfE0YH7BR7i_+sL&F!()!WAKAfUP)wq5t@W@PXfeBEIKS$)f8}A99%&Z3Q7vo;c)X zTxKg4pN+dKj?3=$_!56Dfj0taT=N&8gl_@|q3p~&7Zsu6vQ*XUaM?d6l}EXZj!o$# zzK|+l;7{qELIf(^5L%sP4<81nhgA}$2@+wTJEi6V>5)t%n z6f*o^f-%awJO$GvyJ*-5UvBpri=@>ijf*!Mofes)*-2c9jNohSIsG8F!k?qlG?x*{ zU*_1392QT}beT$D`k5^;@RDAEDn8afmmKaP?$OzsfgHi=oqiJ!D)~dK3 zT|H7@(hvi7^nnN4iZBUDF<`eim|r@2b+NT{rG1Q?#V4`xIQ;{5<^kY1>_`AL_#sK4 GJNn`CJGoVx|-00)b3)dEi}`ucp%z% z(u={2CdLzqCyj|3FUENA-~nTT6p`wN#0!4_gdjKco23|xlg#^O-uGrE^Szn-t?$}A zipT9l(7Z#xlKqa?o@uXW>P?72by|lGK7Y&aow>W`}5lzPSdFM^Rkj zk%}XzyQFL>k(!859b`sq)Z?t>hrKGMjeHetaF*9(xTvtVHiA2l$_-#1Wo#;aAXd{I z(MeZbeo(rxoA&b({Vh7^j!U@Bna=c-rl-cWTsrd%$$}{)V%eBs5E6v_OkPXI4Z^db zEv9K^S$ry;P8y~_^a-M86EaU+kRs9FOP*FLvq@0-z6{Y5a)VwUU%uj!r`84zSR(@zz z^cTd|$HxKQEI4hrS7EC=tkv77f|&roOn~qrY=w{D1vTDzAM!%Cz5(fGwnBG zshpl=x8wt0Bfajfx!n<LhJ70G^3Wb6sD>uKd}b71cky(pvcJ{E5Y}tHp3)DV&%M z7JW*=r@TF~=u?(`Cl@DYwh@}b1Kh@LSV!nQcaej@$W0bhE8f{$HHw(Yx9FO7N|?Wch5dvnL@!#Mo(Q6*;| z0wPSG_$VHxK`B+u-X39>AxuhGnsm^Yk{?Itywp|)zj(_B?`Eq`z?Iuo4qUc@0Ia|K E2TyVEdH?_b delta 805 zcmYjPO=uHA6rS15=4YGVCN0}kt*x?{L|RR%xx`A8VpS}mtr2RZjk_C6No<*H)SErl ziwNUEQLx}er3liCh!;V;C}L@CaVhA*tI%RgPlE4l6LDa^H}k!3hBx25H}SiN{0YBL zCNO?ZZk5aKSN&&U!_$r0`gdu{_Q93U}a_&JG5%mH^< z4!x|ImRQtv%c)IQt7X%2F{8wc+O?R-ydYtRQ77wHCuZ2RUf0YT7Nn~CnSu$w#*CtA zG?T0!$R3!r48SZ|4=GEb_=EJ5%)^ZIXaLrW<1Hc`i=(8^mh2E2O#whtAS|4=!+Y=s zb}OToR|?wKQQem8T7(re$3KhK`L95ffAqF>=7rq?8b$+-|GU#fx&7fAWP&;_z6ET0Y~JR;la8ME!crFk?=J zFJky2ODda2wPv}1FPbJ}M=_HG0F~{iio|h*6o4g_%0@-Eq#MOb-KgPvR8{4Z{*DVL zU~M~f1r}&#&wO$@l$<^Hy`}A2ApAp7*ObJPl6Zaai;`GYPOd3~OUmG?a^{YAPFi;d z?#c7VR^72pk#r1E{>T4qWrE-R%c=rV3qBF$UN$--^WdK$C9=;TyN3E*jj4c&Kxe I0N5w~0&q;XhyVZp diff --git a/backend/app/services/cart.py b/backend/app/services/cart.py index 1ce53ee..0dfcd01 100644 --- a/backend/app/services/cart.py +++ b/backend/app/services/cart.py @@ -36,7 +36,8 @@ def add_to_cart(db: Session, user_id: int, item: CartItemCreate) -> CartItem: db.refresh(existing_item) return existing_item - cart_item = CartItem(cart_id=cart.id, **item.dict()) + item_data = item.model_dump() if hasattr(item, 'model_dump') else item.dict() + cart_item = CartItem(cart_id=cart.id, **item_data) db.add(cart_item) db.commit() db.refresh(cart_item) diff --git a/backend/app/services/order.py b/backend/app/services/order.py index 18e72bc..bb0ce0d 100644 --- a/backend/app/services/order.py +++ b/backend/app/services/order.py @@ -29,12 +29,13 @@ def create_order(db: Session, user_id: int, order_data: OrderCreate) -> Optional order_number = f"ORD-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:6].upper()}" + order_dict = order_data.model_dump() if hasattr(order_data, 'model_dump') else order_data.dict() order = Order( user_id=user_id, order_number=order_number, status="pending", total_amount=total_amount, - **order_data.dict(), + **order_dict, ) db.add(order) diff --git a/backend/app/services/product.py b/backend/app/services/product.py index 03c1dd6..731aeed 100644 --- a/backend/app/services/product.py +++ b/backend/app/services/product.py @@ -32,7 +32,8 @@ def get_product_by_id(db: Session, product_id: int) -> Optional[Product]: def create_product(db: Session, product: ProductCreate) -> Product: - db_product = Product(**product.dict()) + product_data = product.model_dump() if hasattr(product, 'model_dump') else product.dict() + db_product = Product(**product_data) db.add(db_product) db.commit() db.refresh(db_product) @@ -44,7 +45,8 @@ def update_product(db: Session, product_id: int, product_update: ProductUpdate) if not db_product: return None - for field, value in product_update.dict(exclude_unset=True).items(): + update_data = product_update.model_dump(exclude_unset=True) if hasattr(product_update, 'model_dump') else product_update.dict(exclude_unset=True) + for field, value in update_data.items(): setattr(db_product, field, value) db.commit() diff --git a/backend/migrations/003_add_stock_to_model.sql b/backend/migrations/003_add_stock_to_model.sql new file mode 100644 index 0000000..c3d5929 --- /dev/null +++ b/backend/migrations/003_add_stock_to_model.sql @@ -0,0 +1,2 @@ +-- Add stock column to model table +ALTER TABLE model ADD COLUMN IF NOT EXISTS stock INTEGER DEFAULT NULL; diff --git a/backend/migrations/004_make_product_stock_nullable.sql b/backend/migrations/004_make_product_stock_nullable.sql new file mode 100644 index 0000000..2438639 --- /dev/null +++ b/backend/migrations/004_make_product_stock_nullable.sql @@ -0,0 +1,3 @@ +-- Make product stock column nullable (allow hiding stock from customers) +ALTER TABLE product ALTER COLUMN stock DROP DEFAULT; +ALTER TABLE product ALTER COLUMN stock DROP NOT NULL; diff --git a/frontend/src/components/ProductCard.jsx b/frontend/src/components/ProductCard.jsx index 533b3bb..f03d8ee 100644 --- a/frontend/src/components/ProductCard.jsx +++ b/frontend/src/components/ProductCard.jsx @@ -28,20 +28,22 @@ export default function ProductCard({ product }) {
{product.discount_price ? ( <> - ${product.price.toFixed(2)} - ${product.discount_price.toFixed(2)} + ₪{product.price.toFixed(2)} + ₪{product.discount_price.toFixed(2)} ) : ( - ${price.toFixed(2)} + ₪{price.toFixed(2)} )}
-

- {product.stock > 0 ? ( - In Stock - ) : ( - Out of Stock - )} -

+ {product.stock !== null && ( +

+ {product.stock > 0 ? ( + In Stock + ) : ( + Out of Stock + )} +

+ )} View Details diff --git a/frontend/src/components/SearchBar.jsx b/frontend/src/components/SearchBar.jsx index 8c87531..56fbec2 100644 --- a/frontend/src/components/SearchBar.jsx +++ b/frontend/src/components/SearchBar.jsx @@ -40,7 +40,7 @@ export default function SearchBar() { {results.map((product) => ( {product.name} - ${product.price.toFixed(2)} + ₪{product.price.toFixed(2)} ))} diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 970d141..dbced76 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -52,6 +52,17 @@ export default function Admin() { const [editingBrand, setEditingBrand] = useState(null) const [brandFormData, setBrandFormData] = useState({ name: '' }) const [brandsList, setBrandsList] = useState([]) // Separate list for brand management + const [showModelForm, setShowModelForm] = useState(false) + const [editingModel, setEditingModel] = useState(null) + const [modelFormData, setModelFormData] = useState({ + name: '', + category_id: '', + brand: '', + base_price: '', + sizes: '', + stock: '', + description: '', + }) // Redirect if not admin useEffect(() => { @@ -201,8 +212,8 @@ export default function Admin() { discount_price: formData.discount_price ? parseFloat(formData.discount_price) : null, category_id: parseInt(formData.category_id), model_id: formData.model_id ? parseInt(formData.model_id) : null, - stock: parseInt(formData.stock), - sizes: formData.sizes ? formData.sizes.split(',').map(s => s.trim()) : null, + stock: formData.stock ? parseInt(formData.stock) : null, + sizes: formData.sizes ? formData.sizes.split(',').map(s => s.trim()).filter(s => s) : [], images: formData.images.split(',').map(i => i.trim()).filter(i => i), override_price: formData.override_price ? parseFloat(formData.override_price) : null, override_sizes: formData.override_sizes ? formData.override_sizes.split(',').map(s => s.trim()) : null, @@ -505,6 +516,94 @@ export default function Admin() { } } + // Model management functions + const handleModelChange = (e) => { + const { name, value } = e.target + setModelFormData({ ...modelFormData, [name]: value }) + } + + const handleModelSubmit = async (e) => { + e.preventDefault() + + const modelData = { + ...modelFormData, + category_id: parseInt(modelFormData.category_id), + base_price: modelFormData.base_price ? parseFloat(modelFormData.base_price) : null, + sizes: modelFormData.sizes ? modelFormData.sizes.split(',').map(s => s.trim()) : [], + stock: modelFormData.stock ? parseInt(modelFormData.stock) : null, + } + + try { + if (editingModel) { + await api.put(`/models/${editingModel.id}`, modelData) + alert('Model updated successfully!') + } else { + await api.post('/models', modelData) + alert('Model created successfully!') + } + setShowModelForm(false) + setEditingModel(null) + resetModelForm() + fetchModels() + } catch (error) { + console.error('Error saving model:', error) + alert('Error saving model: ' + (error.response?.data?.detail || 'Unknown error')) + } + } + + const handleModelEdit = (model) => { + setEditingModel(model) + setModelFormData({ + name: model.name || '', + category_id: model.category_id || '', + brand: model.brand || '', + base_price: model.base_price || '', + sizes: Array.isArray(model.sizes) ? model.sizes.join(', ') : '', + stock: model.stock || '', + description: model.description || '', + }) + setShowModelForm(true) + setTimeout(() => { + window.scrollTo({ top: 0, behavior: 'smooth' }) + }, 100) + } + + const handleModelDelete = async (id) => { + if (!confirm('Are you sure you want to delete this model? This will unlink all associated products.')) return + + try { + await api.delete(`/models/${id}`) + alert('Model deleted successfully!') + fetchModels() + } catch (error) { + console.error('Error deleting model:', error) + alert('Error deleting model: ' + (error.response?.data?.detail || 'Unknown error')) + } + } + + const resetModelForm = () => { + setModelFormData({ + name: '', + category_id: '', + brand: '', + base_price: '', + sizes: '', + stock: '', + description: '', + }) + } + + const handleModelCancel = () => { + setShowModelForm(false) + setEditingModel(null) + resetModelForm() + } + + const getCategoryName = (categoryId) => { + const category = categories.find(c => c.id === categoryId) + return category ? category.name : 'Unknown' + } + if (!user?.is_admin) { return null } @@ -560,18 +659,21 @@ export default function Admin() { > Brands - setActiveTab('models')} style={{ - display: 'inline-block', padding: '0.75rem 1.5rem', - textDecoration: 'none', - color: '#333', + marginRight: '0.5rem', + border: 'none', + borderBottom: activeTab === 'models' ? '3px solid #007bff' : '3px solid transparent', + backgroundColor: 'transparent', + cursor: 'pointer', + fontWeight: activeTab === 'models' ? 'bold' : 'normal', fontSize: '1rem' }} > Models - + {/* Products Section */} @@ -771,8 +873,8 @@ export default function Admin() {
- - + +
@@ -936,8 +1038,8 @@ export default function Admin() { {product.slug || '-'} - ${product.price} - {product.stock} + ₪{product.price} + {product.stock ?? 'Hidden'} {product.is_featured ? '✓' : '-'} {product.is_on_sale ? '✓' : '-'} @@ -1219,6 +1321,199 @@ export default function Admin() {
)} + + {/* Models Section */} + {activeTab === 'models' && ( + <> +
+

Manage Models

+ +
+ + {showModelForm && ( +
+

{editingModel ? 'Edit Model' : 'Create New Model'}

+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +