From c92cc595cc381b4e352a78c7524280341c23aa4a Mon Sep 17 00:00:00 2001 From: Maria Jose Del Valle Lopez Date: Tue, 15 Sep 2026 12:29:17 -0500 Subject: [PATCH 1/8] activos TI --- public/Plantilla Masiva.xlsx | Bin 81163 -> 81226 bytes src/Models/ActivoTI.ts | 85 ++++++ .../ActivosTIFromSupabase.ts | 286 ++++++++++++++++++ .../ActivosTIRepository.ts | 42 +++ src/repositories/repositoriesContext.tsx | 40 ++- 5 files changed, 438 insertions(+), 15 deletions(-) create mode 100644 src/Models/ActivoTI.ts create mode 100644 src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts create mode 100644 src/repositories/ActivosTIRepository/ActivosTIRepository.ts diff --git a/public/Plantilla Masiva.xlsx b/public/Plantilla Masiva.xlsx index 73fd1a9116edb70c20b906b42ed1f365f95765de..2810bb0905e4c628a7ec23ef9c738f273502a79c 100644 GIT binary patch delta 10787 zcmZ9SWmr_*`tTXLySux)JEdEsL%LgP0FmwiHXtG0-L2Bnl7fJgGzdu7J3P<1{^wlp zd|1D=?|s+0SL_e7;_l$T-NDxzA%l~`d@yU_U|_URU|_IdU|{^6x%}Kbovhs4oH+em zoU8S<-HL>> z9a6qRZ;hH-9}nXD8N5u?(NH4GYnG@MWc?A5123=f+{fmrHM%CuL0REj+BUemnC>Fs z)y3@1qWOuhiL*hD8#)a0&d7LLWl)4o>P+|unb{1k?}7=w{1?4$S|gNKI$7G4vE(AH zt4au#B|m6yHc8QY&6*+<#VgR>R1(Th=P-Zb;xibmKES6 ztzM-3@WW{o!D#2#6=mR%+2=Yi7TCG}mp9Xwg+XluRd^t%#ubLNrI_nsSn%r&JN*3^ zu}Rc6txTKR?w^rYf2Bq$!2<{RpX(oI?TL%$?FXZN1!GT$d3ueXt!D51?MSbcsXX9g z@hE9r;y-iFQqwPtv15!l34LTWF)29d7!s|?ZEN`D=4Vo=l;(S!+C3W_{8Uqo#33QcS(EVaSjE7XN13C7E82V7g9sNcYX3wAX_x z3FFb92<+Zg)uR@ER!yaY6)-40s zx4!%iqW&eT9ZEo%kEhY=XRypM{md6d>|cV&#}`RoB3~o->HdY_B8pEHB8?mnHGUv} zpZZ-6EmH>9!_A2C>+qOc-qweng5%d7*zy@GSZ+1cL&0OdWtQYfv%-5K>t9G_uJJS+ z(c^qcsNrm8QKBh%`1oz`RfnOLmWj6TN7xRaXg^$e$Q_U?%I7E#NC zRXe*x&Y9dPvLFU&w4h&+bL>IhvkFXyks~dp!1f{C0Q=zbm+K)MZSuQ6GsL1hGfNKN z*C|qx#oysZ_6n5CIk(M&m9GzED$gi>7GM*%hntGEw?x&x)eOwr!Hi54a1Trr@Sw9h zSk)+U*xMgJNJ@DIH*c3xKAy0Z8Oe@Te4rjh>`n0M#EgMw5nQADb(`nBhauaAguhBW zoDmn*N)Q>mcwX4PySi!5d%1~cbo_Mp9>XIvyNyL#z#Df}KD(n%efXw6z{I2tEh;BZ zzeXy_+ZNO9`QrxBAIUO9(%~sXQVmHdPRhFG;7ra8uCtb*FV@5eiz`SM@BR#1-39u= z-wlVhqAXg|>CTPXJP1KR69i2+Xl}45DF=x@p$ITAUm@<80$}Bgx%MBb^~&F)sIeN= z>OQ2a0mX}E__x=X42Qp0opYNN!sX?>gyr%!HrzAMs^pB6uQMBSoEdpZ9U3EqNQ{1e zT#0#jGA(Rx9PsZnDn%sAE3u{ZFz|T2>2l(6{slF|+YizD>)H?L`o1#se z&?h6DH>Knu-;KfPE-V^Ay0%eyXXy{etlwlESHEVxf|BQ1r`+WmO=E<*ePumTJYP#0 z?;0=ovo(4BOlRp?aY>I)Gj?wniOZ4F(|B7k{&0I7Iqj_ta{|XZt0t;-Q`Ik3lvR2F z=GV=#OXF_br2A97;1q(st}7uh@kTT0EPn?vi*@k2W9c(^GX2R#Ro388^)!cMeQ(?~ zH^yBX3~(`>n=hXw!Q#dwEd-OU+PxDs`(57;n=XImNQ*SmxA4|SwD#tLW;k%cNj1lr z>rd>IySCak{AlX0-SA{F9#6~*$6u)%uy)}rI;fkJi$!%XtoKdX@Y&q&mo;YGgk~8C ziq>@;Qh3F|bc0m4sdXEBpGS>0^*h=87xxoQ7JZWsU)P*R9+_`F_)dHoAkV@k{=yEv z#kR}}nY|`P8ZJ&sI=L8WhTUk3 z&QHn}ESrY{+cJ()hmcgUefgYUH}`2Zn$$N~Y!TiZc0#IH0aIrVtfT0= zi6{V8xaHPTd9Xy&H%Z~+RCUl!4t`zyvFpycdNic>$H@(YEql)g%gxWyc4{mI?a8^ zjs~BtbT|7|bht7P`D{}P0o{~mZz%H*nRh6|C(O?%GbkhlgsWdS+ST@5FqvvXt&Gzu z!R{wOX#knD7P!)n*_Pz>f zfj{C9OL0!>K)Z~kj#mPOX^y&n=CWz zz&|`!L~(=jD{qm2HS4&zR=pe+owyx_R*3N08orW>$zm&SM&r+{Y;ZoJJhCtnwF0BQ zms0nMV$sartUbTq5ho`rVmvxKJ55raYal3sPVDsIJ`9x*^Hz#eqwFc{EC&wJ8y^~aSW2=L0 zw-7B!t~@F`w%f&BuD~(3sf!;>t9xgbT&`_hfk{pGWeabheL|;CA!Jnh&kMl&5Bt;E;E3 z#HS=BN7}&(`$w+3i%kz)8DQMmgr}~>*{#Rb3#*Qs?q!0}K&RZt4Xi7~z^1MQdhkQb zfc*a2#lvtMoqp@ymzv`^U1!1@P>Qd#YbxKhd$aU_eT# z!;@7qdL&mQM5QY~VaSL{{b?}({CF_iGhR6sIl$zWE7+c*lpkQ)1Bi-nyT;`#d{#ndwt8qO=5+D_s++#JgO?>E zdX6mT#>y(2rddF77$Yy(okK1pA%4=eZ~a(7eTYufiRIjB2y*1x<NGOOTF9oulbjZLu5ubHJLx~&ZGLBTZB0srOkNP9^@ zy@iV;>sD5Z4>yTE$6|bZTt$>rL-kqfQVfZJPxM4}Ig&LJkBWXMFCO*^O1YQ~!mW>Y zldWb*f0mW?)e>!WEoMxZzSY?uF;RL%ViaNTnhMQ$jQM^lwP35G3QAd56P~1)21_f` z>$W3{pTZ5UCV`yb_`_0-=z9#XvUCjQSEwElnOlr z^YyWYXloG#baQIYj4my57##C9y%R1fqomx|YCX&@Ew_mt8^SVT0hht-XwNam9v!Dbt_2_QymEn>rVM61>CrppOCC;V=2C>^Bikx zTN^r1zpw|lX7_|#cHeZ+`Cz8hKM3mT7wwh2dHsIH;>R=Vnp);tx309{yQD?!JIB+g zqn=aL&1r_xT=)vQVx5(k@3Q>KPeSLj8g6t}nrW-{>>+06)|RcLe#+KBrteCzMsuuAK}%dK7nl&tBD&c^@tVg;}hxm8fqHt60Yk z^u^SdRU$cgj;Ey+b6YC6VWKMU9}Ozm#7=Bf$;<09xQ$zm(yHe@V;qDEM^6YLrkh^4 z9}cQ$#tkr>PFhHzhC{`Qbz1!z-0i)Ugma5>9&Y$CqavQ4N>PeP$eKEmUlkG4rPF?i z5M>0Pg9I5{N~Mo=SLB>knMGt-MSk<>j8xO`JNoX9`tEQX=&-B^r96UZZNEiwHs_fO?v#E zQ?r5WlDha`87)_@`tTtf^;2ZJ&M3+F>*fHk%u1e6pzDfcK?thLJ$#+qr}H=75qjTm zi7VdD)XjXWdzJig517{SEDpE{8~>!fM<%nyPy54~-s=u#jxt6P#!=p-j9H{)C`$Px z*-S~Aa%m|>V~yEP`_jnCCVQT|A}>i!1JC0QAwf6xuXVBF8tK-zlbOGi(p!=a5(+ac zV52ka5xA+L%<`0@BfS>Em`TwxNw%kAtdH)jq&qg1BQ9m(=&5w+5Zq7a(MrlCpG8Ys zbAHf?6y%lBue2?S^58G(D|rOqd6q6mV~CkG5(|7IBxn=J6k;DCqC@r7wg1VK^NpWP zJvL~;zUN8TB71dP=c+1QjN1p^GM%wz2rRAs_-8}#1SnxS$PW8~oUMTSB=48?gLMX@k|ItoZBKEm28_FE zvF}`_T}Y!|m0py)|J4JI$^ zh1sABUsnX~I{B!X$cGUn!=t7bC+vmVRLWKB8oH7^KsIw1S!KuN39on@G`9^CBDQs z&62==Bg;Y5v%fUIt}U>kW6GzuiJ;}wvm;gXeP=N|4dV%h<-5;C)U@w;<5cEhJqo`O zj}37@MJB1ec#E9HZ`sB$?e4*9&_-&; zddiQ7>Vh#~(G5$z;N!dX!r0t2qE%E|@eei^Y~?y7BzOlEKGI{1tax#F^dSh*-q>3c zTb;kU@W~vaDb4)aUu+S64^O^<8NPbJS5uM7NWKKO%;z~*w(3K)4@(;7r5SevxzbwSE`H2qMdNU>gPKwU_(2nyR30ic~)G+;70qmL} zF1cA^_BK<&npTa{ls1RcEG6sO!a$5R)K0}W9sUUovA;>p3A{0CFeI)Ttud-%uSj)v z4jjPz=@5a$oHY@Z@5GgRrdwqu-H{q1B-&dOT6`2ZsVi}TxL)J;fhR@Q5PDkoy_ z#gLIF&!@mkatyVq`_L+FQV79eL!>o=DP2R5%mOvLP;Rl6``CM1BVHb2)1)|4QlFh3 zr*3GW=bUSCJ@P6o4c1KHs&lFfcF@7OO(qGI=f6ocTSfZSUs>9d3g$+p@SQn}CVVoM_x}LqLk2JcfM{=0Yi{VB+cAd#ytg zjDmiJRPutYGS3K#wXj#LkvLhp46sRtweo+8U21>O_y>uGEA`%LooB}euD=a5QiE>R zv5Gc_F3fqdn1TK!lKEdGMB*nib^}Z#kZ_bkQ+y>%*d7HFARjU7Mlu+qCrLKkpUj8+ z`^R-zQ4|#`Aut{<#7jZ}JT>a@CE}2~B+5=krTJG5ky^5y$6f##X|C6U`L>ahR9MB~ z_vL3v`k1aRWM3tXKQtrqMP1eX-X)peh$apl&oNR|-W$_jP%3(I?B%?#-F_qOce0q- z38(_6dfKL{bWK5{^Pkw6oP^)Z*4KUOurArBpNBoZNl+eb`4pUl1;!M(%Bk2;)9J4b zuf^G==g&#DuNOf<+f^mCBRCcaVBI4X))@L41wNgYGaaOV*8 zx)a!^n}1AQ}?I z?e@;~M8*a*%31aF#Z%U-K>?>xsZUj&nYz1K&`Bs=Qlq6kca-O8UfQhpD` zB5;}a!)OjP*x}n07LFHNeD#F?kkX$_KlwIERxPKVGOE+d%=x>SA8zV^7Z@jX;OtC} zqI3)f&(NR_KPiDOhsoR!;+8z_3ayh{!c3FPdwePn+kip}(REDX_YX^Ap^K8g;2qX1 zsq~V-oF8#>-*@{`F(F^jmPibvY<5S@Y3RxEBwVj?$1{los5|b+-qab zhcq0m5_bCTpgy(`8BULi^pL{6W1?e`yN>VSO0W?Q=}phuRlXK<*zOC;&VW3h(|&|J z2Nd?vH{CP6zb!cRiDg z^Wg3G-wH<0LB1QBgf;=3Y#B+q&Zp_bQ!~P5{&TKc4c+!w<$F}S0_X9Xd@CDp9yu#Z z_ZnJ?zqd$@6TP`YX12xZ25P;6vV&TaBFrr}5d**cXrDeejb{U@gC{geXLHdcZDHZbAF9FoMowDyx1QoAd?3~yDRh<1`geNVGV`M+m^%SSY@Alk8EX3Vn5Ubo-JghA?@D>MhfsuBU zueGXHSC*raF6sB*KN*2zM5V7;FLXaKt@UUfpK8o))b#lJsO}1=7bdQNH&;m|Y3{Xj zqGt~k8PM5I4E0;1(l7m#K>poiK@ zPkn_6F3HTXkX|-{uFqJ`&W~>L;MD-|DDSgo$1{_)G}|ky4^_tFY?%YGj(0X_kDmhE zaoIi#nnijfp7)?5ClFmwxW^zsw-;Z!Q@=tD-EXD2ne)T>~llsyK&@p-#8vZJ$w{FHt_@GWx#m1D}^ zuT$lP?yHA42L&o@Y58^YL`!|V;;-U*9B=WLnGWv*pOIi-o}bf{3#-)9=7LGJMT|qf9C8M;$LptBG5uMV8XP_N7`p+Hd{O)@ znWqMEtJTJehoYKAMt$)bGmdB7jf`gF`G#m*&$=TJwHTrA;+p9XO=*S(qi*TqV8~S0 z)vlw=b36Y9dv8^qTT z`*5aTVezUYNm96S-7AeXdQnK@kV6X>z}>uLq7mxQK9}MgIW6wildgTvID>n>)^WWg ztawme1@S)1f5cL-!YKY#BShOh;e!;Mv9G`UT$2{q;Ea6`gMzWy45Ylh&p-&II>^*x z&JEQedgZ)ocWynLeC zLl-6+&HCWFNQSMbG=WO>MY7!iCNmgV&b?G28lJKmV%qA=ZXs63l7WcsKXXiMg8Np6 z!Y=werYXxTx3TnhqD?91uqMp0Dnzj|#s=)&%pwdKuidp%&%aL9R4)}bH6tgfC@R#I zrRiG5AOJ8dT-cUsq90=aL|96AYQ$4f3!)@y?nZzG`cAct8dR8+aq_60*(PI9Jh55> zwsLL;*zKP@ac{Afvq!>D{N6Zo!JPx=u;iU3)ZoI*^`|vb=$bh#r2U0fHh_!PZa(sZ zi>D#Ii9T3+hD(CK@? z&MZ7v=@79sm!<1Ytv`!ASix5o+F0cPj@W>Tq=V=AW!(?f98gbA=bOX)4#rv90}Mk7 zy#i>9CmvT*6jrztGbFl^Wvd@X5-(V7c@gBbZnMaD_0cH;L-9MZmo!(I+unzjy^^M8 zyz&zG&XLdF@9M&8D)ol$F?Pes&Es2Ler-y~U{He+{mvrRl5(Hbz|uv``Jo-yx!7Ff zZ77!7(XeyzIuBD>3=`(E#pmBd+zByEiOUwh9&JON)x{(HY_Ds;aJv-Q~S3BYc zF1>L@N;3C(y=~Ys{p#9j<@&hKdbckx`&dpcU*@M0X6)S6u9Vqhh{GM#u+hr#zV1kC zS?Ji_l5cR^>NWm`qvkuG%l94*e^=_(KX$n+=G%5%McsSKUL&_2&EplEEONZo{1Sk; z;8kxHdWl~ID2?pjja97inItCfB8vp)Bh^yZ(QW^2 zKx>yB*(dvUPxu!F4jJ1s;IkYOA+m^s1sqyL4yAh-f~*Es=4V)%vg@vEj=e|p7nNw( z7mYm_sD_zhC{%+B-G=A-8vky~Zsn2Bq|4wLg5yKbbjtx*0Dd5X=zuzS5Jwve5(oqW zE+{~h045@c1CXQyeTAS-pab#nP?ZO$sO={_2p@#!M+Qe8fgjDA*s+OGmPt(-FyXo?q(WY!8loJYtr)hXL8oCIWHCpfX;$)` zJ&P#R2ucYUq^R9FX%EE=f7Si3r7Lq-$P{wp_$Zbo>{NO1l`f67it1ffRK!$W2A))( zti7(26(`d7dvHe%O7IC-zQ5+W-4}ZL@L0FbY_FUo!;YLJy|C!Sjp_sT#UhPpq@j>n z2v{_)cd7wsa5)R3lZhh?6E}@9AL8WooRFJilJ1MOLQW1a{1JW_l5rakv-O27WUdbG z-a(e0%kRe|dMiWpBgZsT@JD}IBBvQOSCNiO81zo{R=2fP=Mm0ozMBEBcxq47Ro!md zNo@_@uJzK)kas>xow0Io5xygdxv0!BXxfbTho5t>4s*8)7HZ}iV&-TnXjjLAZKijr!jkQ$Q*eNFA0D z9oV1(v6KJbl?!Yb7-Hy6s{j7dF#$Jf5CI?wZOH!n&*1;BrV6-HgA`yXIDr;wkQ6MT zAaF$u5&^{l+%zB=SS}3+i3S6(PXodR%4tBPuR07*CNCuebp$GyT zU*HJTyubkX0|l(L14P|{2vB7Jv4UO$z6{VeMnDG?um)}rX*VLkmkt_)#p8d2r~^`r zPz7Bth=dmrkj)4+eFY4@h~)hsoPIm3n5CPUJV``ADHAjxYs&v7tO2;OLJiG<#usP-mr%ee01z&K2+(4K zrY{TxvOz^OAAvqLXh57?0G1uf>paLt9uZ)|4js<#Pmt+PL_j{Y4;lx)u|plT0~j1o zlmJ>#z^0W$LduDNDh{Xu7+83D;{cFwLSX`!yg(huhXPih5kl2S1l(~#oia8gOlG!wtm<;QfL+pzQ^@z}*X?0SO)` z0)WsL*aQ79&;k&7p@EggAUtFLaZEVzzm7$L_!kAHDG1IK5wOb(%?5V{z~h5vgFg#- z`c4G6@IhrT^FY>%>|z13y6~?f0FEC-3fclF`Jr*f0LvHj0mV?jmaRcj)`);-eyC-} zFG%PwA^?#aIyJ_d{~H<%xClU}b{wb^fZDX}K??SW08~M!P0;});eZHGf9ax+F?!{&4 z?hZ6if$)I{QK%1jOyH9!G?h5umncXN6bT53LF?><5BQ6Lq~UZ3K^cV5<7Qe6#0oD> z41xhpiJ(Urxj3{kill(DIOr9;IyqGQKn4~6bD)I2WTr<6{1OK-AWBd{zg{r^4}snO A;s5{u delta 10603 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 B$twT= diff --git a/src/Models/ActivoTI.ts b/src/Models/ActivoTI.ts new file mode 100644 index 0000000..7bcba2b --- /dev/null +++ b/src/Models/ActivoTI.ts @@ -0,0 +1,85 @@ +export type CategoriaActivo = + | "Equipo_computo" + | "periferico" + | "red infraestructura"; + +export type EstadoActivo = + | "disponible" + | "asignado" + | "en_prestamo" + | "en_mantenimiento" + | "dado_de_baja"; +export type Ubicacion_Tipo = "bodega_central" | "tienda"; + +export interface ActivoTI { + id: string; + codigo_inventario: string; + categoria: CategoriaActivo; + tipo: string; + subtipo?: string | null; + marca?: string | null; + modelo?: string | null; + numero_serie: string; + fecha_ingreso: string; + proveedor?: string | null; + estado: EstadoActivo; + ubicacion_tipo: Ubicacion_Tipo; + tienda_id?: string | null; + usuario_asignado_id?: string | null; + fecha_fin_garantia?: string | null; + notas?: string | null; + prestamo_activo_id?: string | null; + created_at: string; + updated_at: string; + created_by: string; + updated_by: string; +} + +export type CrearActivoDTO = Omit< + ActivoTI, + | "id" + | "created_at" + | "updated_at" + | "created_by" + | "updated_by" + | "prestamo_activo_id" +>; + +export type ActualizarActivoDTO = Partial; + +export interface ActivoTIErrors { + codigo_inventario?: string; + categoria?: string; + tipo?: string; + numero_serie?: string; + estado?: string; + fecha_ingreso?: string; + ubicacion_tipo?: string; + general?: string; + tienda_id?: string; +} + +export const validarActivoTI = ( + data: Partial, +): ActivoTIErrors => { + const errors: ActivoTIErrors = {}; + + if (!data.codigo_inventario?.trim()) { + errors.codigo_inventario = "El código de invetario es obligatorio"; + } + + if (!data.numero_serie?.trim()) { + errors.numero_serie = "El numero de serie es obligatorio"; + } + if (!data.categoria) { + errors.categoria = "Debe seleccionar una categoria valida"; + } + if (!data.tipo?.trim()) { + errors.tipo = "El tipo de activo es obligatorio"; + } + if (data.ubicacion_tipo == "tienda" && !data.tienda_id) { + errors.tienda_id = "Debe seleccionar una tienda si la ubicacion es Tienda"; + } + + return errors; +}; diff --git a/src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts b/src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts new file mode 100644 index 0000000..5089dee --- /dev/null +++ b/src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts @@ -0,0 +1,286 @@ +import type { ActivoTI } from "../../Models/ActivoTI"; +import { supabase } from "../../Services/Supabase.service"; +import type { + FilterActivosTI, + ActivosTILoadResult, + ActivosTIRepository, +} from "./ActivosTIRepository"; + +export class SupabaseActivosTIRepository implements ActivosTIRepository { + private readonly tableName = "activos_ti"; + private readonly batchSize = 100; + + private sanitizeSearchValue(value: string): string { + return value.replace(/[,%()]/g, " ").trim(); + } + private buildActivoQuery(filter?: FilterActivosTI, includeCount = false) { + let query = supabase + .from(this.tableName) + .select("*", includeCount ? { count: "exact" } : undefined); + + if (filter?.categoria) { + query = query.eq("categoria", filter.categoria); + } + if (filter?.estado) { + query = query.eq("estado", filter.categoria); + } + if (filter?.ubicacion_tipo) { + query = query.eq("ubicacion_tipo", filter.ubicacion_tipo); + } + if (filter?.usuario_asignado_id) { + query = query.eq("usuario_asignado_id", filter.usuario_asignado_id); + } + const trimmedSearch = String(filter?.search ?? "").trim(); + let searchFilters = " "; + if (trimmedSearch) { + const searchValue = this.sanitizeSearchValue(trimmedSearch); + if (searchValue) { + const searchPattern = `%${searchValue}%`; + searchFilters = [ + `numero_serie.ilike.${searchPattern}`, + `codigo_inventario.ilike.${searchPattern}`, + `tipo.ilike.${searchPattern}`, + `marca.ilike.${searchPattern}`, + `modelo.ilike.${searchPattern}`, + ].join(","); + } + } + + if (searchFilters) { + query = query.or(searchFilters); + } + query = query.order("create_at", { ascending: false }); + + return query; + } + async loadActivos(filter?: FilterActivosTI): Promise { + try { + const pageSize = Math.max(1, Number(filter?.pageSize ?? 10)); + const pageIndex = Math.max(1, Number(filter?.pageIndex ?? 1)); + + if (filter?.paginated) { + const from = (pageIndex - 1) * pageSize; + const to = from + pageSize - 1; + const { data, error, count } = await this.buildActivoQuery( + filter, + true, + ).range(from, to); + + if (error) { + return { + data: [], + message: error.message, + status: false, + }; + } + return { + data: data.map((d) => this.toModel(d)) ?? [], + hasNext: from + (data?.length ?? 0) < (count ?? 0), + message: null, + pageIndex, + pageSize, + status: true, + total: count ?? data?.length ?? 0, + }; + } + const allRows: any[] = []; + let from = 0; + let total = 0; + + while (true) { + const to = from + this.batchSize - 1; + const { data, error, count } = await this.buildActivoQuery( + filter, + from === 0, + ).range(from, to); + + if (error) { + return { + data: [], + message: error.message, + status: false, + }; + } + const rows = data ?? []; + if (from === 0) { + total = count ?? rows.length; + } + + allRows.push(...rows); + + if (!rows.length || rows.length < this.batchSize) { + break; + } + + if (total > 0 && allRows.length >= total) { + break; + } + + from += this.batchSize; + } + return { + data: allRows.map((d) => this.toModel(d)), + hasNext: false, + message: null, + pageIndex, + pageSize, + status: true, + total: total || allRows.length, + }; + } catch (error: any) { + return { + data: [], + status: false, + message: error?.message ?? "Error cargando los tickets registrados", + }; + } + } + async createActivo(payload: Partial): Promise<{ + data: ActivoTI | null; + status: boolean; + message: string | null; + }> { + try { + const supabaseActivo = { + codigo_inventario: payload.codigo_inventario ?? "", + categoria: payload.categoria, + tipo: payload.tipo ?? "", + subtipo: payload.subtipo ?? null, + marca: payload.marca ?? null, + modelo: payload.modelo ?? null, + numero_serie: payload.numero_serie ?? "", + fecha_ingreso: payload.fecha_ingreso ?? new Date().toISOString(), + proveedor: payload.proveedor ?? null, + estado: payload.estado ?? "disponible", + ubicacion_tipo: payload.ubicacion_tipo ?? "bodega_central", + tienda_id: payload.tienda_id ?? null, + usuario_asignado_id: payload.usuario_asignado_id ?? null, + fecha_fin_garantia: payload.fecha_fin_garantia ?? null, + notas: payload.notas ?? null, + }; + + const { data, error } = await supabase + .from(this.tableName) + .insert(supabaseActivo) + .select() + .single(); + + if (error) { + return { + data: null, + message: error.message, + status: false, + }; + } + + return { + data: this.toModel(data), + message: null, + status: true, + }; + } catch (e: any) { + return { + data: null, + message: e?.message ?? "Error creando el activo", + status: false, + }; + } + } + async updateActivo( + id: string, + payload: any, + ): Promise<{ + data: ActivoTI | null; + status: boolean; + message: string | null; + }> { + try { + const { data, error } = await supabase + .from(this.tableName) + .update(payload) + .eq("id", id) + .select() + .single(); + + if (error) { + return { + data: null, + message: error.message, + status: false, + }; + } + + return { + data: this.toModel(data), + message: null, + status: true, + }; + } catch (e: any) { + return { + data: null, + message: e?.message ?? "Error actualizando el activo", + status: false, + }; + } + } + async getActivoById(id: string): Promise<{ + data: ActivoTI | null; + status: boolean; + message: string | null; + }> { + try { + const query = supabase + .from(this.tableName) + .select("*") + .eq("id", id) + .single(); + + const { data, error } = await query; + + if (error) { + return { + data: null, + message: error.message, + status: false, + }; + } + + return { + data: this.toModel(data), + message: null, + status: true, + }; + } catch (error: any) { + return { + data: null, + status: false, + message: error?.message ?? "Error cargando el activo solicitado", + }; + } + } + toModel(bdModel: any): ActivoTI { + return { + id: bdModel.id, + codigo_inventario: bdModel.codigo_inventario, + categoria: bdModel.categoria, + tipo: bdModel.tipo, + subtipo: bdModel.subtipo, + marca: bdModel.marca, + modelo: bdModel.modelo, + numero_serie: bdModel.numero_serie, + fecha_ingreso: bdModel.fecha_ingreso, + proveedor: bdModel.proveedor, + estado: bdModel.estado, + ubicacion_tipo: bdModel.ubicacion_tipo, + tienda_id: bdModel.tienda_id, + usuario_asignado_id: bdModel.usuario_asignado_id, + fecha_fin_garantia: bdModel.fecha_fin_garantia, + notas: bdModel.notas, + prestamo_activo_id: bdModel.prestamo_activo_id, + created_at: bdModel.created_at, + updated_at: bdModel.updated_at, + created_by: bdModel.created_by, + updated_by: bdModel.updated_by, + }; + } +} diff --git a/src/repositories/ActivosTIRepository/ActivosTIRepository.ts b/src/repositories/ActivosTIRepository/ActivosTIRepository.ts new file mode 100644 index 0000000..0c9d02e --- /dev/null +++ b/src/repositories/ActivosTIRepository/ActivosTIRepository.ts @@ -0,0 +1,42 @@ +import type { ActivoTI } from "../../Models/ActivoTI"; + +export type FilterActivosTI = { + categoria?: string; + estado?: string; + ubicacion_tipo?: string; + search?: string; + usuario_asignado_id?: string; + pageSize?: number; + pageIndex?: number; + paginated?: boolean; +}; +export type ActivosTILoadResult = { + data: ActivoTI[]; + status: boolean; + message: string | null; + total?: number; + pageSize?: number; + pageIndex?: number; + hasNext?: boolean; +}; +export interface ActivosTIRepository { + loadActivos(filter?: FilterActivosTI): Promise; + createActivo(payload: Partial): Promise<{ + data: ActivoTI | null; + status: boolean; + message: string | null; + }>; + updateActivo( + id: string, + payload: any, + ): Promise<{ + data: ActivoTI | null; + status: boolean; + message: string | null; + }>; + getActivoById(id: string): Promise<{ + data: ActivoTI | null; + status: boolean; + message: string | null; + }>; +} diff --git a/src/repositories/repositoriesContext.tsx b/src/repositories/repositoriesContext.tsx index aaabc3c..73c94c9 100644 --- a/src/repositories/repositoriesContext.tsx +++ b/src/repositories/repositoriesContext.tsx @@ -13,16 +13,18 @@ import type { ANSRepository } from "./AnsRepository/AnsRepository"; import { SharepointANS } from "./AnsRepository/SharepointANS"; import type { MessagesRepository } from "./ParticipantsRepository/MessagesRepository"; import { SupabaseMessageRepository } from "./ParticipantsRepository/SupabaseMessageRepository"; - +import type { ActivosTIRepository } from "./ActivosTIRepository/ActivosTIRepository"; +import { SupabaseActivosTIRepository } from "./ActivosTIRepository/ActivosTIFromSupabase"; type RepositorySource = "supabase" | "sharepoint"; export type AppRepositories = { tickets: TicketsRepository | null; usuarios: UsuariosSPRepository | null; - attachments: AttachmentRepository | null - logs: LogRepository | null - ans: ANSRepository - messages: MessagesRepository + attachments: AttachmentRepository | null; + logs: LogRepository | null; + ans: ANSRepository; + messages: MessagesRepository; + activosTI: ActivosTIRepository | null; }; type RepositoriesProviderProps = { @@ -30,8 +32,8 @@ type RepositoriesProviderProps = { sources?: Partial<{ tickets: RepositorySource; usuarios: RepositorySource; - attachments: RepositorySource - logs: RepositorySource + attachments: RepositorySource; + logs: RepositorySource; }>; }; @@ -46,18 +48,24 @@ export const RepositoriesProvider: React.FC = ({ const graph = React.useMemo(() => new GraphRest(getToken), [getToken]); const repositories = React.useMemo(() => { - const attachmentsSource = sources?.attachments ?? "supabase" + const attachmentsSource = sources?.attachments ?? "supabase"; const ticketsSource = sources?.tickets ?? "supabase"; const usuariosSource = sources?.usuarios ?? "sharepoint"; - const logsSource = sources?.logs ?? "supabase" + const logsSource = sources?.logs ?? "supabase"; return { - tickets: ticketsSource === "supabase" ? new SupabaseTicketRepository() : null, - usuarios: usuariosSource === "sharepoint" ? new UsuariosSPFromSharepoint(graph) : null, - attachments: attachmentsSource === "supabase" ? new AttachmentFromSupabase() : null, + tickets: + ticketsSource === "supabase" ? new SupabaseTicketRepository() : null, + usuarios: + usuariosSource === "sharepoint" + ? new UsuariosSPFromSharepoint(graph) + : null, + attachments: + attachmentsSource === "supabase" ? new AttachmentFromSupabase() : null, logs: logsSource === "supabase" ? new LogFromSupabase() : null, ans: new SharepointANS(graph), - messages: new SupabaseMessageRepository() + messages: new SupabaseMessageRepository(), + activosTI: new SupabaseActivosTIRepository(), }; }, [graph, sources?.tickets, sources?.usuarios]); @@ -72,8 +80,10 @@ export function useRepositories(): AppRepositories { const ctx = React.useContext(RepositoriesContext); if (!ctx) { - throw new Error("useRepositories must be used within "); + throw new Error( + "useRepositories must be used within ", + ); } return ctx; -} \ No newline at end of file +} From c6d46687ed28a29dab7c23957edc7bb211f1b9b6 Mon Sep 17 00:00:00 2001 From: Maria Jose Del Valle Lopez Date: Tue, 15 Sep 2026 12:36:05 -0500 Subject: [PATCH 2/8] ajusta repositorio de activos TI --- src/Models/ActivoTI.ts | 6 +++--- .../ActivosTIFromSupabase.ts | 18 +++++++++++------- .../ActivosTIRepository.ts | 19 +++++++++++++------ 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/Models/ActivoTI.ts b/src/Models/ActivoTI.ts index 7bcba2b..928be4b 100644 --- a/src/Models/ActivoTI.ts +++ b/src/Models/ActivoTI.ts @@ -1,7 +1,7 @@ export type CategoriaActivo = - | "Equipo_computo" + | "equipo_computo" | "periferico" - | "red infraestructura"; + | "red_infraestructura"; export type EstadoActivo = | "disponible" @@ -32,7 +32,7 @@ export interface ActivoTI { created_at: string; updated_at: string; created_by: string; - updated_by: string; + updated_by?: string; } export type CrearActivoDTO = Omit< diff --git a/src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts b/src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts index 5089dee..d142c36 100644 --- a/src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts +++ b/src/repositories/ActivosTIRepository/ActivosTIFromSupabase.ts @@ -1,4 +1,8 @@ -import type { ActivoTI } from "../../Models/ActivoTI"; +import type { + ActivoTI, + ActualizarActivoDTO, + CrearActivoDTO, +} from "../../Models/ActivoTI"; import { supabase } from "../../Services/Supabase.service"; import type { FilterActivosTI, @@ -22,7 +26,7 @@ export class SupabaseActivosTIRepository implements ActivosTIRepository { query = query.eq("categoria", filter.categoria); } if (filter?.estado) { - query = query.eq("estado", filter.categoria); + query = query.eq("estado", filter.estado); } if (filter?.ubicacion_tipo) { query = query.eq("ubicacion_tipo", filter.ubicacion_tipo); @@ -31,7 +35,7 @@ export class SupabaseActivosTIRepository implements ActivosTIRepository { query = query.eq("usuario_asignado_id", filter.usuario_asignado_id); } const trimmedSearch = String(filter?.search ?? "").trim(); - let searchFilters = " "; + let searchFilters = ""; if (trimmedSearch) { const searchValue = this.sanitizeSearchValue(trimmedSearch); if (searchValue) { @@ -49,7 +53,7 @@ export class SupabaseActivosTIRepository implements ActivosTIRepository { if (searchFilters) { query = query.or(searchFilters); } - query = query.order("create_at", { ascending: false }); + query = query.order("created_at", { ascending: false }); return query; } @@ -131,11 +135,11 @@ export class SupabaseActivosTIRepository implements ActivosTIRepository { return { data: [], status: false, - message: error?.message ?? "Error cargando los tickets registrados", + message: error?.message ?? "Error cargando los activos registrados", }; } } - async createActivo(payload: Partial): Promise<{ + async createActivo(payload: CrearActivoDTO): Promise<{ data: ActivoTI | null; status: boolean; message: string | null; @@ -188,7 +192,7 @@ export class SupabaseActivosTIRepository implements ActivosTIRepository { } async updateActivo( id: string, - payload: any, + payload: ActualizarActivoDTO, ): Promise<{ data: ActivoTI | null; status: boolean; diff --git a/src/repositories/ActivosTIRepository/ActivosTIRepository.ts b/src/repositories/ActivosTIRepository/ActivosTIRepository.ts index 0c9d02e..85664cd 100644 --- a/src/repositories/ActivosTIRepository/ActivosTIRepository.ts +++ b/src/repositories/ActivosTIRepository/ActivosTIRepository.ts @@ -1,9 +1,16 @@ -import type { ActivoTI } from "../../Models/ActivoTI"; +import type { + ActivoTI, + CategoriaActivo, + EstadoActivo, + Ubicacion_Tipo, + CrearActivoDTO, + ActualizarActivoDTO, +} from "../../Models/ActivoTI"; export type FilterActivosTI = { - categoria?: string; - estado?: string; - ubicacion_tipo?: string; + categoria?: CategoriaActivo; + estado?: EstadoActivo; + ubicacion_tipo?: Ubicacion_Tipo; search?: string; usuario_asignado_id?: string; pageSize?: number; @@ -21,14 +28,14 @@ export type ActivosTILoadResult = { }; export interface ActivosTIRepository { loadActivos(filter?: FilterActivosTI): Promise; - createActivo(payload: Partial): Promise<{ + createActivo(payload: CrearActivoDTO): Promise<{ data: ActivoTI | null; status: boolean; message: string | null; }>; updateActivo( id: string, - payload: any, + payload: ActualizarActivoDTO, ): Promise<{ data: ActivoTI | null; status: boolean; From eaef6fd63615777111e060f5fd9b88db087d9fd9 Mon Sep 17 00:00:00 2001 From: Maria Jose Del Valle Lopez Date: Wed, 16 Sep 2026 18:16:15 -0500 Subject: [PATCH 3/8] Se hizo funcinalidad de inventario y sus componentes --- src/App.tsx | 443 ++++++++++++++---- .../inventario/useActivoMovimientos.ts | 0 .../inventario/useActivosTI.ts | 137 ++++++ src/Models/ActivoTI.ts | 7 +- src/components/Inventario/ModalAltaActivo.tsx | 238 ++++++++++ .../Inventario/ModalAltaActivos.css | 161 +++++++ 6 files changed, 892 insertions(+), 94 deletions(-) create mode 100644 src/Funcionalidades/inventario/useActivoMovimientos.ts create mode 100644 src/Funcionalidades/inventario/useActivosTI.ts create mode 100644 src/components/Inventario/ModalAltaActivo.tsx create mode 100644 src/components/Inventario/ModalAltaActivos.css diff --git a/src/App.tsx b/src/App.tsx index b1a3ce0..e27a1a5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,10 +10,14 @@ import CrearPlantilla from "./components/NuevaPlantilla/NuevaPlantilla"; import UsuariosPanel from "./components/Usuarios/Usuarios"; import AnsAdmin from "./components/Admin/AnsAdmin/AnsAdmin"; import CajerosPOSForm from "./components/CajerosPOS/CajerosPOS"; +import { ModalAltaActivos } from "./components/Inventario/ModalAltaActivo"; import type { User } from "./Models/User"; import { useAuth } from "./auth/authContext"; import { useUserRole } from "./Funcionalidades/auth/Usuarios"; -import { GraphServicesProvider, useGraphServices } from "./graph/GrapServicesContext"; +import { + GraphServicesProvider, + useGraphServices, +} from "./graph/GrapServicesContext"; import type { UsuariosSPService } from "./Services/Usuarios.Service"; import HomeIcon from "./assets/home.svg"; import addIcon from "./assets/add.svg"; @@ -25,30 +29,41 @@ import settingsIcon from "./assets/settings.svg"; import templateIcon from "./assets/template.svg"; import WelcomeSolvi from "./components/Welcome/Welcome"; import DashBoardPage from "./components/Dashboard/DashboardPage"; -import usersIcon from "./assets/users.svg" -import ActionsIcon from "./assets/actions.svg" -import siesaIcon from "./assets/siesa.png" -import storageIcon from "./assets/storage.svg" -import cajerosIcon from "./assets/cajeros.svg" +import usersIcon from "./assets/users.svg"; +import ActionsIcon from "./assets/actions.svg"; +import siesaIcon from "./assets/siesa.png"; +import storageIcon from "./assets/storage.svg"; +import cajerosIcon from "./assets/cajeros.svg"; import { logout } from "./auth/msal"; import AnnouncementsTable from "./components/TipsTable/TipsTable"; import { useTheme } from "./Funcionalidades/auth/Theme"; import TeamsEventForm from "./components/Ausencia/Ausencia"; import { StoragePage } from "./components/Storage/StoragePage"; import { PrestamosPage } from "./components/Loans/PretamosPage"; -import loanImage from "./assets/device.svg" +import loanImage from "./assets/device.svg"; import ReportsPage from "./components/Reports/ReportsPage"; import { useRepositories } from "./repositories/repositoriesContext"; import type { TicketsRepository } from "./repositories/TicketsRepository/TicketRepository"; import type { LogRepository } from "./repositories/LogRepository/LogRespository"; import { useContador } from "./Funcionalidades/timeCounter/hooks/useCounter"; -/* ============================================================ - Tipos de navegación y contexto de visibilidad - ============================================================ */ -type RenderCtx = { services?: { Tickets: TicketsRepository; Usuarios: UsuariosSPService; Logs: LogRepository } }; +/* ============================================================ + Tipos de navegación y contexto de visibilidad + ============================================================ */ + +type RenderCtx = { + services?: { + Tickets: TicketsRepository; + Usuarios: UsuariosSPService; + Logs: LogRepository; + }; +}; -type Services = { Tickets: TicketsRepository; Usuarios: UsuariosSPService; Logs: LogRepository }; +type Services = { + Tickets: TicketsRepository; + Usuarios: UsuariosSPService; + Logs: LogRepository; +}; export type MenuItem = { id: string; @@ -68,32 +83,163 @@ export type NavContext = { flags?: Set; hasService?: (k: keyof Services) => boolean; }; - + /* ============================================================ - Árbol único de navegación con reglas de visibilidad - ============================================================ */ + Árbol único de navegación con reglas de visibilidad + ============================================================ */ const NAV: MenuItem[] = [ - { id: "home", label: "Home", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"], autocollapse: true }, - { id: "ticketform", label: "Nuevo Ticket", icon: , to: () => , roles: ["Administrador", "Tecnico", "Listo"] }, - { id: "ticketform_user", label: "Nuevo Ticket", icon: , to: , roles: ["Usuario", "Jefe de zona", "Listo"] }, - { id: "ticketTable", label: "Ver Tickets", icon: , to: , autocollapse: true }, - { id: "task", label: "Tareas", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"], autocollapse: true }, - { id: "formatos", label: "Formatos", icon: , to: , roles: ["Administrador", "Listo"] }, - { id: "info", label: "Información", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] }, - { id: "storage", label: "Almacenamiento", icon: , to: , roles: ["Administrador", "Listo"] }, - { id: "loan", label: "Prestamos", icon: , to: , roles: ["Administrador", "Listo", "Tecnico"] }, - { id: "admin", label: "Administración", icon: , roles: ["Administrador", "Tecnico", "Listo"], children: [ - { id: "plantillas", label: "Plantillas", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] }, - { id: "usuarios", label: "Usuarios", icon: , to: , roles: ["Administrador"] }, - { id: "ans", label: "ANS", icon: , to: , roles: ["Administrador"] }, - { id: "tips", label: "Tips", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] }, - { id: "reports", label: "Reportes", icon: , to: , roles: ["Administrador", "Tecnico", "Listo"] }, + { + id: "home", + label: "Home", + icon: , + to: , + roles: ["Administrador", "Tecnico", "Listo"], + autocollapse: true, + }, + { + id: "ticketform", + label: "Nuevo Ticket", + icon: , + to: () => , + roles: ["Administrador", "Tecnico", "Listo"], + }, + { + id: "ticketform_user", + label: "Nuevo Ticket", + icon: , + to: , + roles: ["Usuario", "Jefe de zona", "Listo"], + }, + { + id: "ticketTable", + label: "Ver Tickets", + icon: , + to: , + autocollapse: true, + }, + { + id: "task", + label: "Tareas", + icon: , + to: , + roles: ["Administrador", "Tecnico", "Listo"], + autocollapse: true, + }, + { + id: "altaActivos", + label: "Nuevo Activo", + icon: , + to: () => ( + { + console.log("Activo creado:", activo); + }} + /> + ), + roles: ["Administrador", "Tecnico", "Listo"], + }, + { + id: "formatos", + label: "Formatos", + icon: , + to: , + roles: ["Administrador", "Listo"], + }, + { + id: "info", + label: "Información", + icon: , + to: , + roles: ["Administrador", "Tecnico", "Listo"], + }, + { + id: "storage", + label: "Almacenamiento", + icon: , + to: , + roles: ["Administrador", "Listo"], + }, + + { + id: "loan", + label: "Prestamos", + icon: , + to: , + roles: ["Administrador", "Listo", "Tecnico"], + }, + { + id: "admin", + label: "Administración", + icon: , + roles: ["Administrador", "Tecnico", "Listo"], + children: [ + { + id: "plantillas", + label: "Plantillas", + icon: , + to: , + roles: ["Administrador", "Tecnico", "Listo"], + }, + { + id: "usuarios", + label: "Usuarios", + icon: , + to: , + roles: ["Administrador"], + }, + { + id: "ans", + label: "ANS", + icon: , + to: , + roles: ["Administrador"], + }, + { + id: "tips", + label: "Tips", + icon: , + to: , + roles: ["Administrador", "Tecnico", "Listo"], + }, + { + id: "reports", + label: "Reportes", + icon: , + to: , + roles: ["Administrador", "Tecnico", "Listo"], + }, ], }, - {id: "acciones", label: "Acciones", icon: , roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"], children: [ - {id: "siesa", label: "Siesa", roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"], icon: , children: [ - {id: "cajpos", label: "Cajeros POS", icon: , to: (rctx: RenderCtx) => rctx.services ? :
Cargando servicios…
, roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"],}, + { + id: "acciones", + label: "Acciones", + icon: , + roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"], + children: [ + { + id: "siesa", + label: "Siesa", + roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"], + icon: , + children: [ + { + id: "cajpos", + label: "Cajeros POS", + icon: , + to: (rctx: RenderCtx) => + rctx.services ? ( + + ) : ( +
Cargando servicios…
+ ), + roles: ["Administrador", "Tecnico", "Jefe de zona", "Listo"], + }, ], }, ], @@ -101,8 +247,8 @@ const NAV: MenuItem[] = [ ]; /* ============================================================ - Utilidades de árbol: filtrado, búsqueda y primera hoja - ============================================================ */ + Utilidades de árbol: filtrado, búsqueda y primera hoja + ============================================================ */ // Aplica reglas de visibilidad a un nodo function isVisible(node: MenuItem, ctx: NavContext): boolean { @@ -113,7 +259,10 @@ function isVisible(node: MenuItem, ctx: NavContext): boolean { } // Devuelve el árbol filtrado (oculta carpetas sin hijos visibles) -function filterNavTree(nodes: readonly MenuItem[], ctx: NavContext): MenuItem[] { +function filterNavTree( + nodes: readonly MenuItem[], + ctx: NavContext, +): MenuItem[] { return nodes .map((n) => { const children = n.children ? filterNavTree(n.children, ctx) : undefined; @@ -127,12 +276,16 @@ function filterNavTree(nodes: readonly MenuItem[], ctx: NavContext): MenuItem[] // Primer leaf para selección inicial function firstLeafId(nodes: readonly MenuItem[]): string { - const pick = (n: MenuItem): string => (n.children?.length ? pick(n.children[0]) : n.id); + const pick = (n: MenuItem): string => + n.children?.length ? pick(n.children[0]) : n.id; return nodes.length ? pick(nodes[0]) : ""; } // Busca un ítem por id -function findById(nodes: readonly MenuItem[], id: string): MenuItem | undefined { +function findById( + nodes: readonly MenuItem[], + id: string, +): MenuItem | undefined { for (const n of nodes) { if (n.id === id) return n; if (n.children) { @@ -144,11 +297,27 @@ function findById(nodes: readonly MenuItem[], id: string): MenuItem | undefined } /* ============================================================ - Sidebar con árbol recursivo y apertura de carpetas - ============================================================ */ - -function Sidebar(props: {navs: readonly MenuItem[]; selected: string; onSelect: (k: string) => void; user: User; role: string; collapsed?: boolean; onToggle?: () => void;}) { - const { navs, selected, onSelect, user, role, collapsed = false, onToggle } = props; + Sidebar con árbol recursivo y apertura de carpetas + ============================================================ */ + +function Sidebar(props: { + navs: readonly MenuItem[]; + selected: string; + onSelect: (k: string) => void; + user: User; + role: string; + collapsed?: boolean; + onToggle?: () => void; +}) { + const { + navs, + selected, + onSelect, + user, + role, + collapsed = false, + onToggle, + } = props; const [open, setOpen] = React.useState>({}); React.useEffect(() => { @@ -156,7 +325,8 @@ function Sidebar(props: {navs: readonly MenuItem[]; selected: string; onSelect: const walk = (nodes: readonly MenuItem[], path: string[] = []) => { nodes.forEach((n) => { const p = [...path, n.id]; - if (n.id === selected) p.slice(0, -1).forEach((id) => (next[id] = true)); + if (n.id === selected) + p.slice(0, -1).forEach((id) => (next[id] = true)); if (n.children?.length) walk(n.children, p); }); }; @@ -183,11 +353,15 @@ function Sidebar(props: {navs: readonly MenuItem[]; selected: string; onSelect: aria-expanded={!collapsed && expanded} title={n.label} > - {!collapsed && ▸} + {!collapsed && ( + ▸ + )} {n.icon ?? null} - {!collapsed && {n.label}} + {!collapsed && ( + {n.label} + )} {!collapsed && expanded && renderTree(n.children!, depth + 1)} @@ -216,7 +390,10 @@ function Sidebar(props: {navs: readonly MenuItem[]; selected: string; onSelect: ); return ( -