From 1873875093e1a28b65696b445f392c1df81805d0 Mon Sep 17 00:00:00 2001 From: Vin Bui Date: Thu, 12 Dec 2024 06:49:04 -0500 Subject: [PATCH] WEB-16: Finish logic for all past announcements --- package.json | 4 +- public/app-icons/Scooped.png | Bin 21491 -> 0 bytes src/app/past/page.tsx | 24 ++- .../announcement/announcementBanner.tsx | 2 +- .../announcement/announcementCell.tsx | 40 ++-- .../announcement/announcementForm.tsx | 18 +- .../announcement/announcementModal.tsx | 18 +- src/components/common/navBar.tsx | 4 +- src/components/landing/landing.tsx | 2 +- .../landing/landingActiveSection.tsx | 2 +- .../landing/landingCreateAnnouncement.tsx | 4 +- src/components/landing/landingPastSection.tsx | 17 +- .../landing/landingUpcomingSection.tsx | 2 +- src/components/past/past.tsx | 204 ++++++++++++++++++ src/components/past/pastAnnouncementCell.tsx | 84 ++++++++ src/components/past/pastFilter.tsx | 63 ++++++ src/components/system/ButtonSecondary1.tsx | 26 --- .../buttonPrimary1.tsx} | 17 +- .../buttonPrimary2.tsx} | 11 +- .../buttonPrimary3.tsx} | 7 +- .../system/button/buttonSecondary1.tsx | 29 +++ .../buttonSecondary2.tsx} | 9 +- .../buttonTertiary.tsx} | 13 +- src/components/system/divider.tsx | 14 ++ .../system/input/inputDatePicker.tsx | 7 +- .../system/input/inputMultiselect.tsx | 6 +- src/components/system/input/inputSearch.tsx | 26 +++ src/components/system/input/inputSelect.tsx | 27 +++ src/components/system/input/inputText.tsx | 4 +- src/components/ui/select.tsx | 144 +++++++++++++ src/icons/AppIcon.tsx | 4 +- src/icons/CheckIcon.tsx | 9 - src/icons/ChevronDownIcon.tsx | 9 - src/icons/filterIcon.tsx | 9 + src/icons/searchIcon.tsx | 13 ++ src/models/announcement.ts | 2 +- src/models/{ => enums}/appName.ts | 5 - src/models/enums/dateFormat.ts | 13 ++ src/models/enums/sortType.ts | 24 +++ src/utils/constants.ts | 1 + src/utils/dummy.ts | 45 ++++ src/utils/utils.ts | 69 ++++-- tests/utils.test.ts | 150 +++++-------- yarn.lock | 111 ++++++---- 44 files changed, 1003 insertions(+), 289 deletions(-) delete mode 100644 public/app-icons/Scooped.png create mode 100644 src/components/past/past.tsx create mode 100644 src/components/past/pastAnnouncementCell.tsx create mode 100644 src/components/past/pastFilter.tsx delete mode 100644 src/components/system/ButtonSecondary1.tsx rename src/components/system/{ButtonPrimary1.tsx => button/buttonPrimary1.tsx} (59%) rename src/components/system/{ButtonPrimary2.tsx => button/buttonPrimary2.tsx} (57%) rename src/components/system/{ButtonPrimary3.tsx => button/buttonPrimary3.tsx} (65%) create mode 100644 src/components/system/button/buttonSecondary1.tsx rename src/components/system/{ButtonSecondary2.tsx => button/buttonSecondary2.tsx} (56%) rename src/components/system/{ButtonTertiary.tsx => button/buttonTertiary.tsx} (51%) create mode 100644 src/components/system/divider.tsx create mode 100644 src/components/system/input/inputSearch.tsx create mode 100644 src/components/system/input/inputSelect.tsx create mode 100644 src/components/ui/select.tsx delete mode 100644 src/icons/CheckIcon.tsx delete mode 100644 src/icons/ChevronDownIcon.tsx create mode 100644 src/icons/filterIcon.tsx create mode 100644 src/icons/searchIcon.tsx rename src/models/{ => enums}/appName.ts (88%) create mode 100644 src/models/enums/dateFormat.ts create mode 100644 src/models/enums/sortType.ts create mode 100644 src/utils/dummy.ts diff --git a/package.json b/package.json index 3ad77ef..cdac08b 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,12 @@ "test": "jest" }, "dependencies": { - "@headlessui/react": "^2.1.9", - "@headlessui/tailwindcss": "^0.2.1", "@nextui-org/date-picker": "^2.1.8", "@nextui-org/theme": "^2.2.11", "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-progress": "^1.1.0", + "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-query": "^5.51.5", "axios": "^1.7.2", @@ -32,6 +31,7 @@ "react-dom": "^18", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", + "uuid": "^11.0.3", "zustand": "^4.5.4" }, "devDependencies": { diff --git a/public/app-icons/Scooped.png b/public/app-icons/Scooped.png deleted file mode 100644 index 54f7b9241778b43cc49acb0391098bfe963feb33..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21491 zcmeJFXH?Y7^9Km`48f5g14vNPASxX-BY=elJp6Mj5q==VN5p=T@mLc&OvZkq^#trE35WGYTS?eY zDl5hnbeoF({O_RuGUC4y@!zZQUmXFu@PCW4)Z`#v_VS~Y{OY`7Q{q%-m0tNpeetG^ zUuAgiLjT=ZX`8PD`X<|Lrk3x?JlNhxx4M#z(BL+NK1Fb^CO)>2n@LYt9Z&E6#bR<$ znD~bF@T1W7%HYvyO*SOF4u=$+dA=-@J1G-c@7q^i8U@=1S*iK@yD_>bQ3}b+d;~=E zSdXa9kp>i3>zH5WIbXwfj_Bq$`K)_XUVO&7%s&gEx1M!M*(;MpY_|QbOHU|@s-$aF zlijkTT!@MTBX(UtWVOa>dAkTROJjWt?_=EV#C^)^@dNFM93YBA?9wU>eLsmECx2cu(Y$`6s1Vl5K7(vU7yAMlo*a2v{Y2saB_T~w;aK=bkY}xmVi0&eD@ex zKY4DQRFze58~2b!f4A})F?Ftt#O5#S;hWbJC8=%*jl?39dZJb0vUxZ6WYu)!dx2|+ z_KSe1Y4$9WV|slaZ4!K=rML>epVBd`h{n}xhIX0t6l4d^kM`v+uG>xzsa$xglW4iP z9@U3!K*={REXGFmBbR-zCp*5G-X6u{Y?ol0+4gqKma3Gy_id;Nkscnw!$IE{m5vSJ z5pAP79{t{~!u0&oHzZN~_^sDW3;{>`6HdjtdW*K;%;{E^4uYO8&bAbmuQ#h^vG~4- zL!YG(a{MqL)*(;lMRC9l1tq~TTE2fGqRqk3hY=wZ`o7=AH5v-aRs6d9HDX0P@dDQKN9N6LNoEo_wY(NWU+CWVIff^Ai{jmSfRbR!PUCFPRMV} zrQ);nm%$h~xg`)?3*U1-dQHj()LdcE&`(U<1Q@BSLL{PPmS1#xEKA-zPM6n7Iu)^{2SFSMrh=t*DU+ZBbN~)9!bBYnLxRUUE8-3j7kv9uMNxTRpO!vR$*=w{KEBI}qa_VN9j^W0M zN}>BF_xfxlY233Xkl}o2Oko{$ z6On{lZRRl{XRKf6XsPox>6)An#YF%kX+}WvTk}_px*;8vP2>bP>v*p3u9B<~O?$$? z?S)UT!>9V)l?^)iyLGAlm4&1%w!K6m!~it*f>W!pIEOBVo!XWR7Z{_CGJCD@NEt3> zxAT)F%F4<`7N#wAOTLR_aa0I3vt9v5HHU$hTC*5+`vz5*3YN`mM18#TXEsFk#iRcF z=BbRLJ;%8^N@2@KXXib&KE{k|pF$)V9P&6;kk0fR*U>;!yQq23@+6lir}z*mItQAh z&8TcCdsd=$n134$;bk&KD_y?FZ#G`P_m=+klMUbuoGYD(K zy?(O9)56DsOW{ve`Ol_}bgCdbUVgmG1leY=BI-|_Q&z4F3j#jL+P|JmMu(1p0%fom zL^vQ%(c~8$D-zwy9yqEZA0l5)W0H@V3lkQq$FCbDMIi+lJUV=0R^q}D%k>KbooAn` zSV25=2jZHutLx0YS5|)fq=)&di1!91BU&5*krZ1g$p;mQlb|Y}7y2LCOdzZ^?}X;_ zid;FNA=Hy{TIR@BmKELp6WCHZf{$>c#)BbKAQGv~L`2q2W73G#UR$!*GsfT?;Myv1 z01=00C4_JKTeocB8Xo_05d=PkgsA6$TTd?9k^1?|cus(k5#EDCTT_;JxD2>hZcXOA zP{R}93~b-QVi7-e6byIECZ%c<6%v!+k}Ni7Yq^H4Y-Z zr=-kJ5M6i(!7Om_-PSN%p#rh#G(zUmh{}6rb=_Z)^3)Ob)E|>Q<#2!Y8o+p9;JJ53Zw}Kx+&lIhcw-BPkjaN=5x|+w*ExYtTjoG&fU`|o zfnMPEZ7VL8jG&FA7*q`lUWf)7bY~^{(+E;7Jgm2dJ-OIesrL!UcB>&1mvys&!b#r> zJXq^ThJG^x!er5PsXJAeVDBtZvH%_l1H-8z1$&4&j$}^cvL4y?!&o=@EY^&DM1>LX zJFre_&}p_tDb4rrCdsk23)He5JRgwQ;sRkpN7ZR6!G)WZHCY9RxPYy+vB%}bpQ_2U zmxAJQ=+s?=*pJRi*bXM@pl#M31n1u;)4+CRtO;XNOMM?tV8 z84lvO38s>+Spmf(QLqzWxNWID$<`a+;t1!3b;Is{{h=EPa$KyI;1(fT29Sk-EJrXt za$19le`^WH69`2E&*C!G*ok#G=bH`8+}AupC~g}L?FE%!JNK`iV6j29Cjk?J&LjH& zp(pD9Kxp%eC7!0F!Qqo8Jo}`6?<-v<@&(l1`qXbJS!z$gbmlSP&#~ z#DRsdOxUolf`2OxyJ@g? zoXGVCLc3r;!WF{o9aU=Oz5>T=<%rz*ek8=h+;^3&bk%-(5!3d1d%8nI%!l?w%Wf|ZS zV@>ZQV{p};)Ohj?%hVaM$G#TI&SD z_->~ot|gdpXx-nI*c(*29xSd7s#!1wuT`?7j>!(V1ofcs$ZpcRv-X1Z11aG|tbaEm zQ04L7EG^#W+OGjs$3fw-gD?=LWZnK1@_(8}M0F^oS9gd!$+y5EBBK2X?94I#9~ENT zN|#1I`TuVBcrDB>tcP>C8A1gM`3>~jPN3FypQoJdSO`^-3%E!v2VxHbu^G~dpLahF zdfg&ug_^k$SsS(s5`f&K-B=?Ak#da_7C3OAMWEv-kk%0)Sc(!YSL^o>i6jXeIv(;< z2Z;!*2`|Q2tRDx~@74(Z0_Ok}!Fkzg4wy0i^)=LCtteP4E)3Hlltlf14q^~? zEQjrQGsrrpJ`TvWR3Z{>dstBpCdbFm{)ZBQ;F~Ik&D`~$B1?+^y>}OzJJi3hu9aGF zP>2fZco6h}0XDO-wZ`(*+1skRZm^MBkjRAOB_3Kv0Vw~--gW80UvLT{uxVD`KUHgHq-`iA}iag1MU~80t+yl^z!sAfFN3axBNxsBrw!1}*drWpQR%pSC{G%Zurt|nlb#6iwY9lGQUf4E=RWZg_fumKwX^dyX+1Xd zrU*{_VfR@KoXAh(#1AMb#mHADE%w}-=fF@95*yYDLcnIXzN?}8w6lRxae*~XPW1{A zL@QyG6J(?IyZ$c4+V-3F!ngLjY$v0a!yTqRO)nlpmm}y;Y=Tx!t81y+gl*9-n_q2g z|1eky?iDe@qrbg!F?@Ple?kbUoJ3@f+>t9|10_ecRZNgb<92SIcqO!$VOipR>FmC} zy-8)G8zSh_3xPuH$hAj|UOxS{Pw?>eT?EhBQ?ew-ooUQI%~}32CfX~d@~Kbzcl0hR z&%CAvt_TObyH)T@l%tJ9uNpK65wAW1d}f4_w@odo-nfDi1jKO7K0-Fli|c5^Ac6JU z37~nB&wEOdz45n^K<~f` zs^<2Rzq+d?Xu>0)6N|0%A(TXV_e=QuuiBK19y{MR5tymP zz(OzP4*bfJDpO(8BZ7h~+Kq7v`}GTQSFawIMkRtxyowNwt# z-XL2(bU+9pE~`h86o{XymIjn6HMdW;efQ%ak78-G9z(;@q=1pBBTWsN2ldI`xLOnM z_4s^<#}KX5eufEf9}(}W`!82naG6+=&cnh}loZ33bH&PuEXwa;NK6Iojc8%jsJwXl zDOFlq=HpF3$kKLbwS5cI{9+s|Vm%(BfJVUGgspz&RP(R{JAU>rvs-chECTHwWkR3PLA z3~fE$@uVB3#h5@N-8^jp4)sy{UJ@s8E<4ctC6O(n80QeO`rq^6Bx;yU%X zlb@Xob_qaMNv@GTaKG*t{}fQ!jB$P zwp|jI=H;lKc$hiDZmM~JC=9|6An@l<+?18DLI5v&#$yr01(6e??zhlXBE@hkSX3cl zd8}6otZ5>|UhX~T!@_EZuoZ880_v-%ouj4r`|{;hm_miv{WTalE1=j$NwazVBsc)q z(1qLBl!i}5`tOu1UOopFlfC`~E3TOr3wo#DNJ|JMuTgUGpm2__8~XfG3Ux_hL$+|m zDep%u(%An;z3s@V2X6m}bilcBQ#CLGJUHin6}#}^4+s;VkXI7NA|HZdM4Z7<8Q8(u z@|P-*DmYmRrL#5!GV1?uLnD#Uj0!J|)l=0}XhRRV(JC)A&^8iS3WvYXxZelob)&5> zHnqZemvsaV78Ut_5op@2wK>Cv{J!{QDv^R4GHZsI%kJf8FesJgjcq+yx|thL&_MOS z054%_6Dnd|xv+S^tTe}x`U6{s%(lj1TC}}dKV~Lr-q@7Md+caSH;}?U?3^wf7Gn3- zW97xu^4C5){hu4umXSoRNze0HidqWT=~Am#n_KEt)w-S8IH9D zOlg)6dv$i*Ruv{Ma2L7W!dB%2h-XbgTgkSvcC`0VGT$)jp8D{Db zli=LI&p{|MT9OM;Nw|gr{cRX$Y1OCRq8R|M*M8wHRp{^d628tef%b8@PZskBXiMXe zHbCu>X-nxs_dh1B{?7gYrZm3t@&q_+>vZVI5ga2>DxXeWqhi56ZK=XzxRJn#1@)M^uvY=RFMlH*u>D+= zgb67slv80sg2Rr?X@B^M3+^jm|FC1jz{&S@cZs$?sxDs)F+-DA*2bp=dpN#|6v01b zp~<3!H4ewEVWj+ZI1C}Brmqcdsqv%|AVgx<`pd=cvNDN;t>XwMVT zM%4w>poFr3m+I)*Hmo5HODes3qxjEUMi>u`VsO7Y7hCb0)=tdXnH6cQA;Jh5&VaQ! z+Q+&E9mC+r4Vbqit%u&PZQHQm^7k`8?k4=(yVVa7q3_nUg)~egQH1=O)rE;>gXv1B zY84WLWR*2S(=Zc3`!%)$akcgMv_?fqytYA(I~xu9@&==lmfaKgxwfwp&_MNwB!vTr zppoy2`tYUsT$L><9!Dd+|G z1{eIQ^lLwtE7oW6SWT2OYhlL9{SwJzXqt`FOPK1fQVfY3uOFp<4wOg{QHKT$SE;UAzEiUAE}NgMp^~Bez{F-749ZyylC8CYEXI14 zQZp8sYYl4BH%?M_STExKa`ep4s_24VT(*_gOQrR;;Dw`vjhxL`-jj1e(Xgw~W$bCW z<}P9gl|$^fiMMb~z}UO2P)CcUWj+dX!7n146v5g7@d=H+GX~`_Wkbr&_()-lf|1I^&G}5%W2Q323J{G?(JQVIx@7doMO7g?cG-X zWsqL-=t(TFBWwAUHm=o9sYcYyL*%_(gp$DcF(UX4a-w_Jvq0EcnuUr_u*T&6h;^=X zqI-MSu?}3B0R06Knj$tW3xz552Ug$G4o*(kgl>Y7sQ2*4>vaf8&-6o74?&t+f=zv-5*}VOe{G zbU61ThvA6Rnj0urqwT$=eCk+GE!_5J4ugH$;UmYvzN`h=g`#jqCTq2{ zwL<{#m_yKfi8#6PaGMgIdKxZricr)z=-J2lSNDjzT&GGrx?*G$^M+Sr!`YQ`1{R9s zkZu2j6wrl>C1T+O{L@`dijAkLUFHBeQem*Pb93H0@yve?-3kHk;CWouYD<-axto5q z{iC+^Vs$%9)k>XWrH;^=AZ>E`^c9gxH$`zL@4*cDd{Woy;Jcemo{vhUj!bnvXPau20tX52VSfj(KB2foE3b#^Acm`CjIfk z3YK$5T%j9~?QMIUxDmx>9xv$>Of0KPnBkINdn-yJzq(ky9D+WMK8 zoszm#H_FlWneDdXM!sYta2c;RJuZpf#p!xLmDF?x6{aL8N_wA58BmG$e_ak>J*E}e zlDBCf>QIgDt}eGrjLwYlg27^f1@GJ5{FOX`sVmHviJ8zA#&scZY<<49F zud6*&>kfCMWd&?y*T?7ULXULlfot4*z#=Oh-x(1n!J94Ak(I^y-u6tgfFnW%H-9Fu z`9FMQ6|3~Dcb;AmY^7~E?E2$@G0WWm-$2jlQPaRLN{g#jcWs;|o<*eNQ#Q}9s#<%h zJP@f~K2S54=0D_Z(>w9DOyYIrIFmk0PaMB8*y-h?aIw5%AdO6ohitvq)1x9H1+t~w z3~+DLZ6-41bFvv%jjxZE)+{8p8Z`Fw+9bOA4#*sPd60|!*3Cy>sTub?J8ZN98Z9dn zu1GwBTY)Jo@dn-bM`p{vBIg|hty1M!ku!*K!@E5{bOhUTN3bG|B)6sA_q2VCUN0zR z2#6$^-mdEj&D~vJ){sb~-abDgF|p|X#had3VYA$uH@@U_Qb@NpPdZW4fuZiL%Bn?i ze1+ltC|+TxtvfXp;{QEn-hQoxfn_`PtU2 zC#B|3x(iDEdsWktj9mY?0kGw$np=eM=b_3`c}V1#T7TxnSKqtkz_~^0HxVEB#b)KJ z#Qc&{@YJuJ`g;!RgHcm{(ZCc|T^)^NGe2ISt);4SldXhEO7b77&U4~w{q8>pnB`T& z1jZ~l*;)a$lX?93Kq!}V1Q*Uw>3##nb?#)A+tLGLp}tK*p1s!sNAg5&pex=cdE7xP zo5ogirz8UU{gtNjqKKqx@19V?2smwkgge2q8{M*-k!US^&guTrGO+KsY}deCm|Br$ zOBRx(EpV^aQ%uG%b*yqL=_dUS0Usu+%Yn;+bkwh)uT4rP8CP&4ot5ef^mXdzwFRA+ zp5LS2@0-?Hc6Yk8u|0y4P+9T2=x)Q29s%v@bhffId(pw1lFdny?uce*jCo+e>Qq)r$C|UuGX(4H8dZJhQ zgp`WgyZz*Or-ss<0hcL)z4(mnG@%l?i=*a9Uu~;rcMUWB)kz_ zZTdq~-1FN7EO_U7)78`*cxP;rGm%Jtw~)siFcCTi&P&{DF}CD&;lp!Wjx!Q+Ma^z5 zQ~RuPlcPh9DQYQJB};Psx~bz(F3uIXIm4GN8_M(pgO(y8dOnwC73h3w%CA z0`-Q+SKLc__w15szC1cUuMzfpqHoFNA-$UZqa<8>w_H?vK>0W&e?`fN$uZCev430a zJlEF(E-px{#XY=Gd8x#C^YtHLXF|lwQiE$9ca-AI&r52HX4=dP>HRQV?N+$qMqLn% zrk|x=1XMz)d8w)LLYdL5`!Us284bL-z@_hs2X6TmXno5Z^%pUbLxtZ}ys~}<8+*!`N7b`jIJu$~quk}w!o8{0v!&ub z%R5LS(8V!q7o8Ot-k4%gs_gkiN9E6{$95xGhg>`lKM3^?Fy|H@oE4#qi|M~@X1gsH zY+bEL#b9PEhlS4aM@ap*z=NHHmF;9PbsQ>#8vXvZr?370IxWMpg_{6$&`eQ+s z!(xYN#vs+NA~-BvM4p)Dzp9AVuXnqUd9UjW@V{)sKpLBwO~em1H~Td zui`9uJFbn~Y>v^in40MRkelM)+qp6-!|F-}=wlau=7I zr|BFeH3A`-hJyp9w?e3S&7b-!{a?TCdN&k%H8^K8zGnCorzI!B^#-h&uSV&vVwZ00 zDQsaFSe=eut4JzLm-{I=kk|B~GCfPG&G|h04fUmMYdN_xfY))hv3v=f{ zxsUO#GA=#Vg9SnZrKm7F^k-%NorVbdtF9R|pu%T`XbrF}?u$>Bm;=siCzjtRT}_7uJT)_6(fRcL6jJT)__DR-{&asQpn2s2dR z)SB<%!rmkfRYMj~v?S*`Ae?0y(XP#rQ@kW@KjZ9ok|5q0(;RKRyRw-r~%z zR*HaHVfvh|)-KP+LAMjG*3QNXY_YB;T3*hTVfKQEW{#Ce*&~yNpOqY< zys}!=0U_zXb1A(y-QAIVhTn#Ic61`1*{O?_oIJF~T-^08-H zXN<_bVl0={X0rTKr!FQg6wp3liinA5qK_C}FP zPj40QygXZy0=)+aXQJg%9Xyvolj;TL9uEA*>D&J3aWA@76j*QU$p%$YgtyevdtO|4U<$;tkYv=T*(}=SwHgOUpD=@M`Nq@m_zt`7o24 znNp~RKb68KB+~bbzr;l>wCTzL&VlWJY}UW+9Q1_+tJZXJmaTI663*q)npS<@y(ZHr zvIXli{O80q-H{T>wxs;2~|5|azO~aMvFKF!7J?1RR@~-Em z#pp_9qm=oXQDLg&v)cee$EBVP%_F>&Q;xx%a|3EZ_O2$iwGbrO5cX-RaY#PlrE8D;pm+*pzrOJBw6rAR_JbZWeE`QvkU!LRurM5zq5tE?t9!*NEJ*bv(I)(2+fk4rr*(qiH*b9c4o3@exGf)12@2`ghMt*87FSUc2$x*ua+T7=P$5(VR)0Eedx^>HId#;aYU0#*QOj8s#S%zhoXiQAe_ek0NHN^ql2JhsL zW%E#;U>0<-_rtw;4BX0_&zihbTN1W0P8llX{3xLYbtN1LG99-zM@_mq0TaYyP*dq< zvCA3=BXFw|hiZ>yU-oC$sL2Xug+{FKHLucG>L#dI$K@@OaqGIOw?gw1XJ=)VxChvrdXzrkUuMnU}#aTHm zS_28+^wYUo`CINI$j6wJkQMG7SuL0qD>&jyZw?#fAcs9YWO|-gTN97qe?>Pzw!-1F zyiV$DYjN(Dk6U_v@tWF159-_eB~IN7>vlYl3G7d=QrxN%GuU79_I8VUa0^ABbP7g| z+~XF|JL4d1!ma^WCKPUO^{teodK`nJb`L}NJir2}K7@?gH*{V%xae0vb$l_sp|saJ zFRyvt@hwz)Ls)`d=Mus#KAs6Zd+COSQZZi~RK=jZRkgxv^;Cc+ZUqfUJ#=B2?V20O z))+zoci9V6TX8~9JpI_ezYE7>()j{|$9(|bqn_sfM$26Pox}q)&AEln)RrRKAi=NX zXxfLob<^O~z2-v-TZ492=gC$ri~FXsP$j3-*ROFYpt8#GZB>}$kLB4k4posvO|JJ3 zd;@cTkM+iW*-hR6rI8u6j)TGX)YuOo1(6^>L;!pu+r;)h@X9p0nwiLPkGtb024Pg3 zUyqzu_KC6B?r<(Q-fbMWp0x!Ix*F36u4%S1&iP-hkADhVeln<^Bd>=-%%CXAUc&Uo z+tLbSJ6?v}9|!%T7o{+Skw$D0*4~BI?pO-WbY_NI z0u7Hwi~EP1Zx~^9vRw606*Rm0>-6f{N0!kSdjto#_D8Gq$U5hXj%wQzoXb!6AnbA; z&Z=F<6zBA0Po4<%zyFh>KhN7U!YHe>TxvUmY)=sDA$I*Epr~pc(=wE8gKL-!a>|~d z>CN-%of;lF%~Vov`#U-%Usl#$C>Z!uy2!ylnCF992wLLhP=_a%KDF5KVNB|p$p}c! zAL-3YMa(Z&Z02*eq3sur85L_kN+1FF zq3k`oXDdPLMr^*Uw#c&f3oymTDsKQeJ=XVSbz~La3G%h;2UE%`fFplL)lL)CXdOMC zvQWYyaj`X#LqFC$joMBa@kY1rfTk-Xh6}c;Nhe;Kk#P2wV55th8>Qfbho(b6UGpDr z6aMsYs*@8_S!O+(ImJ36HfJ|vL^W+r;OW)Rccu(d)hvH_`Yoii!+mwUPWxYU+z0)iRBT@Pp8c6(4KI)bgis`(y*VFZS7x%Gs4eVk{dT(u0t30XEvsoPn4}Qx!yJryqeJJj=Oqn9g4(kI8k+KYJg%<5&b>_2BHhm6p3D?l6>ufc?#Ux68A|7Yf&&hi!gh z5#`J#sTd;atWh|B>eUlEeOW49l47`;)De`EPhQsPgj_??&mr{taijh=`3gh5SI^s& z4Lj*fKd|l9Vc#IfBLqd{wAKruu|MZCViA@UIZ`Y!k$Yd`l6~|{eE+>~;5H1GU$rYa zdQ_qaM?k5PX=e)>Q);)USEQzt1vlh{Z+7-cuHNo1s6R2FzS$;s%6-<~z}u=-*q=)d z>~a!6xCpGsQ-o|d5UB9EsOF~X=|C6e9bGHWmBIR*IJ9~*`f{d)$I}_oXr6u@50pVz zzHIZMo|f|ve^Al7nW3*)Ds{L?`mOu2kXTF@GX%Wh6Rr#w?(HsP8KglImj#JSxtmhl z^>_J-pNF~{6X2|ftWo^!(WB%g!#uVJh1H|UbFaUBJMA@o*f@=ey^HX;ZeE9UW!hO4 zgVUH9-wAv{uY6{0jB}?SF-rw5t+Pt%*9?t02eH9NaHe6nX7!fO;Lwse6W z30Okv%H;wL@VxV$EzxHO9uE4v$#}(>mr-HtPNL+kz0yz>n76MxP2K!du~scfSZ z!^JfsCJFZ3FDwr)H?qSBUCs`QrVe zg~|X{l>Fl0Y^BZVSqfz4aBkov^nCghT6D~y%k!b2fSSrKYiW(!arfONmJU6Ufa)+W zWVw^@kow<#_f(6 zwW=YNgw9bFWdzG}0-q&)XbmMB>Fn=De2%}?xd129s4NFbs?98?{b=%@Y zxmxhS{x8k;sKV|Gl?6u}1WO$bca@b`Yk5IEb%ugS?p(wSqj`zP6^lUg*Q#_&LyM!u zU21&pSo7Ea;c(b;ccMCbOyePw*;pU_F{uazL6DQrTSD9=Lt_sZ(OMYm?XLT&pEp1U@ z6u66D%Y_7gA&GBs!Nbk2|Fq$t`KD8cnSur@FaJ=RS$!j2DGvYw=i-5djL{1k_6EEn zl-6J@f0uK9%1$g{LeP9tDq>{Ux^KKB>ce#noj-_@`^*G2^DE6oNql^NKJ$kj*)=y-#OZ zpZToZ^T))8-8zgMh2R*`O9rmf%%x9#_e~h%?jI_d2}$>%{P3hM)$|xzZN>pSvvoeU zeypT$F)P#J&mXM9MQ$_hXsg@^`?2~721?(D=7;l)LdWx}??hcz-4CpYnwRTX3VrhM$-88(ZvB^WV55dRk1O~AacRav z1G#Zgc_q!rh}QDjcdF-` z^{=+93sE|BtXl6|d)MV|LC#6+Y3B!>ieZ`O2m2_40&j~p^N%&b)57+1w!iO{RqXw7 z;O1~6CJRkjalMn35-Ujw8Xwvssp$m|g|LEB>Z_={xlH+Ly?pOO=cO691ka595bV!n zJN!6xMPDFhu9|(PwW{KBiZcFPVGVt_PjqGYZcMasjlf;z>~$N#HQ-Z;mvf3oXVJ&& zdWnYTM)JWdIo=P|zkj>kZF#Ep#r`Ly+rHmPXu4nr!*yz9u_jxdgCxQ$rq8UC5WTcV z?dO5X1?FlI^9(A4M0tIEcjeOZbV2Ju|McN;qicPWV4YCSwPJQ^c+O21s2(cY(N$tYq@QJm)r_^?Nij3k>CCL8unfp+|{nN>uQ z8jo#e>bn}x&^RkwulDS$F_jn3i~NTQKUxD`Y^ zTSiJ+n{sE6~KB))nYKJAq|1vG>pD1=y$Gjyqb5Psmou)fVj*>|tJJJbB+5 zthd-1-l108X%RL7?@;fa+O5)|*CfUnF&`s!#6pNZ?liyqK4mq4>8(!$t4QLb%)H{iKP7 z>e@+2ODh%=N!6o@iTik2{V#p7%wMW|L5UDy67^$(AuEelz{1XW$#5ju=u{ENXn6k! zWyx2hTv8Y8)9YDSm%yA7xlCx{)eW}R3V535dRYHfme891`l5*v;^)r9~BgKSV`*oYMrT zQnMUYW`$?=SA0HKy8OvG5!0aKz=hM`)$*k$N{XGnnUh<_;VFBMG1|GpQUI#p1w4cv z`F4Cb&L?Vm!Mx=1MLt5yI5DDObv0dtVd`2}rCAZ{sh5AhLFchzICAmn#ZC2G+;;1h z6CK{#ooG- z@9qghkV?H`@;qiDSD~_ZKucxbNy|$y zBj}5FW#!{>b$**(FX`^>lzrf##94}QVlUM$7(Zg(`bt&FJRbE^L`0H$E`Ey^JZ}AT zPLPFs$xo1asK)*W_C{H$>-a&#K37I(g#Y8}QMSVL1Z8k}@^#mtrt%CYuZJ%_qD=0{5sAN;Kxc5B_z4-Wyl5F%( zsWxKok3L2*(O&u1zE%~Cl`(HDzGq6U8|ur^DU`C-nKHj=wsq=Ph(%=k$2B|7%$aUy zOFpOp<%Xy43>rg;#BdR0xGin5$DncETQ(j4(BM71>LZGigKY5ijhvHlpT-U&7j>_X z8S6{9e zTJd|5xi`x;b02=2F~rWEvA{-SW}PUA?HJdS<%{16)R~Xc#Kci_{O4 zUE*3gJNr_g6n7cESwI+EK82%L3GUis{e^ecPs`Gm^rF1CM4!D8&8wj?%EJY(b%hIx zxAl*Vvj`4KB~RC$8ac3;=ZTa8WL9l|%hra=?Q+XSCRZFT40O)?V$}Qv5JBiyMF&=4 zCG;8%)>ZyS96??g4n`BK(&xYFur2 z!db5N1hGj_ghGNZUAmn}N|uju|Ldkx<3LuBBdg)`&(QCnjxGWE2(T0FWhsdyIJnCz zPv{;ab-^O=u9cS876MVX(VFXb)Nst#U@W+$CPak#TS}-^&Qw(WHVu5cw8j5dDgMUo z&jR}g-$xA_%P>AB?jt)xM~etVx`wQ97D7>DP}`Tk_;$N!VnPxtZt8Jnq|?d##md6c zsrg?xtd1~ee=d=7&Mj7@m-~$8^|7e_T+J=E#fJmZUNE6SDSW9$dleOG zD<(=lypV9z7xwV#eNmbIJ75kp^l`QpPX}5G;(ep0*G&Z3{ux5{OVmkQ zanEEt9&9Hw?O8;Jy9Z?s5GMP7Dh*G*454*s+#Q$@p?4BQ1_M^asSO~C$D3!PrU{^S z>m}j>CAzA#R2EDszvO)l5A&I59fYdMm5P8b89{Sg2Zi?5wwJUz#d7P=egj_TxI-}0 zD&6^|OM7qLOnN&$B>oLqwy=!NZ^g{vP4B9?g&fz}qLA^bcTM{Owrq@;up*ls)3O;* z{~UU2B6!IqA&A-ulKDHsA}*Vot*VRE&u9%@+^1QgW|mwx(HCTI&381E`9 zQ%rlE`?Du+Fw2o4D|g{*-~+QL^oz@Pu?jZ?91h_B-`vQ2=OI)hk=^m$GB;NQU)-$2z;Cc`Z2&#r;K{6aqy z%J%Nnk`r&Sf@(Q`sM`6SDCP$n^0&IN@GyU}3kY=hXS;uS+m)<+Z9~h4<#XRZ1Q?X7 zm@gi8`Pw(o^OOFJO}S6{-(n{W0QXv>oA3V--TzAl`EEqE`S9@E3`NM?1+hU$lLIN3|NWgYhz2U@|Aw&qFJb!sk4F6O po~W1%{P(*K|HnrDe^4S;{DPR$oG -

Past Announcements

- + + + + + ); } diff --git a/src/components/announcement/announcementBanner.tsx b/src/components/announcement/announcementBanner.tsx index 45cf3f9..e7af974 100644 --- a/src/components/announcement/announcementBanner.tsx +++ b/src/components/announcement/announcementBanner.tsx @@ -1,4 +1,4 @@ -import CrossThick from "@/icons/CrossThickIcon"; +import CrossThick from "@/icons/crossThickIcon"; import { Announcement } from "@/models/announcement"; interface Props { diff --git a/src/components/announcement/announcementCell.tsx b/src/components/announcement/announcementCell.tsx index c0ed7a7..1f9ca61 100644 --- a/src/components/announcement/announcementCell.tsx +++ b/src/components/announcement/announcementCell.tsx @@ -1,8 +1,9 @@ -import AppIcon from "@/icons/AppIcon"; -import TertiaryButton from "../system/ButtonTertiary"; +import AppIcon from "@/icons/appIcon"; +import ButtonTertiary from "../system/button/buttonTertiary"; import { Announcement } from "@/models/announcement"; import { dateInRange, filterActiveAnnouncements, formatDate } from "@/utils/utils"; import AnnouncementIndicator from "./announcementIndicator"; +import { DateFormat } from "@/models/enums/dateFormat"; interface Props { announcement: Announcement; @@ -18,31 +19,34 @@ export default function AnnouncementCell({ announcement, onClick, onEditClick }: className="flex flex-col p-6 items-start md:items-end md:flex-row justify-center gap-6 md:gap-8 self-stretch bg-neutral-white rounded-lg border border-other-stroke relative cursor-pointer" onClick={onClick} > - +
-
+

{announcement.title}

- {" "} - {formatDate(new Date(announcement.startDate))} - {formatDate(new Date(announcement.endDate))}{" "} + {formatDate(new Date(announcement.startDate), DateFormat.SHORT)} -{" "} + {formatDate(new Date(announcement.endDate), DateFormat.SHORT)}

- {isActive ? : null} + {isActive ? ( + + ) : null}
-
- {announcement.apps.map((app) => ( - - ))} +
+
+ {announcement.apps.map((app) => ( + + ))} +
+ {dateInRange(new Date(announcement.startDate), new Date(announcement.endDate), new Date()) ? ( + + ) : null}
- {isActive ? : null} + {isActive ? ( + + ) : null}
- {dateInRange(new Date(announcement.startDate), new Date(announcement.endDate), new Date()) ? ( - - ) : null}
); } diff --git a/src/components/announcement/announcementForm.tsx b/src/components/announcement/announcementForm.tsx index 0ed169a..ae3ca62 100644 --- a/src/components/announcement/announcementForm.tsx +++ b/src/components/announcement/announcementForm.tsx @@ -1,13 +1,13 @@ -import CrossThinIcon from "@/icons/CrossThinIcon"; -import SpeakerIcon from "@/icons/SpeakerIcon"; +import CrossThinIcon from "@/icons/crossThinIcon"; +import SpeakerIcon from "@/icons/speakerIcon"; import AnnouncementBanner from "./announcementBanner"; -import { useMemo, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { Announcement } from "@/models/announcement"; import InputText from "../system/input/inputText"; import InputDatePicker from "../system/input/inputDatePicker"; import InputMultiSelect from "../system/input/inputMultiselect"; import InputUpload from "../system/input/inputUpload"; -import ButtonPrimary1 from "../system/ButtonPrimary1"; +import ButtonPrimary1 from "../system/button/buttonPrimary1"; import { useUserStore } from "@/stores/useUserStore"; import { addDays } from "date-fns"; @@ -46,9 +46,11 @@ export default function AnnouncementForm({ onClose, editingAnnouncement }: Props () => announcement.apps.length !== 0 && announcement.body !== "" && + announcement.endDate && announcement.endDate !== "" && announcement.imageUrl !== "" && announcement.link !== "" && + announcement.startDate && announcement.startDate !== "" && announcement.title !== "", [announcement] @@ -59,7 +61,7 @@ export default function AnnouncementForm({ onClose, editingAnnouncement }: Props if (!user) return; try { - console.log("scheduling"); + console.log("Scheduling", announcement); } catch (err) { console.error(err); } @@ -110,8 +112,8 @@ export default function AnnouncementForm({ onClose, editingAnnouncement }: Props to: new Date(announcement.endDate), }} setDateRange={(dateRange) => { - handleChange("startDate", dateRange.from); - handleChange("endDate", dateRange.to); + handleChange("startDate", dateRange?.from ?? ""); + handleChange("endDate", dateRange?.to ?? ""); }} />
@@ -123,7 +125,7 @@ export default function AnnouncementForm({ onClose, editingAnnouncement }: Props />
Apps
- handleChange("apps", apps)} /> + handleChange("apps", apps)} />
Upload Image
diff --git a/src/components/announcement/announcementModal.tsx b/src/components/announcement/announcementModal.tsx index 24173fa..1bf87d4 100644 --- a/src/components/announcement/announcementModal.tsx +++ b/src/components/announcement/announcementModal.tsx @@ -1,11 +1,12 @@ import { Announcement } from "@/models/announcement"; import AnnouncementBanner from "./announcementBanner"; -import AppIcon from "@/icons/AppIcon"; -import ButtonPrimary2 from "../system/ButtonPrimary2"; -import ButtonPrimary3 from "../system/ButtonPrimary3"; -import CrossThinIcon from "@/icons/CrossThinIcon"; +import AppIcon from "@/icons/appIcon"; +import CrossThinIcon from "@/icons/crossThinIcon"; import { dateInRange, formatDate } from "@/utils/utils"; import AnnouncementIndicator from "./announcementIndicator"; +import { DateFormat } from "@/models/enums/dateFormat"; +import ButtonSecondary2 from "../system/button/buttonSecondary2"; +import ButtonPrimary2 from "../system/button/buttonPrimary2"; interface AnnouncementModalProps { onClose: () => void; @@ -28,7 +29,10 @@ export default function AnnouncementModal({ onClose, announcement }: Announcemen

- {`${formatDate(new Date(announcement.startDate))} - ${formatDate(new Date(announcement.endDate))}`} + {`${formatDate(new Date(announcement.startDate), DateFormat.SHORT)} - ${formatDate( + new Date(announcement.endDate), + DateFormat.SHORT + )}`}

@@ -50,12 +54,12 @@ export default function AnnouncementModal({ onClose, announcement }: Announcemen
{dateInRange(new Date(announcement.startDate), new Date(announcement.endDate), new Date()) ? ( - console.log("End Live Announcement button tapped")} /> ) : ( - console.log("Delete button tapped")} /> + console.log("Delete button tapped")} /> )} diff --git a/src/components/common/navBar.tsx b/src/components/common/navBar.tsx index 3a126f2..5fd9be2 100644 --- a/src/components/common/navBar.tsx +++ b/src/components/common/navBar.tsx @@ -1,6 +1,6 @@ import appDevLogo from "@/../public/images/appdev_logo.png"; import appDevLogoName from "@/../public/images/appdev_logo_name.png"; -import ButtonSecondary2 from "../system/ButtonSecondary2"; +import ButtonSecondary2 from "../system/button/buttonSecondary2"; import { Constants } from "@/utils/constants"; import { useUserStore } from "@/stores/useUserStore"; import { useRouter } from "next/navigation"; @@ -25,7 +25,7 @@ export default function NavBar() {
User profile image - +
diff --git a/src/components/landing/landing.tsx b/src/components/landing/landing.tsx index cf47449..3df2232 100644 --- a/src/components/landing/landing.tsx +++ b/src/components/landing/landing.tsx @@ -56,7 +56,7 @@ export default function Landing() { }; return user?.name !== "" ? ( -
+
{showForm ? : null} diff --git a/src/components/landing/landingActiveSection.tsx b/src/components/landing/landingActiveSection.tsx index df71b96..a60b38e 100644 --- a/src/components/landing/landingActiveSection.tsx +++ b/src/components/landing/landingActiveSection.tsx @@ -1,4 +1,4 @@ -import CalendarArrowIcon from "@/icons/CalendarArrowIcon"; +import CalendarArrowIcon from "@/icons/calendarArrowIcon"; import { Announcement } from "@/models/announcement"; import { filterActiveAnnouncements, sortAnnouncementsByStartDate } from "@/utils/utils"; import AnnouncementModal from "../announcement/announcementModal"; diff --git a/src/components/landing/landingCreateAnnouncement.tsx b/src/components/landing/landingCreateAnnouncement.tsx index 751d0bd..f50df36 100644 --- a/src/components/landing/landingCreateAnnouncement.tsx +++ b/src/components/landing/landingCreateAnnouncement.tsx @@ -1,5 +1,5 @@ -import SpeakerIcon from "@/icons/SpeakerIcon"; -import ButtonPrimary1 from "../system/ButtonPrimary1"; +import SpeakerIcon from "@/icons/speakerIcon"; +import ButtonPrimary1 from "../system/button/buttonPrimary1"; interface Props { action: () => void; diff --git a/src/components/landing/landingPastSection.tsx b/src/components/landing/landingPastSection.tsx index 80c2558..c30075d 100644 --- a/src/components/landing/landingPastSection.tsx +++ b/src/components/landing/landingPastSection.tsx @@ -1,11 +1,12 @@ -import CalendarPlainIcon from "@/icons/CalendarPlainIcon"; +import CalendarPlainIcon from "@/icons/calendarPlainIcon"; import { Announcement } from "@/models/announcement"; import { filterPastAnnouncements, sortAnnouncementsByStartDate } from "@/utils/utils"; import AnnouncementCell from "../announcement/announcementCell"; import AnnouncementModal from "../announcement/announcementModal"; import { useState } from "react"; -import ButtonSecondary1 from "../system/ButtonSecondary1"; +import ButtonSecondary1 from "../system/button/buttonSecondary1"; import { Constants } from "@/utils/constants"; +import { useRouter } from "next/navigation"; interface Props { announcements?: Announcement[]; @@ -13,6 +14,8 @@ interface Props { } export default function LandingPastSection({ announcements, onEditClick }: Props) { + const router = useRouter(); + const pastAnnouncements = sortAnnouncementsByStartDate(filterPastAnnouncements(announcements ?? [])); const [selectedAnnouncement, setSelectedAnnouncement] = useState(null); @@ -23,6 +26,10 @@ export default function LandingPastSection({ announcements, onEditClick }: Props setSelectedAnnouncement(null); }; + const viewAllAction = () => { + router.push(Constants.pagePath.past); + }; + return (
@@ -41,9 +48,7 @@ export default function LandingPastSection({ announcements, onEditClick }: Props onClick={() => openModal(pastAnnouncements[0])} onEditClick={() => onEditClick(pastAnnouncements[0])} /> - {pastAnnouncements.length > 1 && ( - console.log("Button clicked")} /> - )} + {pastAnnouncements.length > 1 && }
@@ -57,7 +62,7 @@ export default function LandingPastSection({ announcements, onEditClick }: Props onEditClick={() => onEditClick(announcement)} /> ))} - console.log("Button clicked")} /> + ) : ( pastAnnouncements.map((announcement) => ( diff --git a/src/components/landing/landingUpcomingSection.tsx b/src/components/landing/landingUpcomingSection.tsx index 0ab5fe2..c86dfc0 100644 --- a/src/components/landing/landingUpcomingSection.tsx +++ b/src/components/landing/landingUpcomingSection.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useMemo } from "react"; -import HourglassIcon from "@/icons/HourglassIcon"; +import HourglassIcon from "@/icons/hourglassIcon"; import AnnouncementBanner from "../announcement/announcementBanner"; import { Announcement } from "@/models/announcement"; import { calculateTimeRemaining, filterFutureAnnouncements, getEarliestAnnouncements } from "@/utils/utils"; diff --git a/src/components/past/past.tsx b/src/components/past/past.tsx new file mode 100644 index 0000000..0a8c98a --- /dev/null +++ b/src/components/past/past.tsx @@ -0,0 +1,204 @@ +"use client"; + +import { Announcement } from "@/models/announcement"; +import { AppName } from "@/models/enums/appName"; +import { SortType } from "@/models/enums/sortType"; +import { createDummyAnnouncement } from "@/utils/dummy"; +import { ChangeEvent, useEffect, useState } from "react"; +import NavBar from "../common/navBar"; +import PageHeader from "../common/pageHeader"; +import InputSearch from "../system/input/inputSearch"; +import Divider from "../system/divider"; +import PastAnnouncementCell from "./pastAnnouncementCell"; +import Footer from "../common/footer"; +import { InputSelect } from "../system/input/inputSelect"; +import FilterIcon from "@/icons/filterIcon"; +import PastFilter from "./pastFilter"; +import { DateRange } from "react-day-picker"; +import { dateInRange } from "@/utils/utils"; + +// TODO: Replace with React Query to fetch from API +const allAnnouncements: Announcement[] = [ + createDummyAnnouncement(), + createDummyAnnouncement(new Date().toDateString(), new Date().toDateString(), [AppName.EATERY]), + createDummyAnnouncement(new Date().toDateString(), new Date().toDateString(), [AppName.EATERY, AppName.RESELL]), + createDummyAnnouncement(new Date().toDateString(), new Date().toDateString(), [ + AppName.EATERY, + AppName.RESELL, + AppName.TRANSIT, + ]), + createDummyAnnouncement(new Date().toDateString(), new Date().toDateString(), [ + AppName.EATERY, + AppName.RESELL, + AppName.TRANSIT, + AppName.COURSEGRAB, + ]), + createDummyAnnouncement(new Date().toDateString(), new Date().toDateString(), [ + AppName.EATERY, + AppName.RESELL, + AppName.TRANSIT, + AppName.COURSEGRAB, + AppName.VOLUME, + ]), + { + id: "uuidv4", + apps: [AppName.EATERY, AppName.RESELL, AppName.TRANSIT, AppName.COURSEGRAB], + body: "Get a taste of the course content, ask questions, and see if DPD is the right fit for you!", + creator: { + email: "vdb23@cornell.edu", + idToken: "idToken", + imageUrl: "https://lh3.googleusercontent.com/a/ACg8ocLSV3bTsn-XINmiSkt4FbdlzRDV0EJBc_LX-hv7gdo3LGp8cAB_=s96-c", + isAdmin: true, + name: "Vin Bui", + }, + endDate: "2024-10-16T03:00:00.000Z", + imageUrl: "https://appdev-upload.nyc3.cdn.digitaloceanspaces.com/announcements/n07chyp8.jpg", + link: "https://www.instagram.com/p/C4ExCD1rB6U", + startDate: "2024-08-15T03:00:00.000Z", + title: "Testing", + }, +]; + +export default function Past() { + const [searchText, setSearchText] = useState(""); + const [selectedSort, setSelectedSort] = useState(SortType.MOST_RECENT); + const [listedAnnouncements, setListedAnnouncements] = useState( + // Sort by most recent first + allAnnouncements.sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()) + ); + const [showFilters, setShowFilters] = useState(false); + const [filterDateRange, setFilterDateRange] = useState(undefined); + const [filterApps, setFilterApps] = useState([]); + + const handleSearchChange = (event: ChangeEvent) => { + setSearchText(event.target.value); + }; + + useEffect(() => { + // Searching + const searched = allAnnouncements.filter((announcement) => { + const title = announcement.title.toLowerCase().replace(/\s/g, ""); + const search = searchText.toLowerCase().replace(/\s/g, ""); + return title.includes(search); + }); + + // Sorting + const sorted = [...searched].sort((a, b) => { + switch (selectedSort) { + case SortType.MOST_RECENT: + return new Date(b.startDate).getTime() - new Date(a.startDate).getTime(); + case SortType.OLDEST: + return new Date(a.startDate).getTime() - new Date(b.startDate).getTime(); + case SortType.TITLE_A_Z: + return a.title.localeCompare(b.title); + case SortType.TITLE_Z_A: + return b.title.localeCompare(a.title); + default: + return 0; // No sorting if selectedSort is invalid + } + }); + + // Filtering + const hasOverlap = (arr1: AppName[], arr2: AppName[]) => { + if (filterApps.length === 0) return true; + return arr1.some((item) => arr2.includes(item)); + }; + const withinRange = (announcement: Announcement) => { + const startDate = filterDateRange?.from; + const endDate = filterDateRange?.to; + if (!startDate || !endDate) return true; + + // Beginning of start date to end of end date + startDate.setHours(0, 0, 0, 0); + endDate.setHours(23, 59, 59, 999); + + return ( + dateInRange(startDate, endDate, new Date(announcement.startDate)) || + dateInRange(startDate, endDate, new Date(announcement.endDate)) + ); + }; + const filtered = sorted.filter( + (announcement) => hasOverlap(announcement.apps, filterApps) && withinRange(announcement) + ); + setListedAnnouncements(filtered); + setShowFilters(false); + }, [searchText, selectedSort, filterDateRange, filterApps]); + + return ( +
+ +
+ {/* Header */} + + + {/* Filters */} +
+ +
+
setShowFilters(true)} + > + +

+ {filterDateRange && filterApps.length !== 0 + ? "Filter - 2" + : filterDateRange || filterApps.length !== 0 + ? "Filter - 1" + : "Filter"} +

+
+ {showFilters ? ( + setShowFilters(false)} + onApply={(newDateRange, newApps) => { + setFilterDateRange(newDateRange); + setFilterApps(newApps); + }} + /> + ) : null} +
+

SORT BY

+ setSelectedSort(selected)} /> +
+
+
+ + {/* Table Header */} +

{`${listedAnnouncements.length} ${ + listedAnnouncements.length === 1 ? "announcement" : "announcements" + }`}

+
+
+
Image
+
Title
+
Start Time
+
End Time
+
Scheduler
+
Apps
+
+ {listedAnnouncements.map((announcement) => ( +
+ + +
+ ))} +
+ + {/* Table Rows */} +
+ {listedAnnouncements.map((announcement) => ( + + ))} +
+
+ + {/* Footer */} +
+
+
+
+ ); +} diff --git a/src/components/past/pastAnnouncementCell.tsx b/src/components/past/pastAnnouncementCell.tsx new file mode 100644 index 0000000..2c82981 --- /dev/null +++ b/src/components/past/pastAnnouncementCell.tsx @@ -0,0 +1,84 @@ +import AppIcon from "@/icons/appIcon"; +import { Announcement } from "@/models/announcement"; +import { AppName } from "@/models/enums/appName"; +import { DateFormat } from "@/models/enums/dateFormat"; +import { formatDate, getAppCountString } from "@/utils/utils"; + +interface Props { + announcement: Announcement; +} + +export default function PastAnnouncementCell({ announcement }: Props) { + const renderAppIcons = () => { + const appLength = announcement.apps.length; + + if (appLength === Object.keys(AppName).length) { + return

All Apps

; + } else if (appLength < 4) { + return ( +
+ {announcement.apps.map((app) => ( + + ))} +
+ ); + } else { + return ( +
+ {announcement.apps.slice(0, 3).map((app) => ( + + ))} +

{`and ${appLength - 3} more`}

+
+ ); + } + }; + + return ( + <> +
+ {announcement.title} +
{announcement.title}
+

+ {formatDate(new Date(announcement.startDate), DateFormat.SHORT_YEAR)} +

+

+ {formatDate(new Date(announcement.endDate), DateFormat.SHORT_YEAR)} +

+
+ {announcement.creator.name} +

{announcement.creator.name}

+
+

{getAppCountString(announcement.apps)}

+
{renderAppIcons()}
+
+
+ {announcement.title} +
+
+

{announcement.title}

+

+ {`${formatDate(new Date(announcement.startDate), DateFormat.SHORT)} - ${formatDate( + new Date(announcement.endDate), + DateFormat.SHORT + )}`} +

+
+
+ {announcement.creator.name} +

{`Scheduled by ${announcement.creator.name}`}

+
+
+ {announcement.apps.map((app) => ( + + ))} +
+
+
+ + ); +} diff --git a/src/components/past/pastFilter.tsx b/src/components/past/pastFilter.tsx new file mode 100644 index 0000000..f613f7a --- /dev/null +++ b/src/components/past/pastFilter.tsx @@ -0,0 +1,63 @@ +import { AppName } from "@/models/enums/appName"; +import InputDatePicker from "../system/input/inputDatePicker"; +import { DateRange } from "react-day-picker"; +import InputMultiSelect from "../system/input/inputMultiselect"; +import ButtonPrimary2 from "../system/button/buttonPrimary2"; +import ButtonPrimary3 from "../system/button/buttonPrimary3"; +import { useState } from "react"; + +interface Props { + initialDateRange: DateRange | undefined; + initialApps: AppName[]; + onCancel: () => void; + onApply: (newDateRange: DateRange | undefined, newApps: AppName[]) => void; +} + +export default function PastFilter({ initialDateRange, initialApps, onCancel, onApply }: Props) { + const [dateRange, setDateRange] = useState(initialDateRange); + const [apps, setApps] = useState(initialApps); + const [filtersReset, setFiltersReset] = useState(""); + + // Reset Filters + const resetFilters = () => { + setDateRange(undefined); + setApps([]); + + filtersReset === "" ? setFiltersReset("reset") : setFiltersReset(""); // Force re-render + }; + + return ( +
+
+
Filters
+ + {/* Date */} +
+
Date
+ +
+ + {/* Apps */} +
+
Apps
+ setApps(apps.map((str) => AppName[str.toUpperCase() as keyof typeof AppName]))} + /> +
+ + {/* CTAs */} +
+

+ Reset Filters +

+
+ + onApply(dateRange, apps)} className="py-2" textStyle="label" /> +
+
+
+
+ ); +} diff --git a/src/components/system/ButtonSecondary1.tsx b/src/components/system/ButtonSecondary1.tsx deleted file mode 100644 index def5d85..0000000 --- a/src/components/system/ButtonSecondary1.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import CaretRightIcon from "@/icons/CaretRightIcon"; - -interface Props { - text: string; - action: () => void; - disabled?: boolean; - className?: string; -} - -export default function ButtonPrimary1({ text, action, disabled = false, className }: Props) { - const handleClick = (event: React.MouseEvent) => { - event.stopPropagation(); // Prevent event from bubbling up - action(); - }; - - return ( - - ); -} diff --git a/src/components/system/ButtonPrimary1.tsx b/src/components/system/button/buttonPrimary1.tsx similarity index 59% rename from src/components/system/ButtonPrimary1.tsx rename to src/components/system/button/buttonPrimary1.tsx index ad3e47b..fe7a6c7 100644 --- a/src/components/system/ButtonPrimary1.tsx +++ b/src/components/system/button/buttonPrimary1.tsx @@ -1,13 +1,16 @@ -import CaretRightIcon from "@/icons/CaretRightIcon"; +"use client"; + +import CaretRightIcon from "@/icons/caretRightIcon"; interface Props { text: string; action: () => void; disabled?: boolean; className?: string; + textStyle?: string; } -export default function ButtonPrimary1({ text, action, disabled = false, className }: Props) { +export default function ButtonPrimary1({ text, action, disabled = false, className, textStyle }: Props) { const handleClick = (event: React.MouseEvent) => { event.stopPropagation(); // Prevent event from bubbling up action(); @@ -15,13 +18,19 @@ export default function ButtonPrimary1({ text, action, disabled = false, classNa return ( ); diff --git a/src/components/system/ButtonPrimary2.tsx b/src/components/system/button/buttonPrimary2.tsx similarity index 57% rename from src/components/system/ButtonPrimary2.tsx rename to src/components/system/button/buttonPrimary2.tsx index 6eb8460..0f6bb90 100644 --- a/src/components/system/ButtonPrimary2.tsx +++ b/src/components/system/button/buttonPrimary2.tsx @@ -5,21 +5,18 @@ interface Props { action: () => void; disabled?: boolean; className?: string; + textStyle?: string; } -export default function ButtonPrimary2({ text, action, disabled = false, className }: Props) { +export default function ButtonPrimary2({ text, action, disabled = false, className, textStyle }: Props) { const handleClick = (event: React.MouseEvent) => { event.stopPropagation(); // Prevent event from bubbling up action(); }; return ( - ); } diff --git a/src/components/system/ButtonPrimary3.tsx b/src/components/system/button/buttonPrimary3.tsx similarity index 65% rename from src/components/system/ButtonPrimary3.tsx rename to src/components/system/button/buttonPrimary3.tsx index 4567368..0e86949 100644 --- a/src/components/system/ButtonPrimary3.tsx +++ b/src/components/system/button/buttonPrimary3.tsx @@ -5,9 +5,10 @@ interface Props { action: () => void; disabled?: boolean; className?: string; + textStyle?: string; } -export default function ButtonPrimary3({ text, action, disabled = false, className }: Props) { +export default function ButtonPrimary3({ text, action, disabled = false, className, textStyle }: Props) { const handleClick = (event: React.MouseEvent) => { event.stopPropagation(); // Prevent event from bubbling up action(); @@ -15,11 +16,11 @@ export default function ButtonPrimary3({ text, action, disabled = false, classNa return ( ); } diff --git a/src/components/system/button/buttonSecondary1.tsx b/src/components/system/button/buttonSecondary1.tsx new file mode 100644 index 0000000..ae96718 --- /dev/null +++ b/src/components/system/button/buttonSecondary1.tsx @@ -0,0 +1,29 @@ +"use client"; + +import CaretRightIcon from "@/icons/caretRightIcon"; + +interface Props { + text: string; + action: () => void; + disabled?: boolean; + className?: string; + textStyle?: string; +} + +export default function ButtonSecondary1({ text, action, disabled = false, className, textStyle }: Props) { + const handleClick = (event: React.MouseEvent) => { + event.stopPropagation(); // Prevent event from bubbling up + action(); + }; + + return ( + + ); +} diff --git a/src/components/system/ButtonSecondary2.tsx b/src/components/system/button/buttonSecondary2.tsx similarity index 56% rename from src/components/system/ButtonSecondary2.tsx rename to src/components/system/button/buttonSecondary2.tsx index f232c7d..dd603f2 100644 --- a/src/components/system/ButtonSecondary2.tsx +++ b/src/components/system/button/buttonSecondary2.tsx @@ -1,19 +1,22 @@ +"use client"; + interface Props { text: string; action: () => void; disabled?: boolean; className?: string; + textStyle?: string; } -export default function ButtonSecondary2({ text, action, disabled = false, className }: Props) { +export default function ButtonSecondary2({ text, action, disabled = false, className, textStyle }: Props) { const handleClick = (event: React.MouseEvent) => { event.stopPropagation(); // Prevent event from bubbling up action(); }; return ( - ); } diff --git a/src/components/system/ButtonTertiary.tsx b/src/components/system/button/buttonTertiary.tsx similarity index 51% rename from src/components/system/ButtonTertiary.tsx rename to src/components/system/button/buttonTertiary.tsx index 8bc6fed..e9b299b 100644 --- a/src/components/system/ButtonTertiary.tsx +++ b/src/components/system/button/buttonTertiary.tsx @@ -1,13 +1,16 @@ -import EditIcon from "@/icons/EditIcon"; +"use client"; + +import EditIcon from "@/icons/editIcon"; interface Props { text: string; action: () => void; - className?: string; disabled?: boolean; + className?: string; + textStyle?: string; } -export default function TertiaryButton({ text, action, className, disabled = false }: Props) { +export default function TertiaryButton({ text, action, disabled = false, className, textStyle }: Props) { const handleClick = (event: React.MouseEvent) => { event.stopPropagation(); // Prevent event from bubbling up action(); @@ -15,12 +18,12 @@ export default function TertiaryButton({ text, action, className, disabled = fal return ( ); } diff --git a/src/components/system/divider.tsx b/src/components/system/divider.tsx new file mode 100644 index 0000000..74cf54d --- /dev/null +++ b/src/components/system/divider.tsx @@ -0,0 +1,14 @@ +interface Props { + style: "horizontal" | "vertical"; + className?: string; +} + +export default function Divider({ style, className }: Props) { + if (style === "horizontal") { + return
; + } else if (style === "vertical") { + return
; + } else { + return null; + } +} diff --git a/src/components/system/input/inputDatePicker.tsx b/src/components/system/input/inputDatePicker.tsx index 8fc49a1..8cbc138 100644 --- a/src/components/system/input/inputDatePicker.tsx +++ b/src/components/system/input/inputDatePicker.tsx @@ -1,6 +1,6 @@ "use client"; -import { addDays, format } from "date-fns"; +import { format } from "date-fns"; import { CalendarIcon } from "lucide-react"; import { DateRange } from "react-day-picker"; @@ -11,15 +11,14 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover import { useEffect, useState } from "react"; interface Props { - value: DateRange; - setDateRange: (dateRange: DateRange) => void; + value: DateRange | undefined; + setDateRange: (dateRange: DateRange | undefined) => void; } export default function InputDatePicker({ value, setDateRange }: Props) { const [date, setDate] = useState(value); useEffect(() => { - if (!date) return; setDateRange(date); }, [date]); diff --git a/src/components/system/input/inputMultiselect.tsx b/src/components/system/input/inputMultiselect.tsx index 0ec2a73..0d66273 100644 --- a/src/components/system/input/inputMultiselect.tsx +++ b/src/components/system/input/inputMultiselect.tsx @@ -20,10 +20,10 @@ const options = [ interface Props { value: string[]; - setApps: (apps: string[]) => void; + setValues: (values: string[]) => void; } -export default function InputMultiSelect({ value, setApps }: Props) { +export default function InputMultiSelect({ value, setValues }: Props) { const [open, setOpen] = useState(false); const [selectedValues, setSelectedValues] = useState(value); @@ -39,7 +39,7 @@ export default function InputMultiSelect({ value, setApps }: Props) { ); useEffect(() => { - setApps(selectedValues); + setValues(selectedValues); }, [selectedValues]); return ( diff --git a/src/components/system/input/inputSearch.tsx b/src/components/system/input/inputSearch.tsx new file mode 100644 index 0000000..a383d96 --- /dev/null +++ b/src/components/system/input/inputSearch.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { Input } from "@/components/ui/input"; +import SearchIcon from "@/icons/searchIcon"; +import { ChangeEvent } from "react"; + +interface Props { + text: string; + placeholder: string; + onChange: (event: ChangeEvent) => void; +} + +export default function InputSearch({ text, placeholder, onChange }: Props) { + return ( +
+ + +
+ ); +} diff --git a/src/components/system/input/inputSelect.tsx b/src/components/system/input/inputSelect.tsx new file mode 100644 index 0000000..a0dba98 --- /dev/null +++ b/src/components/system/input/inputSelect.tsx @@ -0,0 +1,27 @@ +"use client"; + +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { SortType } from "@/models/enums/sortType"; + +interface Props { + selected: SortType; + setSelected: (selected: SortType) => void; +} + +export function InputSelect({ selected, setSelected }: Props) { + return ( + + ); +} diff --git a/src/components/system/input/inputText.tsx b/src/components/system/input/inputText.tsx index f4a51ca..343dff2 100644 --- a/src/components/system/input/inputText.tsx +++ b/src/components/system/input/inputText.tsx @@ -1,3 +1,5 @@ +"use client"; + import { Input } from "@/components/ui/input"; interface Props { @@ -11,7 +13,7 @@ export default function InputText({ name, placeholder, value, onChange }: Props) return (
{name}
- +
); } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..9704469 --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,144 @@ +"use client"; + +import * as React from "react"; +import * as SelectPrimitive from "@radix-ui/react-select"; +import { Check, ChevronDown, ChevronUp } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)); +SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; diff --git a/src/icons/AppIcon.tsx b/src/icons/AppIcon.tsx index b58433c..b430dde 100644 --- a/src/icons/AppIcon.tsx +++ b/src/icons/AppIcon.tsx @@ -1,4 +1,4 @@ -import { AppName } from "@/models/appName"; +import { AppName } from "@/models/enums/appName"; import { IconProps } from "@/models/props/iconProps"; interface Props extends IconProps { @@ -20,8 +20,6 @@ export default function AppIcon({ appName, className }: Props) { return "/app-icons/Volume.png"; case AppName.UPLIFT: return "/app-icons/Uplift.png"; - case AppName.SCOOPED: - return "/app-icons/Scooped.png"; default: throw new Error(`No icon found for app name: ${appName}`); } diff --git a/src/icons/CheckIcon.tsx b/src/icons/CheckIcon.tsx deleted file mode 100644 index 473b464..0000000 --- a/src/icons/CheckIcon.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { IconProps } from "@/models/props/iconProps"; - -export default function ({ className }: IconProps) { - return ( - - - - ); -} diff --git a/src/icons/ChevronDownIcon.tsx b/src/icons/ChevronDownIcon.tsx deleted file mode 100644 index ea6d0c1..0000000 --- a/src/icons/ChevronDownIcon.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { IconProps } from "@/models/props/iconProps"; - -export default function ({ className }: IconProps) { - return ( - - - - ); -} diff --git a/src/icons/filterIcon.tsx b/src/icons/filterIcon.tsx new file mode 100644 index 0000000..acdf092 --- /dev/null +++ b/src/icons/filterIcon.tsx @@ -0,0 +1,9 @@ +import { IconProps } from "@/models/props/iconProps"; + +export default function ({ className }: IconProps) { + return ( + + + + ); +} diff --git a/src/icons/searchIcon.tsx b/src/icons/searchIcon.tsx new file mode 100644 index 0000000..f7f3663 --- /dev/null +++ b/src/icons/searchIcon.tsx @@ -0,0 +1,13 @@ +import { IconProps } from "@/models/props/iconProps"; + +export default function ({ className }: IconProps) { + return ( + + + + ); +} diff --git a/src/models/announcement.ts b/src/models/announcement.ts index b42c70f..027d235 100644 --- a/src/models/announcement.ts +++ b/src/models/announcement.ts @@ -1,4 +1,4 @@ -import { AppName } from "./appName"; +import { AppName } from "./enums/appName"; import { User } from "./user"; export interface Announcement { diff --git a/src/models/appName.ts b/src/models/enums/appName.ts similarity index 88% rename from src/models/appName.ts rename to src/models/enums/appName.ts index 819f582..7ee07b3 100644 --- a/src/models/appName.ts +++ b/src/models/enums/appName.ts @@ -31,9 +31,4 @@ export enum AppName { * The Uplift app. */ UPLIFT = "uplift", - - /** - * The Scooped app. - */ - SCOOPED = "scooped", } diff --git a/src/models/enums/dateFormat.ts b/src/models/enums/dateFormat.ts new file mode 100644 index 0000000..8b243b0 --- /dev/null +++ b/src/models/enums/dateFormat.ts @@ -0,0 +1,13 @@ +/** + * Enum representing different date formatting options. + */ +export enum DateFormat { + /** + * Format: "M/D 0:00 AM/PM" + */ + SHORT, + /** + * Format: "M/D/Y 0:00 AM/PM" (Y has 2 digits) + */ + SHORT_YEAR, +} diff --git a/src/models/enums/sortType.ts b/src/models/enums/sortType.ts new file mode 100644 index 0000000..bd637c7 --- /dev/null +++ b/src/models/enums/sortType.ts @@ -0,0 +1,24 @@ +/** + * Represents the different sorting options available for a list of announcements. + */ +export enum SortType { + /** + * Sort announcements by most recent first. + */ + MOST_RECENT = "Most Recent", + + /** + * Sort announcements by oldest first. + */ + OLDEST = "Oldest", + + /** + * Sort announcements by title in ascending alphabetical order (A-Z). + */ + TITLE_A_Z = "Title A to Z", + + /** + * Sort announcements by title in descending alphabetical order (Z-A). + */ + TITLE_Z_A = "Title Z to A", +} diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7d4d469..845a588 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -9,6 +9,7 @@ export const Constants = { default: "/", landing: "/landing", login: "/login", + past: "/past", }, queryKey: { fetchAnnouncements: "fetchAnnouncements", diff --git a/src/utils/dummy.ts b/src/utils/dummy.ts new file mode 100644 index 0000000..e6dfd3d --- /dev/null +++ b/src/utils/dummy.ts @@ -0,0 +1,45 @@ +import { Announcement } from "@/models/announcement"; +import { AppName } from "@/models/enums/appName"; +import { User } from "@/models/user"; +import { v4 as uuidv4 } from "uuid"; + +/** + * Creates a dummy announcement object for testing or development purposes. + * + * @param startDate The start date of the announcement. Defaults to August 15th, 2024. + * @param endDate The end date of the announcement. Defaults to October 16th, 2024. + * @param apps An array of app names relevant to the announcement. Defaults to all app names. + * @returns A dummy announcement object. + */ +export function createDummyAnnouncement( + startDate: string = "2024-08-15T03:00:00.000Z", + endDate: string = "2024-10-16T03:00:00.000Z", + apps: AppName[] = [ + AppName.EATERY, + AppName.TRANSIT, + AppName.UPLIFT, + AppName.COURSEGRAB, + AppName.VOLUME, + AppName.RESELL, + ] +): Announcement { + const creator: User = { + email: "vdb23@cornell.edu", + idToken: "idToken", + imageUrl: "https://lh3.googleusercontent.com/a/ACg8ocLSV3bTsn-XINmiSkt4FbdlzRDV0EJBc_LX-hv7gdo3LGp8cAB_=s96-c", + isAdmin: true, + name: "Vin Bui", + }; + + return { + id: uuidv4(), + apps, + body: "Get a taste of the course content, ask questions, and see if DPD is the right fit for you!", + creator, + endDate, + imageUrl: "https://appdev-upload.nyc3.cdn.digitaloceanspaces.com/announcements/n07chyp8.jpg", + link: "https://www.instagram.com/p/C4ExCD1rB6U", + startDate, + title: "DPD Lecture 0", + }; +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 824f60e..b1d9093 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,6 @@ import { Announcement } from "@/models/announcement"; +import { AppName } from "@/models/enums/appName"; +import { DateFormat } from "@/models/enums/dateFormat"; /** * Filters out announcements whose startDate is in the past. @@ -100,18 +102,61 @@ export const dateInRange = (startDate: Date, endDate: Date, targetDate: Date = n }; /** - * Formats a Date object into a string in the M/D 00:00 AM/PM format. + * Formats a date object according to the specified DateFormat. * - * @param date - A date. - * @returns A string representing [date] in the above format. + * @param date - The date object to format. + * @param format - The desired date format (from DateFormat enum). + * @returns The formatted date string. + * + * @example + * const date = new Date('2024-09-17T05:25:00'); + * const formattedDate1 = formatDate(date, DateFormat.SHORT); // "9/17 5:25 AM" + * const formattedDate2 = formatDate(date, DateFormat.SHORT_YEAR); // "9/17/24 5:25 AM" + */ +export const formatDate = (date: Date, format: DateFormat): string => { + let options: Intl.DateTimeFormatOptions; + + switch (format) { + case DateFormat.SHORT: + options = { + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }; + break; + case DateFormat.SHORT_YEAR: + options = { + year: "2-digit", + month: "numeric", + day: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }; + break; + default: + throw new Error(`Invalid DateFormat: ${format}`); + } + + return new Intl.DateTimeFormat("en-US", options).format(date).replace(",", ""); +}; + +/** + * Generates a string describing the number of apps provided. + * + * @param apps An array of AppName enum values. + * @returns A string representing the number of apps: "All Apps", "1 App", or "2 Apps". */ -export const formatDate = (date: Date) => { - const month = date.getMonth() + 1; - const day = date.getDate(); - const time = date.toLocaleTimeString("en-US", { - hour: "numeric", - minute: "2-digit", - hour12: true, - }); - return `${month}/${day} ${time}`; +export const getAppCountString = (apps: AppName[]): string => { + const appCount = apps.length; + switch (appCount) { + case Object.keys(AppName).length: + return "All Apps"; + case 1: + return "1 App"; + default: + return `${appCount} Apps`; + } }; diff --git a/tests/utils.test.ts b/tests/utils.test.ts index 862cb5d..bee5dda 100644 --- a/tests/utils.test.ts +++ b/tests/utils.test.ts @@ -1,6 +1,7 @@ import { Announcement } from "@/models/announcement"; -import { AppName } from "@/models/appName"; -import { User } from "@/models/user"; +import { AppName } from "@/models/enums/appName"; +import { DateFormat } from "@/models/enums/dateFormat"; +import { createDummyAnnouncement } from "@/utils/dummy"; import { calculateTimeRemaining, dateInRange, @@ -8,92 +9,25 @@ import { filterFutureAnnouncements, filterPastAnnouncements, formatDate, + getAppCountString, getEarliestAnnouncements, sortAnnouncementsByStartDate, } from "../src/utils/utils"; -const creator: User = { - email: "vdb23@cornell.edu", - idToken: "idToken", - imageUrl: "https://lh3.googleusercontent.com/a/ACg8ocLSV3bTsn-XINmiSkt4FbdlzRDV0EJBc_LX-hv7gdo3LGp8cAB_=s96-c", - isAdmin: true, - name: "Vin Bui", -}; - const announcements: Announcement[] = [ - { - id: "1", - apps: [AppName.EATERY], - body: "Announcement 1", - creator, - endDate: "2024-04-01T00:00:00Z", - imageUrl: "image1.jpg", - link: "link1", - startDate: "2024-03-15T00:00:00Z", - title: "Announcement 1", - }, - { - id: "2", - apps: [AppName.RESELL, AppName.COURSEGRAB], - body: "Announcement 2", - creator, - endDate: "2024-08-30T00:00:00Z", - imageUrl: "image2.jpg", - link: "link2", - startDate: "2024-08-20T00:00:00Z", - title: "Announcement 2", - }, - { - id: "3", - apps: [AppName.UPLIFT], - body: "Announcement 3", - creator, - endDate: "2025-03-30T00:00:00Z", - imageUrl: "image3.jpg", - link: "link3", - startDate: "2025-03-25T00:00:00Z", - title: "Announcement 3", - }, + createDummyAnnouncement("2024-03-15T00:00:00Z", "2024-04-01T00:00:00Z", [AppName.EATERY]), + createDummyAnnouncement("2024-08-20T00:00:00Z", "2024-08-30T00:00:00Z", [AppName.RESELL, AppName.COURSEGRAB]), + createDummyAnnouncement("2025-03-25T00:00:00Z", "2025-03-30T00:00:00Z", [AppName.UPLIFT]), ]; const duplicateStartAnnouncements: Announcement[] = [ - { - id: "4", - apps: [AppName.EATERY], - body: "Announcement 4", - creator, - endDate: "2024-05-01T00:00:00Z", - imageUrl: "image4.jpg", - link: "link4", - startDate: "2024-03-15T00:00:00Z", - title: "Announcement 4", - }, ...announcements, + createDummyAnnouncement("2024-03-15T00:00:00Z", "2024-05-01T00:00:00Z", [AppName.UPLIFT]), ]; const noFutureAnnouncements: Announcement[] = [ - { - id: "0", - apps: [AppName.EATERY], - body: "Announcement 1", - creator, - endDate: "2024-05-01T00:00:00Z", - imageUrl: "image4.jpg", - link: "link4", - startDate: "2024-03-15T00:00:00Z", - title: "Announcement 4", - }, - { - id: "1", - apps: [AppName.EATERY], - body: "Announcement 2", - creator, - endDate: "2023-11-12T00:00:00Z", - imageUrl: "image4.jpg", - link: "link4", - startDate: "2023-05-30T00:00:00Z", - title: "Announcement 4", - }, + createDummyAnnouncement("2024-03-15T00:00:00Z", "2024-05-01T00:00:00Z"), + createDummyAnnouncement("2023-05-30T00:00:00Z", "2023-11-12T00:00:00Z"), ]; describe("Utils", () => { @@ -101,8 +35,8 @@ describe("Utils", () => { it("should filter announcements to only include those with a start date in the future", () => { const result = filterFutureAnnouncements(announcements, new Date(2024, 7, 1)); expect(result.length).toBe(2); - expect(result[0].id).toBe("2"); - expect(result[1].id).toBe("3"); + expect(result[0].id).toBe(announcements[1].id); + expect(result[1].id).toBe(announcements[2].id); }); it("should filter out past announcements (no future announcements)", () => { const result = filterFutureAnnouncements(noFutureAnnouncements, new Date(2024, 7, 1)); @@ -113,9 +47,9 @@ describe("Utils", () => { describe("sortAnnouncementsByStartDate", () => { it("should sort announcements by their start date in ascending order", () => { const result = sortAnnouncementsByStartDate(announcements); - expect(result[0].id).toBe("1"); - expect(result[1].id).toBe("2"); - expect(result[2].id).toBe("3"); + expect(result[0].id).toBe(announcements[0].id); + expect(result[1].id).toBe(announcements[1].id); + expect(result[2].id).toBe(announcements[2].id); }); }); @@ -173,14 +107,14 @@ describe("Utils", () => { it("should return an array with the announcement with the earliest start date", () => { const result = getEarliestAnnouncements(announcements); expect(result.length).toBe(1); - expect(result[0].id).toBe("1"); + expect(result[0].id).toBe(announcements[0].id); }); it("should return an array with multiple announcements if they have the same earliest start date", () => { const result = getEarliestAnnouncements(duplicateStartAnnouncements); expect(result.length).toBe(2); - expect(result[0].id).toBe("4"); - expect(result[1].id).toBe("1"); + expect(result[0].id).toBe(duplicateStartAnnouncements[0].id); + expect(result[1].id).toBe(duplicateStartAnnouncements[1].id); }); it("should return an empty array if there are no announcements", () => { @@ -193,7 +127,7 @@ describe("Utils", () => { it("should filter announcements to only include those with an end date in the future", () => { const result = filterActiveAnnouncements(announcements, new Date(2024, 11, 1)); expect(result.length).toBe(1); - expect(result[0].id).toBe("3"); + expect(result[0].id).toBe(announcements[2].id); }); it("should filter announcements to only include those with an end date in the future (all announcements have concluded)", () => { const result = filterActiveAnnouncements(duplicateStartAnnouncements, new Date(2026, 11, 1)); @@ -202,8 +136,8 @@ describe("Utils", () => { it("should filter announcements to only include those with an end date in the future (includes active announcement)", () => { const result = filterActiveAnnouncements(announcements, new Date(2024, 7, 25)); expect(result.length).toBe(2); - expect(result[0].id).toBe("2"); - expect(result[1].id).toBe("3"); + expect(result[0].id).toBe(announcements[1].id); + expect(result[1].id).toBe(announcements[2].id); }); }); @@ -253,37 +187,37 @@ describe("Utils", () => { describe("formatDate", () => { it("should format the date correctly for a date in the middle of the year", () => { const date = new Date("2024-07-15T14:30:00"); - const result = formatDate(date); + const result = formatDate(date, DateFormat.SHORT); expect(result).toBe("7/15 2:30 PM"); }); it("should format the date correctly for a date at the beginning of the year", () => { const date = new Date("2024-01-01T00:00:00"); - const result = formatDate(date); + const result = formatDate(date, DateFormat.SHORT); expect(result).toBe("1/1 12:00 AM"); }); it("should format the date correctly for a date at the end of the year", () => { const date = new Date("2024-12-31T23:59:59"); - const result = formatDate(date); + const result = formatDate(date, DateFormat.SHORT); expect(result).toBe("12/31 11:59 PM"); }); it("should format the date correctly for a single-digit month and day", () => { const date = new Date("2024-03-05T07:05:00"); - const result = formatDate(date); + const result = formatDate(date, DateFormat.SHORT); expect(result).toBe("3/5 7:05 AM"); }); it("should format the date correctly for noon", () => { const date = new Date("2024-06-15T12:00:00"); - const result = formatDate(date); + const result = formatDate(date, DateFormat.SHORT); expect(result).toBe("6/15 12:00 PM"); }); it("should format the date correctly for midnight", () => { const date = new Date("2024-09-22T00:00:00"); - const result = formatDate(date); + const result = formatDate(date, DateFormat.SHORT); expect(result).toBe("9/22 12:00 AM"); }); }); @@ -292,8 +226,8 @@ describe("Utils", () => { it("should filter announcements to only include those with an end date in the past", () => { const result = filterPastAnnouncements(announcements, new Date(2024, 11, 1)); expect(result.length).toBe(2); - expect(result[0].id).toBe("1"); - expect(result[1].id).toBe("2"); + expect(result[0].id).toBe(announcements[0].id); + expect(result[1].id).toBe(announcements[1].id); }); it("all announcements are in the future (returns empty array)", () => { @@ -315,8 +249,30 @@ describe("Utils", () => { it("one input announcement has the exact same end date as the date it's being compared to (should not be in the output)", () => { const result = filterPastAnnouncements(announcements, new Date("2025-03-30T00:00:00Z")); expect(result.length).toBe(2); - expect(result[0].id).toBe("1"); - expect(result[1].id).toBe("2"); + expect(result[0].id).toBe(announcements[0].id); + expect(result[1].id).toBe(announcements[1].id); + }); + }); + + describe("getAppCountString", () => { + it("should output all apps", () => { + const result = getAppCountString(createDummyAnnouncement().apps); + expect(result).toBe("All Apps"); + }); + + it("should output 1 app", () => { + const result = getAppCountString( + createDummyAnnouncement(new Date().toDateString(), new Date().toDateString(), [AppName.RESELL]).apps + ); + expect(result).toBe("1 App"); + }); + + it("should output multiple but not all apps", () => { + const result = getAppCountString( + createDummyAnnouncement(new Date().toDateString(), new Date().toDateString(), [AppName.EATERY, AppName.RESELL]) + .apps + ); + expect(result).toBe("2 Apps"); }); }); }); diff --git a/yarn.lock b/yarn.lock index 5c96235..d63b6d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -309,22 +309,13 @@ "@floating-ui/core" "^1.6.0" "@floating-ui/utils" "^0.2.8" -"@floating-ui/react-dom@^2.0.0", "@floating-ui/react-dom@^2.1.2": +"@floating-ui/react-dom@^2.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" integrity sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A== dependencies: "@floating-ui/dom" "^1.0.0" -"@floating-ui/react@^0.26.16": - version "0.26.28" - resolved "https://registry.yarnpkg.com/@floating-ui/react/-/react-0.26.28.tgz#93f44ebaeb02409312e9df9507e83aab4a8c0dc7" - integrity sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw== - dependencies: - "@floating-ui/react-dom" "^2.1.2" - "@floating-ui/utils" "^0.2.8" - tabbable "^6.0.0" - "@floating-ui/utils@^0.2.8": version "0.2.8" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" @@ -371,21 +362,6 @@ dependencies: tslib "2" -"@headlessui/react@^2.1.9": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.2.0.tgz#a8e32f0899862849a1ce1615fa280e7891431ab7" - integrity sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ== - dependencies: - "@floating-ui/react" "^0.26.16" - "@react-aria/focus" "^3.17.1" - "@react-aria/interactions" "^3.21.3" - "@tanstack/react-virtual" "^3.8.1" - -"@headlessui/tailwindcss@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@headlessui/tailwindcss/-/tailwindcss-0.2.1.tgz#1becc201f69358a40e08bd676acc234b2cabe6e4" - integrity sha512-2+5+NZ+RzMyrVeCZOxdbvkUSssSxGvcUxphkIfSVLpRiKsj+/63T2TOL9dBYMXVfj/CGr6hMxSRInzXv6YY7sA== - "@img/sharp-darwin-arm64@0.33.5": version "0.33.5" resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz#ef5b5a07862805f1e8145a377c8ba6e98813ca08" @@ -1106,6 +1082,11 @@ resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== +"@radix-ui/number@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.1.0.tgz#1e95610461a09cdf8bb05c152e76ca1278d5da46" + integrity sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ== + "@radix-ui/primitive@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" @@ -1125,6 +1106,16 @@ dependencies: "@radix-ui/react-primitive" "2.0.0" +"@radix-ui/react-collection@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.1.0.tgz#f18af78e46454a2360d103c2251773028b7724ed" + integrity sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw== + dependencies: + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.0" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-compose-refs@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" @@ -1195,6 +1186,11 @@ aria-hidden "^1.1.1" react-remove-scroll "2.6.0" +"@radix-ui/react-direction@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.1.0.tgz#a7d39855f4d077adc2a1922f9c353c5977a09cdc" + integrity sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg== + "@radix-ui/react-dismissable-layer@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" @@ -1357,6 +1353,33 @@ "@radix-ui/react-context" "1.1.0" "@radix-ui/react-primitive" "2.0.0" +"@radix-ui/react-select@^2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.1.2.tgz#2346e118966db793940f6a866fd4cc5db2cc275e" + integrity sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA== + dependencies: + "@radix-ui/number" "1.1.0" + "@radix-ui/primitive" "1.1.0" + "@radix-ui/react-collection" "1.1.0" + "@radix-ui/react-compose-refs" "1.1.0" + "@radix-ui/react-context" "1.1.1" + "@radix-ui/react-direction" "1.1.0" + "@radix-ui/react-dismissable-layer" "1.1.1" + "@radix-ui/react-focus-guards" "1.1.1" + "@radix-ui/react-focus-scope" "1.1.0" + "@radix-ui/react-id" "1.1.0" + "@radix-ui/react-popper" "1.2.0" + "@radix-ui/react-portal" "1.1.2" + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/react-slot" "1.1.0" + "@radix-ui/react-use-callback-ref" "1.1.0" + "@radix-ui/react-use-controllable-state" "1.1.0" + "@radix-ui/react-use-layout-effect" "1.1.0" + "@radix-ui/react-use-previous" "1.1.0" + "@radix-ui/react-visually-hidden" "1.1.0" + aria-hidden "^1.1.1" + react-remove-scroll "2.6.0" + "@radix-ui/react-slot@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" @@ -1426,6 +1449,11 @@ resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz#3c2c8ce04827b26a39e442ff4888d9212268bd27" integrity sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w== +"@radix-ui/react-use-previous@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.1.0.tgz#d4dd37b05520f1d996a384eb469320c2ada8377c" + integrity sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og== + "@radix-ui/react-use-rect@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz#13b25b913bd3e3987cc9b073a1a164bb1cf47b88" @@ -1440,6 +1468,13 @@ dependencies: "@radix-ui/react-use-layout-effect" "1.1.0" +"@radix-ui/react-visually-hidden@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz#ad47a8572580f7034b3807c8e6740cd41038a5a2" + integrity sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ== + dependencies: + "@radix-ui/react-primitive" "2.0.0" + "@radix-ui/rect@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.0.tgz#f817d1d3265ac5415dadc67edab30ae196696438" @@ -1511,7 +1546,7 @@ "@react-types/shared" "^3.26.0" "@swc/helpers" "^0.5.0" -"@react-aria/focus@3.19.0", "@react-aria/focus@^3.17.1", "@react-aria/focus@^3.19.0": +"@react-aria/focus@3.19.0", "@react-aria/focus@^3.19.0": version "3.19.0" resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.19.0.tgz#82b9a5b83f023b943a7970df3d059f49d61df05d" integrity sha512-hPF9EXoUQeQl1Y21/rbV2H4FdUR2v+4/I0/vB+8U3bT1CJ+1AFj1hc/rqx2DqEwDlEwOHN+E4+mRahQmlybq0A== @@ -1547,7 +1582,7 @@ "@react-types/shared" "^3.26.0" "@swc/helpers" "^0.5.0" -"@react-aria/interactions@3.22.5", "@react-aria/interactions@^3.21.3", "@react-aria/interactions@^3.22.5": +"@react-aria/interactions@3.22.5", "@react-aria/interactions@^3.22.5": version "3.22.5" resolved "https://registry.yarnpkg.com/@react-aria/interactions/-/interactions-3.22.5.tgz#9cd8c93b8b6988f1d315d3efb450119d1432bbb8" integrity sha512-kMwiAD9E0TQp+XNnOs13yVJghiy8ET8L0cbkeuTgNI96sOAp/63EJ1FSrDf17iD8sdjt41LafwX/dKXW9nCcLQ== @@ -1810,18 +1845,6 @@ dependencies: "@tanstack/query-core" "5.62.7" -"@tanstack/react-virtual@^3.8.1": - version "3.11.1" - resolved "https://registry.yarnpkg.com/@tanstack/react-virtual/-/react-virtual-3.11.1.tgz#708a5e27041f8678f78b96228376f7a35b6a29fa" - integrity sha512-orn2QNe5tF6SqjucHJ6cKTKcRDe3GG7bcYqPNn72Yejj7noECdzgAyRfGt2pGDPemhYim3d1HIR/dgruCnLfUA== - dependencies: - "@tanstack/virtual-core" "3.10.9" - -"@tanstack/virtual-core@3.10.9": - version "3.10.9" - resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.10.9.tgz#55710c92b311fdaa8d8c66682a0dbdd684bc77c4" - integrity sha512-kBknKOKzmeR7lN+vSadaKWXaLS0SZZG+oqpQ/k80Q6g9REn6zRHS/ZYdrIzHnpHgy/eWs00SujveUN/GJT2qTw== - "@tsconfig/node10@^1.0.7": version "1.0.11" resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" @@ -4163,11 +4186,6 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -tabbable@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-6.2.0.tgz#732fb62bc0175cfcec257330be187dcfba1f3b97" - integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== - tailwind-merge@^1.14.0: version "1.14.0" resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz#e677f55d864edc6794562c63f5001f45093cdb8b" @@ -4360,6 +4378,11 @@ util-deprecate@^1.0.2: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== +uuid@^11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d" + integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"