From fc8cdb579ffff3e2ac0153cb46610009d7dd4386 Mon Sep 17 00:00:00 2001 From: kdeterme Date: Sat, 10 Oct 2026 15:06:26 +0200 Subject: [PATCH] refactor(controls): replace bootstrap-table with custom table and column selector --- loko/controls/locale/en/LC_MESSAGES/django.mo | Bin 17306 -> 18456 bytes loko/controls/locale/en/LC_MESSAGES/django.po | 61 ++ loko/controls/locale/fr/LC_MESSAGES/django.mo | Bin 18501 -> 19741 bytes loko/controls/locale/fr/LC_MESSAGES/django.po | 61 ++ loko/controls/locale/nl/LC_MESSAGES/django.mo | Bin 18147 -> 19363 bytes loko/controls/locale/nl/LC_MESSAGES/django.po | 61 ++ .../static/controls/controls_index.css | 799 ++++++++++++++--- .../static/controls/controls_index.js | 846 ++++++++++++------ .../templates/controls/controls_index.html | 337 +++++-- loko/controls/views.py | 82 ++ 10 files changed, 1757 insertions(+), 490 deletions(-) diff --git a/loko/controls/locale/en/LC_MESSAGES/django.mo b/loko/controls/locale/en/LC_MESSAGES/django.mo index 4f19d3461ef00f9e26addc588341d0e64466a188..27c95bc0fc314ab04e31728accd7e9294290fa29 100644 GIT binary patch delta 7133 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 delta 6008 zcmY+|30zfG0>|<5SVT}15EoPs+(ktUK@$-q85hcJ%5m4+QQULf(nmU$W0=d+3@tTN zQ&T2m=k`aG&*1JMUZVGNEzbubsX zrCEmjXSUk?FYsyOHs(BqnjEMaVN7L=$5>3mayS;FaWY1t$JQ61I<^bdp?#J<*0`DU?seW>iI3y5C<`Vi!d0w;A+$j zze9EGKB~bdSP{dySl7q!qmfFrc8+BH)o@QLTH!#{Vp)c?WeQLua}?FFyVww`bCPV2 z&2R+j_%dvQyKMaxOlQAx9cLtZp*lJMHA3U-F#ek38B}QQbL zU!kVrysf{5OsnyYcFvDPb*KSq1Ts+%+6SxRa8w6ox+&8Tx^Fc zP*dKO6H&%+K3v_9jHaPAJx$#SOw2I`)+fKf)?2W)JO!> zclKkf@u;CrML+C;>fj5gp&Vp=37G}65Pfkgsw4TRhWDb{DMEGZOzCmAG2c_rqPS=E zXX3P99W``K&!o2 zvqaSDPeEPK7q$4ZY<&)D4&O#~WUD>?2{vc{7^;ECn28~coG;=4EXV#F)LNO3ZdJTP zApkd^hH4vX5$-}YblO^My^C7CPf#5XVgW^Cebl1LKy{#>bv$Yc=c1;}gXM8cW8Qz= zXa^O1#7rT^<5|>=19_Qwg-t!w1?k8FHkqh~#v!w2=3*eek6Hr-s3|;wYOojs@F8jq zxR|d%42fm@HMBLU&`7jI^|TG@!Y-%_2U|zk`U$8T%(wfCQ61Wd42s!<`ea{0t%*md z2hh7I15qOs=BA(_Z-|=9r%;O}6?I_->b2{N>c}`-KNa2KN1W~cVx(@ZaeSmt8^HD>!!}=wryNo%9nyN?^jNXzs z)cNgEQ<;uh#9dHpVx&F(Dpq2DA%@ew$)lhi<)eo5)6xUHMyQSzV@14;y742_4SZTU zYakT0Ukf$lvDO4@JJbVZpgPnUHMJu!l=jUS3Yx>0QBS%UHPr862%bcBqy)A4@1SlF z$~e9Y8P#9_6RaCl zMAb*2I@kd<1-(#j#Zc7!vQZt#MV+@C)$m5t{qqwT|1b)>sZaw)P*ZUNwHPm3Zz5ky zb07H=z(lgLbY3#*yU_)8{cxOuS(t(sv2w;0oBT-KrhgC2It71=7JEO23PC$)>+e1MO zu0!2m3+lu@_JsY`%*K0v9c3$u~7%~XuQ?WhhEVQ2gSbzTymH+3WxwPre^>W88_ zHWmZ*{!gHzk(`$MeRsD`JbMrt0a;iae#%$unDu0DN6rrBevMj2f8-sJV5eIZs>; zb-xVB zMX0Gcf?5mTpzd=ORsSQZonMf8xAE)X+#ndEI8YtqFbV762-KX;$LhEQb;Hfr6brB! zUPS(#GC}G58HXvT`%J*bxCZ$&m?B&6dPXDcW))IUPt#Bhbt^rconw>J%}b^aV`0RtRn?P<8q5UAa9UjqA}3+ih~(ry@^e1`D@%xw8U50I*qxu zzmZQ#NAh1yTwecAHj}a~h{7SV$X4{hskSWsK`PK8{eQT4%FmGy z%E_2c{!A(nZJ9*vn>Y1iw><1e!pIh)?`YW;NQi_`e91`igp4JLMB8~X zleEze+c2_%Jg*(LB3(Jj(=x1z`!c)#B!wiLM3Qfawh<(WEF&>Q+q(|`_gl`zYfCCp zd)OY)x9AV#LsEwfCmTsGqV2weyOb<`bDfIP-*)`&AqUBeq&)eRyhpSRBz`nlwtY|G zWxMkewjwW(zuP(&=la<4Ym)tj(DwK9;7?_9dy=?ErjTjmAqnKT2Ro4)=}m zZTrb%a*`||XNa~m@+TG8CX%zICBCS7{td+6R*3$jCFw@8$(JOU#V3*JqW(z#sn-B5a zZ$8C4C+>lt=Tbtnw_Gb9m$z$Tj>}V$lw2u+0sZXLs9$-d@Qo{k*@Vp78Zt VcxI%hPsa?;sg8ZU$r%@Yegl~}Z1VsB diff --git a/loko/controls/locale/en/LC_MESSAGES/django.po b/loko/controls/locale/en/LC_MESSAGES/django.po index 670dba7..12099a2 100644 --- a/loko/controls/locale/en/LC_MESSAGES/django.po +++ b/loko/controls/locale/en/LC_MESSAGES/django.po @@ -1414,3 +1414,64 @@ msgstr "Error generating the PDF" #~ msgid "Mise à jour" #~ msgstr "Last update" + +#: controls/templates/controls/controls_index.html +msgid "Sélectionnez les critères à afficher sur la carte et dans le tableau" +msgstr "Select criteria to display on the map and in the table" + +#: controls/templates/controls/controls_index.html +msgid "Colonnes" +msgstr "Columns" + +#: controls/templates/controls/controls_index.html +msgid "Choisir les colonnes à afficher" +msgstr "Choose columns to display" + +#: controls/templates/controls/controls_index.html +msgid "Toutes" +msgstr "All" + +#: controls/templates/controls/controls_index.html +msgid "Défaut" +msgstr "Default" + +#: controls/templates/controls/controls_index.html +msgid "Afficher par" +msgstr "Show by" + +#: controls/templates/controls/controls_index.html +msgid "Filtres actifs" +msgstr "Active filters" + +#: controls/templates/controls/controls_index.html +msgid "Supprimer ce critère" +msgstr "Remove this criterion" + +#: controls/templates/controls/controls_index.html +msgid "Tout effacer" +msgstr "Clear all" + +#: controls/templates/controls/controls_index.html +msgid "Aucun contrôle trouvé" +msgstr "No controls found" + +#: controls/templates/controls/controls_index.html +msgid "Aucun contrôle ne correspond à vos critères de recherche actuels." +msgstr "No controls match your current search criteria." + +#: controls/templates/controls/controls_index.html +msgid "Réinitialiser les filtres" +msgstr "Reset filters" + +#: controls/templates/controls/controls_index.html +msgid "Affichage de %(start)s à %(end)s sur %(total)s contrôle(s)" +msgstr "Showing %(start)s to %(end)s of %(total)s control(s)" + +#: controls/templates/controls/controls_index.html +msgid "Pagination des contrôles" +msgstr "Controls pagination" + +#: controls/templates/controls/controls_index.html +msgid "Excel" +msgstr "Excel" + diff --git a/loko/controls/locale/fr/LC_MESSAGES/django.mo b/loko/controls/locale/fr/LC_MESSAGES/django.mo index 74cd42c8c00de83d742246e828b3bc3ce942c722..f5a76b44fa9242bbda0cd21880c9646e3554737b 100644 GIT binary patch literal 19741 zcmeI24R{?@na2nENh#&Ce6^GTTavW7X$uq~ZK0t_OKH-E^h3S`r#F*i%FRrenHx$) zLHq(1PyuyS7I1f4i73St6xIc;Srt|k1w|GFu_!1Yh%Umep!@sJIdkt#DXaTD`|Pv( zn0fNv-<&h&^F8l*&p9)BV!yE;b-3K!9A`S5zK7$yU-$bd)^X+?>^S^6^ZDVCb1EDI z*TQk|bm+o!U<5CQZ-Fnu$#DNe9A^S_;eoIo&VVPu1K>GO^(Hp{!*E~1+wAl0Q28IW z;U{ePw>JEHIFaZ39BOz7R6CD^yTe9!1e^=i-qYYg@NKXaMmBsqq{+_xHvDsV0^!#o zf6iN{InLg&3(kfc;COfybm4XIFnEWJe+a7Gk3zL?2UPvfLG}A3I0=rY^LxU5q4FOF z-wGGPyRrH=*?OZFnSn9I74_qJHcF_kl-3wc{8#6E1|(&pV*c zMK=C}Q0@B&R6Sdv(%k|zes@E)_Xltmd=jeu@eJw|*Z_%gj)&6!dUzE)1F9X*z!mUC zsB%v@+{CYhs&@^P-Sk0~Q-v<%6S^1QfHiN^mPPO``X}9@I1?F zU?btXpq@Vs8{lM&MDfSLlVK4`Pg|kJ?KY_PehErn_d<>TLr`|{sD1vdjeiZQzhh92 z^tLC|lsXb>To%~)Wl(y1JH(Wo5>&nqK(+5`D1F=x)$i?aD*O&qJD!5d_X3puCSoj@ zf^!&D`IhU4H*p~mSMsCuRlkY1-jjl&$Mb}oSX z!xkG}2c3?Haf{++uk%RR3;)YVVzJHhd7OeXra2-BD^Y;ptHAeH)aX z-T_sA50oATEw6wo?*=#l-U&4yz6O>4$53|oyp11&(Q6zJhiXp&>UlFPz|)}0zZkBB z*FsF%`3W2kccam=mx)ke7pmSmD1FU^vcrW?^_*_mW0}H9#9s>4?yH~+KLce~_d?bG zxaCVw<2Y`P8Mg!BM8b`55?lb~rfc3BcJMx^`mTk0z+0j8cPErS9)N1^!%+F3fy)272%@_e1I73MhNN9_|IVLACF0sQMp-%J-P%Q&4(+4$6-AnQO*(B~-gg zQ2p8jRo+by)i}38{+!47IU4TCU>*xkgjyF9cnrJ+E`dL>&)xY(pX;F7bski?mq3ln z)lhb_#qu_&dHEHn`F;%8nm_s^|Am{TO?^nSX~twWA5j9*=_>$FrdF`%vw;z{Xz+)xImC>iINOx~=d) zcqdeQAAqyqV^H<)hO(x>nNa;W21@^@!K>g}sCMjt*T83?%6%7;L-8Mis`ukicJn!? za_)la*N-fpgsT6SkfC>8hO(#G3yr^D0i`b=s(qJRZi7b={vp&nd(Fl-ut?VvUJVt$ z37!nMKp9s~jHYmMyK>3euxDUL*#$N`d zw;SOh@K&gN_d>P*drSbI)8V;L_IVN92;U1aW#6Y_&O;4-2|nFZ8m(L z+(SE1VXI-Cl}GuYyEcsM*7j)7fJ?N|?0ZxO1V2&!H0w$HDI8uw3Ge%|t{ zP26vL8yGcf|}>A!0~X(DW<(sq4FOEC%{FPtv0?LD*px>F4^# zgvY}3AxoRH6&?ehfJ@+h?dJI!C_P^W)vg<$%KakLxNL`$;3Jk#!QBbJ0LQ?Wp!D{# z(h5EF9Vj_8`8hxY2Uyczy8@(41K=bwNdLVk^$kL*R9bn$WI0Yvo= zAX|_xAiDM=&06?}EM)rl74BKCvhkbDb5x$KQA+Y0vOjSLAal6?qawL9wz{50UPmrL zMmu0{R|(WP;pN_k-xa-4sJn#*S(pGSU)T!}n`=sF5PUD@?1e*YEu z4f5SA#>^M34Z37wm~M7`lHa%4`$MVkAmnQ{ybkV5IKNKf=3(SaS$X}KW*`4Ej6i%~k+Z|4__a7ksG-MmH8F>ZKwF)UCw<*B&H^_C!=?ZY|Vc@(6e%aoC z5l*q?D(XLMyl^=2>*0HmeYoEM7a%twHz0>1Gm+mS7b9zseUUCi*GG`6knbqK)ob-KY9Qtk&m6JlzONWPgqd4#zoEA5Yy&&~tr)5>E(^5=>p6oXY z-4N*xl7T1;Hk5p~FNzbl6!eC^)6&xu6#Kkh-|hC@MM)Y*Veg5v>XX!q(}rZxTpQyK z-&FI|54&rhCFQv0VH%}gsrGR(3e$M__L5(p(8x-CrJz5c@hW-#ym@XVRbtclu{+?= zmyJ<5>yshPOx*L!UYIH~X>D{lbURBPz4YC#;h~=4q1X?Lbg%ljQbBEG7?lTouUxIY z_ThjnFy3W>ey+hv6}}t${a$>2nR>&pT+&Dwx+uM-enk!sIW2LqFBqhsC9fu@iKJ)Q zO(ogz5T%y+bNizpOk6tLAEj=06r>a#$G%3lJ60mh!~9n3bQ$BgSB2?mO+1EF&8@K* zj1spPquiS*QtFKTVxLc2eAg?cWxtd(y`d1SraZWd!fkw>L6#QKT`x(ZVlX_E#}CBO zMn83v;h~a`)ka~6S=6W4H+oB4^Ekoa!_*DJVyP@U%K8y|0|QY^R*zxq_2TYop$uA5 zE~Q?YC(XmmlB~7vK!n{E{WPU#C@x8??s8Pk%p@(_?(mQ%jAzWJiKlA&fF^HP z=O-z*j97J=Cmwb}C856wrMl7xwoX3Tg5U4c6W&TLHKQBLq6VUb67eu;RE+uuN`C4$ zm^4w41Q;sAQH)9|GLf-(*=$zVqHcP|kK0{4nH1BgzG6`W8reoBjbCoGW4hy#tzVac#;L~JbVd;G(4pKVXI1%d7tWC zuEA;ynnpY5R;Am!sOXh4B`LDiW90Os8BrvKr`|ZNejEmxDQZEQVs@#5lNce5y3YU>$H}g*5RRI#I)Va zzy|mvmFSXkQ1gn#qfcwa=4|!{y=Yx)N)IFNyODfMchIA`ifc+d#vVJP*TvzXZi=Bv zGA++~KAM>M(Gp$`6ZHl%O_b{Tm=P>Z)ZL`ksBkff7@bDik0_F8Q8Y&KzAgKQxDhXJ8eBZUJ+xoG;Y;8uSz6tC3V_D z*JJLeC(`eQVX=>eFG$R&^0de>CQ4>j$+moctMuwU$rvb2oO~V0hRW@hc53@iCOW%0 zn?GTcGI^*LXSJc6x|(0RdfXF)<{7>n?T{h!;bt7W>w)6?*_FYxJQ_7kpbwo>XPZjqkOUGZ~pAwXw{U+Hb?C4Ogd(&HiMKD#mtXvPVZ#$%l+uoHLF)S z%Z=G+m0=C&XVi1gQNMI;S9{@fu7;XaxJI%>EOaS*Q0J7&jF(c4<4T-Us@u)#t-Irr z>u_|HUapWmTjQ;4qfF#LfUjVJyWXH*q|jn<_+}hH_AmkX&?MW}%7}T0kBZG!MjnQz znWJcOTM~xY-(1K%ApJ6~kU}?Sy^RBQAa2Q&adWo5F?J=b1J*N8XIPAI+u7!lM|Qf^ zRn~tyJF_X6_rm02(I$6Kl>NlgEbGS#Eo8V&#-!G}y;;27hoISFjpFcPF=M^L&}Ap( zZ>otJ#Lbzyh_nH;rqp=|FNb;)ryY-5jdqg(JU+{SRL=%u6N8(1<|d~-b1$S5JPn?`5_O?b_hrRT4k0_^e$RUq#c*rF`+F6)ad$++Qwb3#C*N(AUiJ z{LDFFB&5l=mzCb^+!^$#O%$$kcxa=xz^r$&H+;AqVbcN3%C7d*Ww#{RrnI`}N@~tl zRL)+9ADy$>Gv#SgREN&3@Ae1Efp)pL2%JNM(_W*|5dp0d?VQbw+B&?7SvwtOC*jsd z*$yl8OAU&{zGHXQ>XlB%h&p!)*6a;o0tw7X79BFB3OB*HP`9TmXDuopHR6` zUzX;^IU=^w?-8-7&oIml zBw$W=)duA;)y8Bo+PQS)XSY>l?b{>On74=qt?qJGZ@^3kvzePXtILBiJ7WAl+hkS` zrb^8D^ORPHr8ZxFF=ZWWs;85kTv#V&=kODRS*e-nR+^UZB-TcRYP4&SJt6~rQOX&u zcAsrG9I~RA!%e~hMOJo2e(ZGic^quAU*$2Afv_p0u4T~UoUFZJC9ZY@Z_=?@3z!>G z(&*YV9Bq?RR{4hZAeA*vUi^f4U%##LZ09g;brAPkiPkwg+e~We*Z6q%2uEYifw8cm z%;HF>s%odxI(8{L>Sd_4HF5?uUYq?~t=G;LU*jp*sd_P)o19LT3J#aq$l8&s$9(1Xa93fO z7Q}(h&Yf}8UDk%5hUUjwZQ6+EaBP9vtGjTgQ%zD?_1!z4Od6ci@^>U|{rnN3qep~} zsR9&V>#=5%_p zttmCqXMI_K8QYhw%9rn2FU>8$S<@xYwt zsxQlATK0uV<_qXUw)58-V2*^oE1mHY##!t2CeB*B_;C~W2ehbiqb#w{{wyGl4X53S z*WQy|Z(8{+>(;(3j#a0&5^^qyaoZ;3-yw@(|kjay;a$XHGvd;hGcFE-QPp ze3kd$yJ+B><*~2FnRu)P)}Q=$D*dn2V(P5<*0G*FhnbMoH)4(=2P&_S6tYtk?~osU z9n8(XFLHRrl%Mn2ev;D}zMMBvj7+V|s>D%TKFPC{yqFeNbQjk9v0iO9yPYjdSGFu~ zbJw?Zty!^p)g(MS@4pHiNpH|yIJw-L6xK$~?xfDug)U#mk|-?bEo8HMMq5i)p<{KS zwZ%PUb#upxIcKh3)i$Z!OVR?9I!xG8M{%>eY+ZZ1yQ-z5&0W;i(Xyg_NlR;MSKFF3 zCz3SmEi>Z^YkjZ3*==uGwS1jgvZ%d%NhxTG;@)Z+TF|kgqpjNP`AzdCEyW6PG6lJf zW_r0Poy$%>2p48ASQ0;7yl(BX!tvE~YF3XQ7uq;OcL!l_vwQr8Af41%D#u=_z`Kcl zUbln;Suk0A^g=e)da}4abdOl<&Tm*a$r=6jRsAUO7xnFnZz}v%Rts)MKYfjU`WpT8 zHTvmm=Pzo10iV7`zoGpf{f74c;uG5F7qrnYXm9=n?SJ3{T2{p9_cJ~ySH3v?S>Mk_ zKcDeBqIz5({d_j!Ls{je&xqLk<$_)j#pVtC=;t%@|6Ze?&qhC=jeb7M{tImM^V#U< pvp?hWSvUz8|7No>pNK1PVfyl5)RwZ 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 DY3ti8 diff --git a/loko/controls/locale/fr/LC_MESSAGES/django.po b/loko/controls/locale/fr/LC_MESSAGES/django.po index 31fc86e..9dbed2b 100644 --- a/loko/controls/locale/fr/LC_MESSAGES/django.po +++ b/loko/controls/locale/fr/LC_MESSAGES/django.po @@ -1419,3 +1419,64 @@ msgstr "Erreur lors de la génération du PDF" #~ msgid "Mise à jour" #~ msgstr "Mise à jour" + +#: controls/templates/controls/controls_index.html +msgid "Sélectionnez les critères à afficher sur la carte et dans le tableau" +msgstr "Sélectionnez les critères à afficher sur la carte et dans le tableau" + +#: controls/templates/controls/controls_index.html +msgid "Colonnes" +msgstr "Colonnes" + +#: controls/templates/controls/controls_index.html +msgid "Choisir les colonnes à afficher" +msgstr "Choisir les colonnes à afficher" + +#: controls/templates/controls/controls_index.html +msgid "Toutes" +msgstr "Toutes" + +#: controls/templates/controls/controls_index.html +msgid "Défaut" +msgstr "Défaut" + +#: controls/templates/controls/controls_index.html +msgid "Afficher par" +msgstr "Afficher par" + +#: controls/templates/controls/controls_index.html +msgid "Filtres actifs" +msgstr "Filtres actifs" + +#: controls/templates/controls/controls_index.html +msgid "Supprimer ce critère" +msgstr "Supprimer ce critère" + +#: controls/templates/controls/controls_index.html +msgid "Tout effacer" +msgstr "Tout effacer" + +#: controls/templates/controls/controls_index.html +msgid "Aucun contrôle trouvé" +msgstr "Aucun contrôle trouvé" + +#: controls/templates/controls/controls_index.html +msgid "Aucun contrôle ne correspond à vos critères de recherche actuels." +msgstr "Aucun contrôle ne correspond à vos critères de recherche actuels." + +#: controls/templates/controls/controls_index.html +msgid "Réinitialiser les filtres" +msgstr "Réinitialiser les filtres" + +#: controls/templates/controls/controls_index.html +msgid "Affichage de %(start)s à %(end)s sur %(total)s contrôle(s)" +msgstr "Affichage de %(start)s à %(end)s sur %(total)s contrôle(s)" + +#: controls/templates/controls/controls_index.html +msgid "Pagination des contrôles" +msgstr "Pagination des contrôles" + +#: controls/templates/controls/controls_index.html +msgid "Excel" +msgstr "Excel" + diff --git a/loko/controls/locale/nl/LC_MESSAGES/django.mo b/loko/controls/locale/nl/LC_MESSAGES/django.mo index bee81478c9ab24e67674053449d13d14ace52f7c..c2e8dbf1cbfe4cf33c5cfe27b12dc89af50379b5 100644 GIT binary patch delta 7154 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 diff --git a/loko/controls/locale/nl/LC_MESSAGES/django.po b/loko/controls/locale/nl/LC_MESSAGES/django.po index 292a97b..d3d0e6d 100644 --- a/loko/controls/locale/nl/LC_MESSAGES/django.po +++ b/loko/controls/locale/nl/LC_MESSAGES/django.po @@ -1417,3 +1417,64 @@ msgstr "Fout bij het genereren van de PDF" #~ msgid "Mise à jour" #~ msgstr "Laatste update" + +#: controls/templates/controls/controls_index.html +msgid "Sélectionnez les critères à afficher sur la carte et dans le tableau" +msgstr "Selecteer de criteria om weer te geven op de kaart en in de tabel" + +#: controls/templates/controls/controls_index.html +msgid "Colonnes" +msgstr "Kolommen" + +#: controls/templates/controls/controls_index.html +msgid "Choisir les colonnes à afficher" +msgstr "Kies de weer te geven kolommen" + +#: controls/templates/controls/controls_index.html +msgid "Toutes" +msgstr "Alle" + +#: controls/templates/controls/controls_index.html +msgid "Défaut" +msgstr "Standaard" + +#: controls/templates/controls/controls_index.html +msgid "Afficher par" +msgstr "Weergeven per" + +#: controls/templates/controls/controls_index.html +msgid "Filtres actifs" +msgstr "Actieve filters" + +#: controls/templates/controls/controls_index.html +msgid "Supprimer ce critère" +msgstr "Dit criterium verwijderen" + +#: controls/templates/controls/controls_index.html +msgid "Tout effacer" +msgstr "Alles wissen" + +#: controls/templates/controls/controls_index.html +msgid "Aucun contrôle trouvé" +msgstr "Geen controles gevonden" + +#: controls/templates/controls/controls_index.html +msgid "Aucun contrôle ne correspond à vos critères de recherche actuels." +msgstr "Geen controles komen overeen met uw huidige zoekcriteria." + +#: controls/templates/controls/controls_index.html +msgid "Réinitialiser les filtres" +msgstr "Filters resetten" + +#: controls/templates/controls/controls_index.html +msgid "Affichage de %(start)s à %(end)s sur %(total)s contrôle(s)" +msgstr "Weergave van %(start)s tot %(end)s van %(total)s controle(s)" + +#: controls/templates/controls/controls_index.html +msgid "Pagination des contrôles" +msgstr "Paginering van controles" + +#: controls/templates/controls/controls_index.html +msgid "Excel" +msgstr "Excel" + diff --git a/loko/controls/static/controls/controls_index.css b/loko/controls/static/controls/controls_index.css index 5ed25ff..e17b5c0 100644 --- a/loko/controls/static/controls/controls_index.css +++ b/loko/controls/static/controls/controls_index.css @@ -1,8 +1,15 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;600&display=swap'); + /* ========================================================================== Controls Index CSS ========================================================================== */ -/* Supprime le contour bleu Bootstrap au focus sur l'input de recherche */ +/* Typography & Global Layout */ +.controls-layout { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} + +/* Supprime le contour bleu Bootstrap au focus sur les inputs */ #controls-search-input:focus, #project-autocomplete-input:focus { outline: none; @@ -28,86 +35,220 @@ } } -/* Styles pour la modale de filtre */ +/* ─── Airbnb-style Filter Modal ────────────────────────────────────── */ #filterModal .modal-dialog { - max-width: 95%; + max-width: 820px; + margin: 1.5rem auto; } + #filterModal .modal-content { + border: 1px solid #e2e8f0; + border-radius: 18px; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); display: flex; flex-direction: column; - max-height: 90vh; + max-height: 88vh; + overflow: hidden; + background-color: #ffffff; } + +#filterModal .modal-header { + padding: 1.25rem 1.5rem; + border-bottom: 1px solid #f1f5f9; + background-color: #ffffff; +} + +#filterModal .modal-title { + font-size: 1.25rem; + font-weight: 700; + color: #0f172a; + letter-spacing: -0.02em; +} + +#filterModal .btn-close { + font-size: 0.85rem; + padding: 0.5rem; + opacity: 0.5; + transition: opacity 0.15s ease; +} +#filterModal .btn-close:hover { + opacity: 1; +} + #filterModal .modal-body { - flex: 1; + padding: 1.5rem; overflow-y: auto; + background-color: #ffffff; } + #filterModal .modal-footer { - flex-shrink: 0; + padding: 1rem 1.5rem; + border-top: 1px solid #f1f5f9; + background-color: #ffffff; + display: flex; + justify-content: space-between; + align-items: center; } +/* Filter Sections */ .filter-section { - margin-bottom: 1rem; + margin-bottom: 1.5rem; +} + +.filter-section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} + +.filter-section-header label { + font-size: 0.9rem; + font-weight: 700; + color: #0f172a; + letter-spacing: -0.01em; + margin: 0; +} + +.filter-section-buttons { + display: flex; + gap: 0.4rem; +} + +.filter-section-buttons .btn { + font-size: 0.75rem; + font-weight: 500; + padding: 0.2rem 0.6rem; + border-radius: 6px; + border: 1px solid #e2e8f0; + background-color: #ffffff; + color: #64748b; + transition: all 0.15s ease; +} + +.filter-section-buttons .btn:hover { + background-color: #f1f5f9; + color: #0f172a; + border-color: #cbd5e1; +} + +/* Filter Options (Pills) */ +.filter-options { + display: flex; + flex-wrap: wrap; + gap: 0.45rem; + align-items: center; +} + +.filter-option { + display: inline-flex; + margin: 0; + position: relative; +} + +.filter-option input[type="checkbox"] { + position: absolute; + opacity: 0; + width: 0; + height: 0; + pointer-events: none; } -/* Default badge styling */ .filter-option label { display: inline-flex; align-items: center; - justify-content: center; - margin: 0; + gap: 0.35rem; + padding: 0.35rem 0.85rem; + font-size: 0.78rem; font-weight: 500; + color: #334155; + background-color: #ffffff; + border: 1.5px solid #e2e8f0; + border-radius: 9999px; cursor: pointer; - background-color: #e7f1ff; - color: #0c5cc9; - border-radius: 1rem; - padding: 0.4rem 0.5rem; - font-size: 0.75rem; - border: 1px solid #b8d4f1; - transition: all 0.2s ease; - width: 140px; - flex: 1 1 auto; - text-align: center; + transition: all 0.15s ease; + user-select: none; white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + margin: 0; } -/* Contextual color badges - inactive state */ -.filter-option label.context-badge { - border-color: currentColor; - color: inherit; - background-color: transparent; +.filter-option label:hover { + background-color: #f8fafc; + border-color: #94a3b8; + color: #0f172a; } -.filter-option label.btn-outline-primary { border-color: #0c5cc9; color: #0c5cc9; } -.filter-option label.btn-outline-danger { border-color: #dc3545; color: #dc3545; } -.filter-option label.btn-outline-warning { border-color: #ffc107; color: #c69500; } -.filter-option label.btn-outline-success { border-color: #28a745; color: #28a745; } -.filter-option label.btn-outline-info { border-color: #17a2b8; color: #17a2b8; } -.filter-option label.btn-outline-dark { border-color: #343a40; color: #343a40; } -.filter-option label.btn-outline-secondary { border-color: #6c757d; color: #6c757d; } - -/* Default badges - checked state */ +/* Standard Checked State */ .filter-option input[type="checkbox"]:checked + label:not(.context-badge) { - background-color: #0c5cc9; color: white; border-color: #0c5cc9; + background-color: #0f172a !important; + border-color: #0f172a !important; + color: #ffffff !important; + font-weight: 600; + box-shadow: 0 2px 6px rgba(15, 23, 42, 0.16); } +/* Contextual badges - inactive state (outline) */ +.filter-option label.context-badge { + background-color: #ffffff; + border-width: 1.5px; + border-style: solid; + font-weight: 600; +} + +.filter-option label.btn-outline-primary, +.filter-option label.status-primary { border-color: #0c5cc9; color: #0c5cc9; } +.filter-option label.btn-outline-danger, +.filter-option label.status-danger { border-color: #dc3545; color: #dc3545; } +.filter-option label.btn-outline-warning, +.filter-option label.status-warning { border-color: #f59e0b; color: #d97706; } +.filter-option label.btn-outline-success, +.filter-option label.status-success { border-color: #198754; color: #198754; } +.filter-option label.btn-outline-info, +.filter-option label.status-info { border-color: #0284c7; color: #0284c7; } +.filter-option label.btn-outline-dark, +.filter-option label.status-dark { border-color: #334155; color: #334155; } +.filter-option label.btn-outline-secondary, +.filter-option label.status-secondary { border-color: #64748b; color: #64748b; } + +/* Contextual badges - hover state */ +.filter-option input[type="checkbox"]:not(:checked):hover + label.btn-outline-primary { background-color: #eff6ff; } +.filter-option input[type="checkbox"]:not(:checked):hover + label.btn-outline-danger { background-color: #fef2f2; } +.filter-option input[type="checkbox"]:not(:checked):hover + label.btn-outline-warning { background-color: #fffbeb; } +.filter-option input[type="checkbox"]:not(:checked):hover + label.btn-outline-success { background-color: #f0fdf4; } +.filter-option input[type="checkbox"]:not(:checked):hover + label.btn-outline-info { background-color: #f0f9ff; } +.filter-option input[type="checkbox"]:not(:checked):hover + label.btn-outline-dark { background-color: #f8fafc; } +.filter-option input[type="checkbox"]:not(:checked):hover + label.btn-outline-secondary { background-color: #f8fafc; } + /* Contextual badges - checked state */ -.filter-option input[type="checkbox"]:checked + label.context-badge { color: white; } -.filter-option input[type="checkbox"]:checked + label.btn-outline-primary { background-color: #0c5cc9; border-color: #0c5cc9; } -.filter-option input[type="checkbox"]:checked + label.btn-outline-danger { background-color: #dc3545; border-color: #dc3545; } -.filter-option input[type="checkbox"]:checked + label.btn-outline-warning { background-color: #ffc107; border-color: #ffc107; color: #212529 !important; } -.filter-option input[type="checkbox"]:checked + label.btn-outline-success { background-color: #28a745; border-color: #28a745; } -.filter-option input[type="checkbox"]:checked + label.btn-outline-info { background-color: #17a2b8; border-color: #17a2b8; } -.filter-option input[type="checkbox"]:checked + label.btn-outline-dark { background-color: #343a40; border-color: #343a40; } -.filter-option input[type="checkbox"]:checked + label.btn-outline-secondary { background-color: #6c757d; border-color: #6c757d; } - -/* Default badges - hover */ -.filter-option input[type="checkbox"]:hover + label:not(.context-badge) { background-color: #cfe2f3; } +.filter-option input[type="checkbox"]:checked + label.context-badge { + color: #ffffff !important; +} +.filter-option input[type="checkbox"]:checked + label.btn-outline-primary { + background-color: #0c5cc9 !important; border-color: #0c5cc9 !important; box-shadow: 0 2px 6px rgba(12, 92, 201, 0.2); +} +.filter-option input[type="checkbox"]:checked + label.btn-outline-danger { + background-color: #dc3545 !important; border-color: #dc3545 !important; box-shadow: 0 2px 6px rgba(220, 53, 69, 0.2); +} +.filter-option input[type="checkbox"]:checked + label.btn-outline-warning { + background-color: #f59e0b !important; border-color: #f59e0b !important; box-shadow: 0 2px 6px rgba(245, 158, 11, 0.2); +} +.filter-option input[type="checkbox"]:checked + label.btn-outline-success { + background-color: #198754 !important; border-color: #198754 !important; box-shadow: 0 2px 6px rgba(25, 135, 84, 0.2); +} +.filter-option input[type="checkbox"]:checked + label.btn-outline-info { + background-color: #0284c7 !important; border-color: #0284c7 !important; box-shadow: 0 2px 6px rgba(2, 132, 199, 0.2); +} +.filter-option input[type="checkbox"]:checked + label.btn-outline-dark { + background-color: #1e293b !important; border-color: #1e293b !important; box-shadow: 0 2px 6px rgba(30, 41, 59, 0.2); +} +.filter-option input[type="checkbox"]:checked + label.btn-outline-secondary { + background-color: #64748b !important; border-color: #64748b !important; box-shadow: 0 2px 6px rgba(100, 116, 139, 0.2); +} +/* Subcategories */ .filter-option.subcategory-option label { - font-size: 0.875em; - color: #555; + font-size: 0.76rem; + color: #64748b; } .filter-option-break { flex-basis: 100%; @@ -118,74 +259,34 @@ align-items: center; gap: 0.25rem; } - -.filter-section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.75rem; - gap: 1rem; -} -.filter-section-header label { - font-weight: 600; - color: #333; - margin: 0; - flex-shrink: 0; -} -.filter-section-buttons { - display: flex; - gap: 0.5rem; - margin-left: auto; - flex-shrink: 0; -} -.filter-section-buttons button { - padding: 0.25rem 0.5rem; - font-size: 0.75rem; - white-space: nowrap; -} - -.filter-options { - display: flex; - flex-wrap: wrap; - gap: 0.5rem; - min-height: 2.5rem; - padding: 0.5rem; - background-color: #f8f9fa; - border-radius: 0.375rem; - border: 1px solid #dee2e6; -} - -.filter-option { - display: flex; - align-items: center; -} - -.filter-option input[type="checkbox"] { - position: absolute; - opacity: 0; - width: 0; - height: 0; - cursor: pointer; +.filter-subcat-controls button { + font-size: 0.72rem; + padding: 0.15rem 0.5rem; + border-radius: 5px; + border: 1px solid #e2e8f0; + background: #fff; + color: #64748b; } /* Project Autocomplete & Chips */ .project-chip { font-size: 0.8rem; font-weight: 500; - border-radius: 1rem !important; + border-radius: 9999px !important; + background-color: #ffffff !important; + border: 1px solid #cbd5e1 !important; + color: #1e293b !important; transition: background-color 0.15s ease; - border-color: #ced4da !important; } .project-chip:hover { - background-color: #f1f3f5 !important; + background-color: #f1f5f9 !important; } .project-chip .remove-project-btn { opacity: 0.6; transition: opacity 0.15s ease; } - .project-chip .remove-project-btn:hover { opacity: 1; } @@ -200,43 +301,417 @@ #project-autocomplete-dropdown .list-group-item:hover, #project-autocomplete-dropdown .list-group-item.active-item { - background-color: #e9ecef; + background-color: #f1f5f9; } #project-autocomplete-dropdown .list-group-item.selected-project { - background-color: #e7f1ff; + background-color: #eff6ff; color: #0c5cc9; } -/* Limites de largeur pour les colonnes Code et Thématiques */ -#controls-table th[data-field="control_code"], -#controls-table td.col-code { +/* ─── Header Tabs & New Button ─────────────────────────────────────── */ +.controls-tabs { + border-bottom: 2px solid #e2e8f0; + margin-bottom: 0; +} + +.controls-tabs .nav-link { + font-size: 0.92rem; + font-weight: 600; + color: #64748b; + border: none; + border-bottom: 2px solid transparent; + margin-bottom: -2px; + padding: 0.5rem 1rem; + background: transparent; + transition: all 0.15s ease; + display: inline-flex; + align-items: center; + gap: 0.45rem; +} + +.controls-tabs .nav-link:hover { + color: #0f172a; + border-color: #cbd5e1; +} + +.controls-tabs .nav-link.active { + color: #0f172a; + border-bottom-color: #0f172a; + background: transparent; +} + +.tab-counter-badge { + font-size: 0.74rem; + font-weight: 600; + padding: 2px 7px; + border-radius: 9999px; + background-color: #f1f5f9; + color: #475569; + border: 1px solid #e2e8f0; +} + +.controls-tabs .nav-link.active .tab-counter-badge { + background-color: #0f172a; + color: #ffffff; + border-color: #0f172a; +} + +.btn-new-control { + background-color: #0f172a; + color: #ffffff; + border: 1px solid #0f172a; + border-radius: 8px; + padding: 0.42rem 0.85rem; + font-size: 0.84rem; + font-weight: 600; + display: inline-flex; + align-items: center; + gap: 0.45rem; + transition: all 0.15s ease; + text-decoration: none; + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08); +} + +.btn-new-control:hover { + background-color: #1e293b; + color: #ffffff; + border-color: #1e293b; + transform: translateY(-1px); + box-shadow: 0 4px 6px -1px rgba(15, 23, 42, 0.12); +} + +/* ─── Active Filters Bar ───────────────────────────────────────────── */ +.active-filters-bar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 0.45rem; + padding: 0 0.15rem; +} + +.active-filter-label { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: #94a3b8; + margin-right: 0.2rem; +} + +.active-filter-chip { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.2rem 0.6rem; + font-size: 0.76rem; + font-weight: 500; + border-radius: 9999px; + background-color: #f1f5f9; + border: 1px solid #e2e8f0; + color: #334155; + transition: all 0.12s ease; +} + +.active-filter-chip .remove-filter-btn { + border: none; + background: none; + padding: 0; + margin-left: 2px; + color: #94a3b8; + cursor: pointer; + display: inline-flex; + align-items: center; + font-size: 0.75rem; + line-height: 1; +} + +.active-filter-chip .remove-filter-btn:hover { + color: #dc2626; +} + +.btn-clear-all-filters { + font-size: 0.76rem; + color: #dc2626; + text-decoration: none; + font-weight: 600; + padding: 0.2rem 0.5rem; + border-radius: 6px; + transition: background-color 0.12s ease; +} + +.btn-clear-all-filters:hover { + background-color: #fef2f2; + color: #b91c1c; +} + +/* ─── Table Toolbar & Column Chooser ───────────────────────────────── */ +.controls-toolbar { + padding: 0.25rem 0; +} + +.filter-trigger-btn { + border: 1px solid #cbd5e1; + border-radius: 8px; + font-size: 0.82rem; + font-weight: 500; + padding: 0.32rem 0.75rem; + background-color: #ffffff; + color: #334155; + display: inline-flex; + align-items: center; + gap: 0.4rem; + transition: all 0.15s ease; +} +.filter-trigger-btn:hover { + background-color: #f8fafc; + border-color: #94a3b8; + color: #0f172a; +} + +#filter-badge { + background-color: #0f172a !important; + color: #ffffff !important; + font-size: 0.7rem; + font-weight: 700; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); + top: -5px !important; + right: -7px !important; + left: auto !important; + transform: none !important; + min-width: 1.25rem; + height: 1.25rem; + padding: 0 4px !important; + line-height: 1.25rem; +} +#filter-badge:not(.d-none) { + display: inline-flex !important; + align-items: center; + justify-content: center; +} + +#columns-dropdown-btn { + border: 1px solid #cbd5e1; + border-radius: 8px; + font-size: 0.82rem; + font-weight: 500; + padding: 0.32rem 0.75rem; + background-color: #ffffff; + color: #334155; +} +#columns-dropdown-btn:hover { + background-color: #f8fafc; + border-color: #94a3b8; + color: #0f172a; +} + +.columns-dropdown-menu { + border-radius: 12px; + border: 1px solid #e2e8f0; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.12), 0 8px 10px -6px rgba(0, 0, 0, 0.08); + min-width: 250px; + z-index: 1060; +} + +.columns-checkbox-list { + max-height: 280px; + overflow-y: auto; +} + +.columns-checkbox-list .form-check { + margin-bottom: 0.2rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.columns-checkbox-list .form-check-input { + cursor: pointer; + margin-top: 0; +} + +.columns-checkbox-list .form-check-label { + cursor: pointer; + font-size: 0.82rem; +} + +.page-size-selector { + font-size: 0.82rem; + color: #64748b; +} + +.page-size-select { + padding: 0.25rem 0.55rem; + font-size: 0.82rem; + border: 1px solid #cbd5e1; + border-radius: 7px; + background-color: #ffffff; + color: #334155; + cursor: pointer; + font-weight: 500; +} +.page-size-select:focus { + outline: none; + border-color: #2563eb; +} + +/* ─── Custom Controls Table Card ───────────────────────────────────── */ +.controls-table-card { + background-color: #ffffff; + border: 1px solid #e2e8f0; + border-radius: 12px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03); + overflow: hidden; +} + +#controls-table-scrollable { + overflow-y: auto; + overflow-x: auto; +} + +/* Scrollbar discrète */ +#controls-table-scrollable::-webkit-scrollbar { + width: 6px; + height: 6px; +} +#controls-table-scrollable::-webkit-scrollbar-track { + background: #f8fafc; +} +#controls-table-scrollable::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} +#controls-table-scrollable::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +.controls-table { + width: 100%; + margin-bottom: 0; + border-collapse: separate; + border-spacing: 0; +} + +/* Table Headers (Sticky) */ +.controls-table thead th { + position: sticky; + top: 0; + z-index: 5; + background-color: #f8fafc !important; + color: #475569 !important; + font-size: 0.74rem !important; + font-weight: 600 !important; + text-transform: uppercase !important; + letter-spacing: 0.04em !important; + border-bottom: 1.5px solid #e2e8f0 !important; + padding: 0.55rem 0.75rem !important; + vertical-align: middle !important; + white-space: nowrap !important; + user-select: none; +} + +.controls-table thead th.sortable { + cursor: pointer; + transition: background-color 0.12s ease; +} + +.controls-table thead th.sortable:hover { + background-color: #f1f5f9 !important; + color: #0f172a !important; +} + +.sort-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.4rem; +} + +.sort-icon { + font-size: 0.75rem; +} + +/* Table Rows & Cells */ +.controls-table tbody td { + padding: 0.45rem 0.75rem !important; + vertical-align: middle !important; + font-size: 0.82rem !important; + color: #334155; + border-bottom: 1px solid #f1f5f9 !important; + line-height: 1.35 !important; +} + +.controls-table tbody tr { + transition: background-color 0.12s ease; +} + +.controls-table tbody tr:hover { + background-color: #f8fafc !important; +} + +.controls-table tbody tr.selected, +.controls-table tbody tr.rowhighlight { + background-color: #eff6ff !important; +} + +/* Code Link Badge in Table */ +.table-code-link { + font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important; + font-size: 0.79rem !important; + font-weight: 600 !important; + color: #0f172a !important; + text-decoration: none !important; + white-space: nowrap !important; + display: inline-block; + padding: 2px 7px; + border-radius: 5px; + background-color: #f8fafc; + border: 1px solid #e2e8f0; + transition: all 0.15s ease; +} +.table-code-link:hover { + color: #2563eb !important; + border-color: #93c5fd; + background-color: #eff6ff; +} + +/* Action button / Zoom button */ +.zoom-btn, +.table-action-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + border-radius: 6px; + color: #64748b; + background-color: transparent; + border: 1px solid transparent; + transition: all 0.15s ease; + text-decoration: none; +} +.zoom-btn:hover, +.table-action-btn:hover { + color: #0f172a; + background-color: #f1f5f9; + border-color: #cbd5e1; +} + +/* Limites de largeur pour colonnes */ +.controls-table th.col-control_code, +.controls-table td.col-control_code { max-width: 140px; width: 140px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } -#controls-table td.col-code a { - display: inline-block; - max-width: 100%; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - vertical-align: middle; -} - -#controls-table th[data-field="thematic_names"], -#controls-table td.col-thematics { +.controls-table th.col-thematic_names, +.controls-table td.col-thematic_names { max-width: 180px; width: 180px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } -#controls-table td.col-thematics .thematic-content { +.controls-table td.col-thematics .thematic-content { display: inline-block; max-width: 100%; overflow: hidden; @@ -245,29 +720,60 @@ vertical-align: middle; } -/* Layout helpers */ -.controls-layout #controls-table-container { - position: relative; - min-height: 420px; +/* Empty State */ +.table-empty-state { + padding: 2.5rem 1rem; + text-align: center; } -#filter-dropdown-wrapper { - position: absolute; - top: 0.5rem; - left: 0.5rem; - z-index: 10; +/* ─── Custom Pagination Bar ────────────────────────────────────────── */ +.controls-pagination-bar { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.65rem 1rem; + border-top: 1px solid #e2e8f0; + background-color: #ffffff; + flex-wrap: wrap; + gap: 0.5rem; } -.filter-dropdown { - min-width: min(640px, calc(100vw - 4rem)); +.controls-pagination-bar .pagination-info { + font-size: 0.82rem; + color: #64748b; } +.controls-pagination-bar .pagination .page-item .page-link { + border-radius: 6px; + margin: 0 2px; + border-color: #e2e8f0; + color: #334155; + font-size: 0.8rem; + padding: 0.25rem 0.55rem; + text-decoration: none; +} + +.controls-pagination-bar .pagination .page-item.active .page-link { + background-color: #0f172a; + border-color: #0f172a; + color: #ffffff; +} + +.controls-pagination-bar .pagination .page-item.disabled .page-link { + color: #cbd5e1; + background-color: transparent; + border-color: #f1f5f9; +} + +/* ─── Map Container ────────────────────────────────────────────────── */ .controls-map { - height: calc(100vh - 210px); + height: calc(100vh - 190px); min-height: 360px; position: relative; width: 100%; - margin-top: 50px; + border-radius: 12px; + border: 1px solid #e2e8f0; + margin-top: 44px; } .controls-map-filter { @@ -276,6 +782,9 @@ right: 2.75rem; z-index: 5; box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.15); + border-radius: 8px; + font-weight: 500; + font-size: 0.8rem; } .popup-scrollable { @@ -289,16 +798,8 @@ } @media (max-width: 991.98px) { - .controls-layout #filter-dropdown-wrapper { - position: static; - margin-bottom: 1rem; - } - - .controls-layout #controls-table-container { - padding-top: 0; - } - - .filter-dropdown { - width: 100%; + .controls-layout .controls-toolbar { + flex-direction: column; + align-items: stretch; } } diff --git a/loko/controls/static/controls/controls_index.js b/loko/controls/static/controls/controls_index.js index befb256..0c60061 100644 --- a/loko/controls/static/controls/controls_index.js +++ b/loko/controls/static/controls/controls_index.js @@ -1,9 +1,32 @@ +/** + * Controls Index JavaScript + * Custom table implementation with client-side sorting, pagination, and column selection. + */ + let controls_geojson = {}; let allControls = []; +let filteredControls = []; let visibleRows = []; +let currentPage = 1; +let pageSize = 50; +let sortField = 'updated_at'; +let sortDir = 'desc'; +let hiddenColumns = new Set(); + const MAX_ITEMS = 5000; const DESCRIPTION_MAX_LENGTH = 60; +const CTRL_COLUMNS_KEY = 'controls_columns_hidden'; + +function escapeHtml(str) { + if (str === null || str === undefined) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} function truncateText(text, limit) { if (!text || text.length <= limit) { @@ -12,6 +35,20 @@ function truncateText(text, limit) { return `${text.substring(0, limit - 3).trim()}...`; } +function dateFormatter(value) { + if (!value) return '-'; + try { + const date = new Date(value); + if (isNaN(date.getTime())) return '-'; + const lang = (document.documentElement.lang || 'fr').split('-')[0]; + const localeMap = { 'fr': 'fr-BE', 'nl': 'nl-BE', 'en': 'en-GB' }; + const locale = localeMap[lang] || 'fr-BE'; + return date.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric' }); + } catch (e) { + return '-'; + } +} + const STATUS_METADATA = { in_progress: { label: gettext('En cours'), badge: 'warning', color: '#ffc107' }, information: { label: gettext('Information'), badge: 'info', color: '#0dcaf0' }, @@ -36,6 +73,21 @@ function getControlTypeMetadata(type) { return CONTROL_TYPE_METADATA[type] || { label: type || '-', badge: 'secondary', icon: '' }; } +const AVAILABLE_COLUMNS = [ + { key: 'control_code', label: gettext('Code') }, + { key: 'thematic_names', label: gettext('Thématiques') }, + { key: 'category_names', label: gettext('Catégories') }, + { key: 'control_type_label', label: gettext('Type') }, + { key: 'status_label', label: gettext('Statut') }, + { key: 'project_name', label: gettext('Projet') }, + { key: 'location', label: gettext('Localisation') }, + { key: 'author', label: gettext('Auteur') }, + { key: 'remarks_count', label: gettext('Remarques') }, + { key: 'created_at', label: gettext('Création') }, + { key: 'updated_at', label: gettext('Mise à jour') }, + { key: 'description', label: gettext('Description') }, +]; + const controlsgeojsonloader = new GeojsonLoader(); const controls_layer_defs = [ { @@ -83,29 +135,32 @@ document.addEventListener('DOMContentLoaded', () => { setupFilterForm(); setupFilterZoneButton(); setupLayoutButtons(); + setupQuickFilterRemovers(); + updateFilterBadge(); const query = new URLSearchParams(window.location.search).toString(); const initialUrl = query ? `${controlsgeojsonUrl}?${query}` : controlsgeojsonUrl; controlsgeojsonloader.loadGeojson(initialUrl); - // Sync export button with initial URL filters _updateExportExcelUrl(query); }); controlsgeojsonloader.addEventListener('geojsonLoaded', (event) => { const geojsonData = event.detail; controls_geojson = geojsonData; - allControls = geojsonData.features || []; - visibleRows = [...allControls]; + const features = geojsonData.features || []; - loadDataIntoControlsTable(allControls); - fitMapToControls(allControls); - updateFilterLimitAlert(allControls.length); + loadDataIntoControlsTable(features); + fitMapToControls(features); + updateFilterLimitAlert(features.length); }); controlsgeojsonloader.addEventListener('geojsonError', () => { document.getElementById('controls-spinner')?.classList.add('d-none'); document.getElementById('controls-table-container')?.classList.remove('d-none'); + allControls = []; + filteredControls = []; + renderTable(); updateResultCount(0); }); @@ -195,7 +250,7 @@ function registerMapInteractions(event) { .setHTML(popupContent) .addTo(mapInstance); - highlightRow('#controls-table', 'id', props.id); + highlightControlRow(props.id); }); mapInstance.on('mouseenter', 'controls', () => { @@ -207,73 +262,155 @@ function registerMapInteractions(event) { }); } +// ─── Custom Table Core ─────────────────────────────────────────────── + +function getHiddenColumns() { + try { + const raw = localStorage.getItem(CTRL_COLUMNS_KEY); + if (!raw) return new Set(); + const parsed = JSON.parse(raw); + return new Set(Array.isArray(parsed) ? parsed : []); + } catch (e) { + return new Set(); + } +} + +function saveHiddenColumns(set) { + try { + localStorage.setItem(CTRL_COLUMNS_KEY, JSON.stringify([...set])); + } catch (e) { + // ignore + } +} + function initControlsTable() { - const $table = $('#controls-table'); - if (!$table.length) { - return; - } - - function calculateTableHeight() { - const windowHeight = $(window).height(); - const offset = 260; - return Math.max(360, windowHeight - offset); - } - - // Prepare initial sort from saved preferences - const initialSort = {}; - if (typeof SAVED_SORT !== 'undefined' && SAVED_SORT.sortName) { - initialSort.sortName = SAVED_SORT.sortName; - initialSort.sortOrder = SAVED_SORT.sortOrder || 'desc'; - } else { - initialSort.sortName = 'updated_at'; - initialSort.sortOrder = 'desc'; - } - - $table.bootstrapTable({ - columns: [ - { field: 'actions', title: '', formatter: 'actionsFormatter', class: 'text-nowrap' }, - { field: 'control_code', title: gettext('Code'), formatter: 'codeFormatter', class: 'text-nowrap col-code', sortable: true }, - { field: 'thematic_names', title: gettext('Thématiques'), formatter: 'thematicFormatter', class: 'col-thematics', sortable: true }, - { field: 'category_names', title: gettext('Catégories'), sortable: true }, - { field: 'control_type_label', title: gettext('Type'), formatter: 'controlTypeFormatter', class: 'text-nowrap', sortable: true }, - { field: 'status_label', title: gettext('Statut'), formatter: 'statusFormatter', class: 'text-nowrap', sortable: true }, - { field: 'project_name', title: gettext('Projet'), formatter: 'projectFormatter', sortable: true }, - { field: 'location', title: gettext('Localisation'), sortable: true }, - { field: 'author', title: gettext('Auteur'), class: 'text-nowrap', sortable: true }, - { field: 'remarks_count', title: gettext('Remarques'), formatter: 'remarksFormatter', class: 'text-center text-nowrap', sortable: true }, - { field: 'created_at', title: gettext('Création'), formatter: 'dateFormatter', class: 'text-nowrap', sortable: true }, - { field: 'updated_at', title: gettext('Mise à jour'), formatter: 'dateFormatter', class: 'text-nowrap', sortable: true }, - { field: 'description', title: gettext('Description'), formatter: 'descriptionFormatter', sortable: true } - ], - height: calculateTableHeight(), - ...initialSort - }); - // Restore saved column visibility - _restoreColumnVisibility($table); + hiddenColumns = getHiddenColumns(); - // Save column visibility when toggled - $table.on('column-switch.bs.table', function () { - setTimeout(function () { - _saveColumnVisibility($table); - $table.bootstrapTable('resetView'); - }, 0); + // Restore initial sort + if (typeof SAVED_SORT !== 'undefined' && SAVED_SORT && SAVED_SORT.sortName) { + sortField = SAVED_SORT.sortName; + sortDir = SAVED_SORT.sortOrder || 'desc'; + } else { + sortField = 'updated_at'; + sortDir = 'desc'; + } + + // Page size setup + const pageSizeSelect = document.getElementById('page-size-select'); + if (pageSizeSelect) { + pageSize = parseInt(pageSizeSelect.value, 10) || 50; + pageSizeSelect.addEventListener('change', function () { + pageSize = parseInt(this.value, 10) || 50; + currentPage = 1; + renderTable(); + }); + } + + // Columns chooser dropdown + initColumnChooser(); + + // Sortable headers click listeners + document.querySelectorAll('#controls-table thead th.sortable').forEach(th => { + th.addEventListener('click', function () { + const field = this.dataset.field; + if (!field) return; + handleHeaderSort(field); + }); }); - // Save sort preferences when sort changes - $table.on('sort.bs.table', function () { - setTimeout(savePreferencesFromUrl, 500); + // Pagination clicks delegation + document.getElementById('controls-pagination-list')?.addEventListener('click', (e) => { + const link = e.target.closest('a.page-link'); + if (!link) return; + e.preventDefault(); + const newPage = parseInt(link.dataset.page, 10); + if (!isNaN(newPage) && newPage >= 1 && newPage !== currentPage) { + currentPage = newPage; + renderTable(); + const scrollable = document.getElementById('controls-table-scrollable'); + if (scrollable) scrollable.scrollTo({ top: 0, behavior: 'smooth' }); + } }); - registerZoomAction('#controls-table', '.zoom-btn', map, 16); + // Zoom button click delegation in table + document.getElementById('controls-table')?.addEventListener('click', (e) => { + const btn = e.target.closest('.zoom-btn'); + if (!btn || !map) return; + const lon = parseFloat(btn.dataset.lon); + const lat = parseFloat(btn.dataset.lat); + if (!isNaN(lon) && !isNaN(lat)) { + map.flyTo({ + center: [lon, lat], + zoom: 16, + essential: true + }); + } + }); +} - $table.on('post-body.bs.table', () => { - visibleRows = $('#controls-table').bootstrapTable('getData'); - syncMapWithTable(); +function initColumnChooser() { + const container = document.getElementById('columns-list-container'); + if (!container) return; + + container.innerHTML = ''; + AVAILABLE_COLUMNS.forEach(col => { + const isVisible = !hiddenColumns.has(col.key); + const div = document.createElement('div'); + div.className = 'form-check py-1'; + div.innerHTML = ` + + + `; + container.appendChild(div); }); - $(window).on('resize', () => { - $table.bootstrapTable('resetView', { height: calculateTableHeight() }); + // Checkbox change listener + container.addEventListener('change', (e) => { + const cb = e.target.closest('.col-toggle-check'); + if (!cb) return; + const colKey = cb.dataset.column; + if (cb.checked) { + hiddenColumns.delete(colKey); + } else { + hiddenColumns.add(colKey); + } + saveHiddenColumns(hiddenColumns); + applyColumnVisibility(); + }); + + // Don't close dropdown on click inside + document.querySelector('.columns-dropdown-menu')?.addEventListener('click', (e) => { + e.stopPropagation(); + }); + + // Select all columns + document.getElementById('cols-select-all')?.addEventListener('click', (e) => { + e.preventDefault(); + hiddenColumns.clear(); + saveHiddenColumns(hiddenColumns); + container.querySelectorAll('.col-toggle-check').forEach(cb => { cb.checked = true; }); + applyColumnVisibility(); + }); + + // Reset columns to default + document.getElementById('cols-reset-default')?.addEventListener('click', (e) => { + e.preventDefault(); + hiddenColumns.clear(); + localStorage.removeItem(CTRL_COLUMNS_KEY); + container.querySelectorAll('.col-toggle-check').forEach(cb => { cb.checked = true; }); + applyColumnVisibility(); + }); + + applyColumnVisibility(); +} + +function applyColumnVisibility() { + AVAILABLE_COLUMNS.forEach(col => { + const isHidden = hiddenColumns.has(col.key); + document.querySelectorAll(`.col-${col.key}`).forEach(el => { + el.classList.toggle('d-none', isHidden); + }); }); } @@ -285,7 +422,6 @@ function loadDataIntoControlsTable(features) { const typeMeta = getControlTypeMetadata(props.control_type); return { - actions: '', id: props.id || feature.id, control_code: props.control_code || '', description: props.description || '', @@ -312,20 +448,292 @@ function loadDataIntoControlsTable(features) { }; }); - $('#controls-spinner').addClass('d-none'); - $('#controls-table-container').removeClass('d-none'); - $('#controls-table').bootstrapTable('load', tableData); + allControls = tableData; + filteredControls = [...allControls]; + sortData(filteredControls, sortField, sortDir); - visibleRows = tableData; + currentPage = 1; + renderTable(); + + document.getElementById('controls-spinner')?.classList.add('d-none'); + document.getElementById('controls-table-container')?.classList.remove('d-none'); + + visibleRows = filteredControls; syncMapWithTable(); - updateResultCount(tableData.length); + updateResultCount(filteredControls.length); + updateFilterBadge(); } +function sortData(data, field, dir) { + const multiplier = dir === 'asc' ? 1 : -1; + data.sort((a, b) => { + let valA = a[field]; + let valB = b[field]; + + if (valA === null || valA === undefined || valA === '') return 1 * multiplier; + if (valB === null || valB === undefined || valB === '') return -1 * multiplier; + + if (field === 'remarks_count') { + return (Number(valA) - Number(valB)) * multiplier; + } + if (field === 'created_at' || field === 'updated_at') { + return (new Date(valA).getTime() - new Date(valB).getTime()) * multiplier; + } + return String(valA).localeCompare(String(valB), undefined, { numeric: true, sensitivity: 'base' }) * multiplier; + }); +} + +function handleHeaderSort(field) { + if (sortField === field) { + sortDir = sortDir === 'asc' ? 'desc' : 'asc'; + } else { + sortField = field; + sortDir = ['updated_at', 'created_at', 'remarks_count'].includes(field) ? 'desc' : 'asc'; + } + + sortData(filteredControls, sortField, sortDir); + currentPage = 1; + renderTable(); + savePreferencesFromUrl(); +} + +function updateHeaderSortIcons() { + document.querySelectorAll('#controls-table thead th.sortable').forEach(th => { + const field = th.dataset.field; + const icon = th.querySelector('.sort-icon'); + if (!icon) return; + if (field === sortField) { + icon.className = `sort-icon bi ${sortDir === 'asc' ? 'bi-sort-up' : 'bi-sort-down'} text-primary`; + } else { + icon.className = 'sort-icon bi bi-arrow-down-up text-muted opacity-50'; + } + }); +} + +function renderTable() { + const tbody = document.getElementById('controls-table-tbody'); + if (!tbody) return; + + const totalItems = filteredControls.length; + const totalPages = Math.ceil(totalItems / pageSize) || 1; + if (currentPage > totalPages) currentPage = totalPages; + if (currentPage < 1) currentPage = 1; + + const startIndex = (currentPage - 1) * pageSize; + const endIndex = Math.min(startIndex + pageSize, totalItems); + const pageItems = filteredControls.slice(startIndex, endIndex); + + // État vide + if (totalItems === 0) { + tbody.innerHTML = ` + + +
+
+
${gettext('Aucun contrôle trouvé')}
+
${gettext('Aucun contrôle ne correspond à vos critères de recherche actuels.')}
+ +
+ + + `; + updatePaginationBar(0, 0, 0, 1); + updateHeaderSortIcons(); + applyColumnVisibility(); + return; + } + + // Rendu des lignes + let rowsHtml = ''; + for (let i = 0; i < pageItems.length; i++) { + rowsHtml += renderRowHtml(pageItems[i]); + } + tbody.innerHTML = rowsHtml; + + updatePaginationBar(startIndex + 1, endIndex, totalItems, totalPages); + updateHeaderSortIcons(); + applyColumnVisibility(); +} + +function renderRowHtml(row) { + const codeLabel = escapeHtml(row.control_code || `CTRL#${row.id}`); + const typeMeta = getControlTypeMetadata(row.control_type); + const statusMeta = getStatusMetadata(row.status); + const detailUrl = `${controlsIndexUrl}${row.id}/`; + + const zoomBtn = (row.lon && row.lat) + ? `` + : ``; + + const actionBtn = ``; + + const themIcon = row.thematic_icon ? `` : ''; + const typeIcon = typeMeta.icon ? `` : ''; + + const projectHtml = row.project_name && row.project_name !== '-' + ? (row.project_id && typeof projectsDetailBaseUrl !== 'undefined' + ? `${escapeHtml(row.project_name)}` + : `${escapeHtml(row.project_name)}`) + : '-'; + + const remarksCount = parseInt(row.remarks_count, 10) || 0; + const remarksBadge = remarksCount > 0 + ? `${remarksCount}` + : `0`; + + const descText = row.description ? truncateText(row.description, DESCRIPTION_MAX_LENGTH) : '-'; + const descTitle = row.description ? escapeHtml(row.description) : ''; + + return ` + + ${zoomBtn}${actionBtn} + + ${codeLabel} + + + ${themIcon}${escapeHtml(row.thematic_names || '-')} + + + ${escapeHtml(row.category_names || '-')} + + + ${typeIcon}${escapeHtml(row.control_type_label || typeMeta.label)} + + + ${escapeHtml(row.status_label || statusMeta.label)} + + + ${projectHtml} + + + ${escapeHtml(row.location || '-')} + + + ${escapeHtml(row.author || '-')} + + + ${remarksBadge} + + + ${dateFormatter(row.created_at)} + + + ${dateFormatter(row.updated_at)} + + + ${escapeHtml(descText)} + + + `; +} + +function updatePaginationBar(start, end, total, totalPages) { + const infoEl = document.getElementById('controls-pagination-info'); + const listEl = document.getElementById('controls-pagination-list'); + if (!infoEl || !listEl) return; + + if (total === 0) { + infoEl.textContent = gettext('Aucun contrôle chargé'); + listEl.innerHTML = ''; + return; + } + + infoEl.innerHTML = interpolate( + gettext('Affichage de %(start)s à %(end)s sur %(total)s contrôle(s)'), + { start, end, total }, + true + ); + + if (totalPages <= 1) { + listEl.innerHTML = ''; + return; + } + + let pagesHtml = ''; + + // Bouton Précédent + const prevDisabled = currentPage === 1 ? 'disabled' : ''; + pagesHtml += ` +
  • + + + +
  • + `; + + // Numéros de page + const pageNumbers = getPageRange(currentPage, totalPages); + pageNumbers.forEach(p => { + if (p === '...') { + pagesHtml += `
  • …
  • `; + } else { + const activeClass = p === currentPage ? 'active' : ''; + pagesHtml += ` +
  • + ${p} +
  • + `; + } + }); + + // Bouton Suivant + const nextDisabled = currentPage === totalPages ? 'disabled' : ''; + pagesHtml += ` +
  • + + + +
  • + `; + + listEl.innerHTML = pagesHtml; +} + +function getPageRange(current, total) { + if (total <= 7) { + return Array.from({ length: total }, (_, i) => i + 1); + } + if (current <= 4) { + return [1, 2, 3, 4, 5, '...', total]; + } + if (current >= total - 3) { + return [1, '...', total - 4, total - 3, total - 2, total - 1, total]; + } + return [1, '...', current - 1, current, current + 1, '...', total]; +} + +function highlightControlRow(id) { + const rowIndex = filteredControls.findIndex(row => String(row.id) === String(id)); + if (rowIndex === -1) return; + + const targetPage = Math.floor(rowIndex / pageSize) + 1; + if (currentPage !== targetPage) { + currentPage = targetPage; + renderTable(); + } + + setTimeout(() => { + document.querySelectorAll('#controls-table-tbody tr').forEach(tr => { + tr.classList.remove('selected', 'rowhighlight'); + }); + const targetTr = document.querySelector(`#controls-table-tbody tr[data-id="${id}"]`); + if (targetTr) { + targetTr.classList.add('selected', 'rowhighlight'); + targetTr.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, 60); +} + +// Alias global pour compatibilité avec base_script / popups +window.highlightRow = function (tableId, field, value) { + highlightControlRow(value); +}; + function updateFilterLimitAlert(count) { const alertEl = document.getElementById('filter-limit-alert'); - if (!alertEl) { - return; - } + if (!alertEl) return; const reached = count >= MAX_ITEMS; alertEl.classList.toggle('d-none', !reached); @@ -338,9 +746,9 @@ function updateFilterLimitAlert(count) { function updateResultCount(count) { const target = document.getElementById('controls-result-count'); - if (!target) { - return; - } + const badge = document.getElementById('controls-counter-badge'); + if (badge) badge.textContent = count; + if (!target) return; if (!count) { target.textContent = gettext('Aucun contrôle chargé'); @@ -352,9 +760,7 @@ function updateResultCount(count) { } function fitMapToControls(features) { - if (!map || !features.length) { - return; - } + if (!map || !features.length) return; const bounds = new maplibregl.LngLatBounds(); features.forEach((feature) => { @@ -369,33 +775,81 @@ function fitMapToControls(features) { } } -function setupFilterForm() { - const form = document.getElementById('filter-form'); - if (!form) { +function syncMapWithTable() { + if (!map || !map.getLayer('controls')) return; + + if (!visibleRows.length || visibleRows.length === allControls.length) { + map.setFilter('controls', null); return; } - // Apply filters button + const ids = visibleRows.map((row) => row.id); + if (!ids.length) { + map.setFilter('controls', ['==', ['get', 'id'], '__none__']); + return; + } + + map.setFilter('controls', ['match', ['get', 'id'], ['literal', ids], true, false]); +} + +function countActiveFilters() { + const form = document.getElementById('filter-form'); + if (!form) return 0; + let count = 0; + if (form.querySelectorAll('input[name="thematic"]:checked').length > 0) count++; + if (form.querySelectorAll('input[name="category"]:checked').length > 0) count++; + if (form.querySelectorAll('input[name="type"]:checked').length > 0) count++; + if (form.querySelectorAll('input[name="status"]:checked').length > 0) count++; + if (form.querySelectorAll('input[name="project"]').length > 0) count++; + const dateFrom = form.querySelector('input[name="date_from"]')?.value; + const dateTo = form.querySelector('input[name="date_to"]')?.value; + if (dateFrom || dateTo) count++; + if (form.querySelector('input[name="mine"]')?.checked) count++; + const searchVal = document.getElementById('controls-search-input')?.value.trim(); + if (searchVal) count++; + return count; +} + +function updateFilterBadge() { + const count = countActiveFilters(); + const badge = document.getElementById('filter-badge'); + const countSpan = document.getElementById('filter-count'); + if (badge && countSpan) { + if (count > 0) { + countSpan.textContent = count; + badge.classList.remove('d-none'); + } else { + badge.classList.add('d-none'); + } + } +} + +// ─── Filter Form & Preferences ─────────────────────────────────────── + +function setupFilterForm() { + const form = document.getElementById('filter-form'); + if (!form) return; + + // Bouton appliquer les filtres document.getElementById('apply-filters')?.addEventListener('click', () => { const params = buildFilterParams(new FormData(form)); _applySearchToParams(params); savePreferencesFromForm(params); refreshListing(params); + updateFilterBadge(); const filterModal = bootstrap.Modal.getInstance(document.getElementById('filterModal')); if (filterModal) filterModal.hide(); }); - // Reset filters button + // Bouton réinitialiser les filtres document.getElementById('reset-filters')?.addEventListener('click', () => { - // Uncheck filter checkboxes form.querySelectorAll('input[type="checkbox"][name="thematic"]').forEach(cb => { cb.checked = false; }); form.querySelectorAll('input[type="checkbox"][name="category"]').forEach(cb => { cb.checked = false; }); form.querySelectorAll('input[type="checkbox"][name="type"]').forEach(cb => { cb.checked = false; }); form.querySelectorAll('input[type="checkbox"][name="status"]').forEach(cb => { cb.checked = false; }); form.querySelectorAll('input[type="checkbox"][name="project"]').forEach(cb => { cb.checked = false; }); - // Reset dates and mine const dateFrom = form.querySelector('input[name="date_from"]'); const dateTo = form.querySelector('input[name="date_to"]'); if (dateFrom) dateFrom.value = ''; @@ -403,7 +857,6 @@ function setupFilterForm() { const mineCheck = form.querySelector('input[name="mine"]'); if (mineCheck) mineCheck.checked = false; - // Clear project chips const projContainer = document.getElementById('selected-projects-container'); if (projContainer) { projContainer.querySelectorAll('.project-chip').forEach(chip => chip.remove()); @@ -423,7 +876,6 @@ function setupFilterForm() { const projClear = document.getElementById('project-search-clear-btn'); if (projClear) projClear.style.display = 'none'; - // Reset category view document.querySelectorAll('.filter-options[data-field="category"] .filter-option[data-parent-id]:not([data-parent-id=""])').forEach(ch => { ch.style.display = 'none'; }); @@ -433,24 +885,24 @@ function setupFilterForm() { const params = buildFilterParams(new FormData(form)); _applySearchToParams(params); - // Save empty preferences on reset if (typeof savePreferencesUrl !== 'undefined') { fetch(savePreferencesUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, - body: JSON.stringify({ filters: {}, sort: {} }) + body: JSON.stringify({ filters: {}, sort: { sortName: sortField, sortOrder: sortDir } }) }).catch(() => {}); } refreshListing(params); + updateFilterBadge(); const filterModal = bootstrap.Modal.getInstance(document.getElementById('filterModal')); if (filterModal) filterModal.hide(); }); - // Select-all / deselect-all buttons + // Boutons Tous / Aucun document.querySelectorAll('.select-all-btn').forEach(btn => { - btn.addEventListener('click', function(e) { + btn.addEventListener('click', function (e) { e.preventDefault(); const field = this.getAttribute('data-field'); const container = document.querySelector(`.filter-options[data-field="${field}"]`); @@ -463,7 +915,7 @@ function setupFilterForm() { }); document.querySelectorAll('.deselect-all-btn').forEach(btn => { - btn.addEventListener('click', function(e) { + btn.addEventListener('click', function (e) { e.preventDefault(); const field = this.getAttribute('data-field'); const container = document.querySelector(`.filter-options[data-field="${field}"]`); @@ -478,7 +930,7 @@ function setupFilterForm() { }); }); - // Thematic → category chain + // Chaînage Thématique → Catégorie document.querySelectorAll('input[name="thematic"]').forEach(cb => { cb.addEventListener('change', () => { filterCategoriesByThematics(); @@ -486,7 +938,7 @@ function setupFilterForm() { }); document.querySelectorAll('input[name="category"]').forEach(cb => { - cb.addEventListener('change', function() { + cb.addEventListener('change', function () { const option = this.closest('.filter-option'); const catId = option ? option.getAttribute('data-category-id') : null; const parentId = option ? option.getAttribute('data-parent-id') : null; @@ -519,7 +971,7 @@ function setupFilterForm() { }); }); - // Subcategory groups initialization + // Groupes de sous-catégories const catContainer = document.querySelector('.filter-options[data-field="category"]'); if (catContainer) { const parentIds = new Set(); @@ -572,7 +1024,7 @@ function setupFilterForm() { if (allChildren[0]) allChildren[0].style.marginLeft = '0.5rem'; - // Controls Tous/Aucun + // Contrôles Tous/Aucun const controls = document.createElement('div'); controls.className = 'filter-subcat-controls'; controls.setAttribute('data-for-parent', parentId); @@ -582,7 +1034,7 @@ function setupFilterForm() { ``; allChildren[allChildren.length - 1].insertAdjacentElement('afterend', controls); - controls.querySelector('.subcat-select-all').addEventListener('click', function(e) { + controls.querySelector('.subcat-select-all').addEventListener('click', function (e) { e.preventDefault(); catContainer.querySelectorAll(`.filter-option[data-parent-id="${parentId}"]`).forEach(opt => { if (opt.style.display !== 'none') { @@ -591,7 +1043,7 @@ function setupFilterForm() { } }); }); - controls.querySelector('.subcat-deselect-all').addEventListener('click', function(e) { + controls.querySelector('.subcat-deselect-all').addEventListener('click', function (e) { e.preventDefault(); catContainer.querySelectorAll(`.filter-option[data-parent-id="${parentId}"]`).forEach(opt => { const cb = opt.querySelector('input[type="checkbox"]'); @@ -648,7 +1100,7 @@ function setupProjectAutocomplete() { function addProjectChip(id, name) { const currentIds = getSelectedProjectIds(); if (currentIds.includes(String(id))) { - return; // already added + return; } const chip = document.createElement('div'); @@ -656,7 +1108,7 @@ function setupProjectAutocomplete() { chip.setAttribute('data-project-id', id); chip.innerHTML = ` - ${name} + ${escapeHtml(name)} `; @@ -665,7 +1117,6 @@ function setupProjectAutocomplete() { updateEmptyMessage(); } - // Remove project chip handler (delegated) container.addEventListener('click', (e) => { const removeBtn = e.target.closest('.remove-project-btn'); if (removeBtn) { @@ -677,14 +1128,12 @@ function setupProjectAutocomplete() { } }); - // Clear all projects button clearAllBtn?.addEventListener('click', (e) => { e.preventDefault(); container.querySelectorAll('.project-chip').forEach(chip => chip.remove()); updateEmptyMessage(); }); - // Autocomplete search input.addEventListener('input', () => { const q = input.value.trim(); if (clearBtn) clearBtn.style.display = q ? '' : 'none'; @@ -718,7 +1167,7 @@ function setupProjectAutocomplete() { item.setAttribute('data-project-name', p.name); item.setAttribute('data-index', idx); item.innerHTML = ` - ${p.label} + ${escapeHtml(p.label)} ${isSelected ? `${gettext('Ajouté')}` : ``} `; item.addEventListener('click', () => { @@ -735,7 +1184,6 @@ function setupProjectAutocomplete() { }, 200); }); - // Keyboard navigation in autocomplete input.addEventListener('keydown', (e) => { const items = dropdown.querySelectorAll('.list-group-item-action'); if (!items.length || dropdown.classList.contains('d-none')) { @@ -770,7 +1218,6 @@ function setupProjectAutocomplete() { input.focus(); }); - // Close on click outside document.addEventListener('click', (e) => { if (!input.contains(e.target) && !dropdown.contains(e.target)) { closeDropdown(); @@ -841,8 +1288,8 @@ function refreshListing(params) { _updateExportExcelUrl(queryString); - $('#controls-spinner').removeClass('d-none'); - $('#controls-table-container').addClass('d-none'); + document.getElementById('controls-spinner')?.classList.remove('d-none'); + document.getElementById('controls-table-container')?.classList.add('d-none'); handleGeojsonMapLoading(false, controlsgeojsonloader, 'controls'); controlsgeojsonloader.loadGeojson(targetUrl); @@ -854,7 +1301,30 @@ function _updateExportExcelUrl(queryString) { btn.href = queryString ? `${controlsExportExcelUrl}?${queryString}` : controlsExportExcelUrl; } -// === Search widget === +// ─── Suppression rapide des filtres actifs (chips) ─────────────────── + +function setupQuickFilterRemovers() { + document.querySelectorAll('.remove-filter-btn').forEach(btn => { + btn.addEventListener('click', function (e) { + e.preventDefault(); + const param = this.dataset.param; + const val = this.dataset.val; + const url = new URL(window.location.href); + + if (param === 'q' || param === 'date_from' || param === 'date_to' || param === 'mine') { + url.searchParams.delete(param); + } else { + const remaining = url.searchParams.getAll(param).filter(v => v !== val); + url.searchParams.delete(param); + remaining.forEach(v => url.searchParams.append(param, v)); + } + + window.location.href = url.toString(); + }); + }); +} + +// ─── Search Widget ─────────────────────────────────────────────────── function _applySearchToParams(params) { const q = $('#controls-search-input').val().trim(); @@ -915,111 +1385,17 @@ window.addEventListener('load', function () { function setupFilterZoneButton() { const button = document.getElementById('filterzone'); - if (!button) { - return; - } + if (!button) return; button.addEventListener('click', (event) => { event.preventDefault(); - if (!map || !controls_geojson.features) { - return; - } + if (!map || !controls_geojson.features) return; filterTableByMapBounds(map, controls_geojson, loadDataIntoControlsTable); }); } -function syncMapWithTable() { - if (!map || !map.getLayer('controls')) { - return; - } +// ─── Layout Switcher ───────────────────────────────────────────────── - if (!visibleRows.length || visibleRows.length === allControls.length) { - map.setFilter('controls', null); - return; - } - - const ids = visibleRows.map((row) => row.id); - if (!ids.length) { - map.setFilter('controls', ['==', ['get', 'id'], '__none__']); - return; - } - - map.setFilter('controls', ['match', ['get', 'id'], ['literal', ids], true, false]); -} - -// Table column formatters - -function actionsFormatter(value, row) { - let html = ''; - if (row.lon && row.lat) { - html += ` - `; - } - return html; -} - -function codeFormatter(value, row) { - const label = value || '-'; - return `${label}`; -} - -function descriptionFormatter(value, row) { - if (!value || !value.trim()) return '-'; - const safeValue = value.trim(); - const truncated = truncateText(safeValue, DESCRIPTION_MAX_LENGTH); - return `${truncated}`; -} - -function thematicFormatter(value, row) { - if (!value || value === '-') { - return '-'; - } - const icon = row.thematic_icon ? `` : ''; - return `${icon}${value}`; -} - -function statusFormatter(value, row) { - if (!value) { - return '-'; - } - const meta = getStatusMetadata(row.status); - const label = row.status_label || meta.label; - return `${label}`; -} - -function controlTypeFormatter(value, row) { - const meta = getControlTypeMetadata(row.control_type); - const label = value || meta.label; - const icon = row.control_type_icon ? `` : ''; - return `${icon}${label}`; -} - -function projectFormatter(value, row) { - if (!value || value === '-') return '-'; - if (row.project_id && typeof projectsDetailBaseUrl !== 'undefined') { - return `${value}`; - } - return `${value}`; -} - -function remarksFormatter(value, row) { - const count = parseInt(value, 10) || 0; - if (count === 0) return '0'; - return `${count}`; -} - -window.actionsFormatter = actionsFormatter; -window.codeFormatter = codeFormatter; -window.descriptionFormatter = descriptionFormatter; -window.thematicFormatter = thematicFormatter; -window.statusFormatter = statusFormatter; -window.controlTypeFormatter = controlTypeFormatter; -window.projectFormatter = projectFormatter; -window.remarksFormatter = remarksFormatter; - -// Layout switcher function setupLayoutButtons() { const btnTable = document.getElementById('layout-btn-table'); const btnBoth = document.getElementById('layout-btn-both'); @@ -1027,9 +1403,7 @@ function setupLayoutButtons() { const colLeft = document.getElementById('col-left'); const colRight = document.getElementById('col-right'); - if (!btnTable || !btnBoth || !btnMap || !colLeft || !colRight) { - return; - } + if (!btnTable || !btnBoth || !btnMap || !colLeft || !colRight) return; function setLayout(layout) { [btnTable, btnBoth, btnMap].forEach(btn => btn.classList.remove('text-primary')); @@ -1045,7 +1419,6 @@ function setupLayoutButtons() { colRight.classList.remove('d-none', 'col-md-6'); colRight.classList.add('col-12'); } else { - // both (default) btnBoth.classList.add('text-primary'); colLeft.classList.remove('d-none', 'col-12'); colLeft.classList.add('col-md-6'); @@ -1057,7 +1430,6 @@ function setupLayoutButtons() { setTimeout(() => { maploader.map?.resize(); - $('#controls-table').bootstrapTable('resetView'); }, 100); } @@ -1069,7 +1441,7 @@ function setupLayoutButtons() { setLayout(savedLayout); } -// Preferences Management +// ─── Preferences Management ────────────────────────────────────────── function getCookie(name) { let cookieValue = null; @@ -1086,36 +1458,6 @@ function getCookie(name) { return cookieValue; } -const CTRL_COLUMNS_KEY = 'controls_columns_hidden'; - -function _saveColumnVisibility($table) { - try { - const allCols = $table.bootstrapTable('getOptions').columns[0] || []; - const visibleFields = $table.bootstrapTable('getVisibleColumns').map((c) => c.field); - const hiddenFields = allCols - .filter((c) => c.field && c.field !== 'actions' && !visibleFields.includes(c.field)) - .map((c) => c.field); - localStorage.setItem(CTRL_COLUMNS_KEY, JSON.stringify(hiddenFields)); - } catch (e) { - // ignore - } -} - -function _restoreColumnVisibility($table) { - try { - const raw = localStorage.getItem(CTRL_COLUMNS_KEY); - if (!raw) return; - const hiddenFields = JSON.parse(raw); - if (Array.isArray(hiddenFields)) { - hiddenFields.forEach((field) => { - $table.bootstrapTable('hideColumn', field); - }); - } - } catch (e) { - // ignore - } -} - function savePreferencesFromUrl() { if (typeof savePreferencesUrl === 'undefined') return; @@ -1131,14 +1473,7 @@ function savePreferencesFromUrl() { } }); - const $table = $('#controls-table'); - let sort = {}; - if ($table.length && $table.data('bootstrap.table')) { - const options = $table.bootstrapTable('getOptions'); - if (options.sortName) { - sort = { sortName: options.sortName, sortOrder: options.sortOrder || 'desc' }; - } - } + const sort = { sortName: sortField, sortOrder: sortDir }; fetch(savePreferencesUrl, { method: 'POST', @@ -1161,14 +1496,7 @@ async function savePreferencesFromForm(urlParams) { } }); - const $table = $('#controls-table'); - let sort = {}; - if ($table.length && $table.data('bootstrap.table')) { - const options = $table.bootstrapTable('getOptions'); - if (options.sortName) { - sort = { sortName: options.sortName, sortOrder: options.sortOrder || 'desc' }; - } - } + const sort = { sortName: sortField, sortOrder: sortDir }; try { await fetch(savePreferencesUrl, { diff --git a/loko/controls/templates/controls/controls_index.html b/loko/controls/templates/controls/controls_index.html index 72a7216..a49f28c 100644 --- a/loko/controls/templates/controls/controls_index.html +++ b/loko/controls/templates/controls/controls_index.html @@ -8,24 +8,27 @@ {% block content %} - +