refonte: sidebar transform fixe, sticky headers, pipeline filters, margin-left mainWrapper

This commit is contained in:
Nabil Derouiche 2026-05-30 17:16:46 +00:00
parent ba77ef8240
commit a14d030a5a
4 changed files with 115 additions and 13 deletions

View File

@ -0,0 +1,79 @@
{
"permissions": {
"allow": [
"Bash(ls /Volumes/web/rla/*.env /Volumes/web/rla/.env* 2>/dev/null; ls /Volumes/web/rla/node_modules | grep -E \"puppeteer|pptxgen|xlsx|exceljs|docx|pdf\" 2>/dev/null)",
"Bash(npm install:*)",
"Bash(node -e \"require\\('./services/calc'\\); console.log\\('calc OK'\\)\" 2>&1)",
"Bash(node -e \"require\\('./services/export-pdf'\\); console.log\\('export-pdf OK'\\)\" 2>&1)",
"Bash(node -e \"\nrequire\\('dotenv'\\).config\\(\\);\n// Mock pour tester sans baserow\nconsole.log\\('Routes test...'\\);\nrequire\\('./routes/synthese'\\);\nrequire\\('./routes/alertes'\\);\nrequire\\('./routes/en-service'\\);\nrequire\\('./routes/en-cours'\\);\nrequire\\('./routes/par-region'\\);\nrequire\\('./routes/clotures'\\);\nrequire\\('./routes/pilotage'\\);\nrequire\\('./routes/matrice-risque'\\);\nrequire\\('./routes/export'\\);\nconsole.log\\('Toutes les routes chargées OK'\\);\n\")",
"Bash(node -e \"\n// Test complet de chargement\nrequire\\('dotenv'\\).config\\(\\);\nconst exp = require\\('./services/export-pdf'\\);\nconst xl = require\\('./services/export-xlsx'\\);\nconsole.log\\('PDF service:', typeof exp.generateSynthese\\);\nconsole.log\\('XLSX service:', typeof xl.generateXlsx\\);\n\n// Test mock PDF\nconst buf = exp.generateSynthese\\({\n total:3, actifs:2, clotures:1,\n taux_avancement_moyen: 65,\n budget: { total:'1 200 000 DT', consomme:'800 000 DT', restant:'400 000 DT' },\n par_statut: { 'En cours': 2, 'Clôturé': 1 },\n alertes_delais: { count:1, critique:0, items:[{ ref:'M-001', projet:'Projet A', region:'Gabes', entrepreneur:'Ent X', delai_restant:60, niveau:'attention' }] }\n}\\);\nconsole.log\\('PDF synthese OK, size:', buf.length, 'bytes'\\);\n\")",
"Bash(node -e \"\nconst pdf = require\\('./services/export-pdf'\\);\npdf.generateSynthese\\({\n total:5, actifs:4, clotures:1,\n taux_avancement_moyen: 68,\n budget: { total:'1 500 000 DT', consomme:'900 000 DT', restant:'600 000 DT' },\n par_statut: { 'En cours': 3, 'En service': 1, 'Clôturé': 1 },\n alertes_delais: { count:2, critique:1, items:[\n { ref:'M-001', projet:'Projet A', region:'Gabes', entrepreneur:'Ent X', delai_restant:30, niveau:'critique' },\n { ref:'M-002', projet:'Projet B', region:'Sfax', entrepreneur:'Ent Y', delai_restant:75, niveau:'attention' },\n ]}\n}\\).then\\(buf => {\n console.log\\('PDF généré OK:', buf.length, 'bytes'\\);\n require\\('fs'\\).writeFileSync\\('/tmp/test-rla.pdf', buf\\);\n console.log\\('Fichier écrit : /tmp/test-rla.pdf'\\);\n}\\).catch\\(e => console.error\\('ERREUR:', e.message\\)\\);\n\")",
"Bash(curl -s -X POST http://localhost:3005/api/auth/login \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"username\":\"nabil\",\"password\":\"Rla2025Sfax\"}' | python3 -c \"\nimport sys,json; d=json.load\\(sys.stdin\\)\nif 'token' in d:\n import base64\n payload = d['token'].split\\('.'\\)[1]\n payload += '=' * \\(4 - len\\(payload\\) % 4\\)\n info = json.loads\\(base64.b64decode\\(payload\\)\\)\n print\\('Login OK — role:', info['role'], '| region:', info['region']\\)\nelse:\n print\\('ERREUR:', d\\)\n\")",
"Bash(which sshpass:*)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {sudo docker ps | grep rla}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1\n\nexpect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {ls /usr/local/bin/docker /usr/bin/docker /var/packages/ContainerManager/usr/bin/docker 2>&1; which docker; echo PATH=\\\\$PATH}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"WebFetch(domain:baserow.bolbol.tn)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {curl -s -H 'Authorization: Token zJaDdkttN1gr6oPvd3cxfCXNwzvvwMMF' 'https://baserow.bolbol.tn/api/database/rows/table/856/?page=1&size=3&user_field_names=true'}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {curl -s -H 'Authorization: Token zJaDdkttN1gr6oPvd3cxfCXNwzvvwMMF' 'https://baserow.bolbol.tn/api/database/rows/table/872/?page=1&size=3&user_field_names=true'}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuYWJpbCIsImlkIjoxLCJ1c2VybmFtZSI6Im5hYmlsIiwicm9sZSI6InN1cGVyYWRtaW4iLCJyZWdpb24iOiJhbGwiLCJpYXQiOjE3NzMzNTg3MTksImV4cCI6MTc3MzM4NzUxOX0.A1XEUDQAiOOyl8R-rPO6qYl8W_aa2Os-oB23aNIu2vs\" __NEW_LINE_096044aa0eaba021__ echo \"=== /api/en-service ===\" curl -sk https://rla.bolbol.tn/api/en-service -H \"Authorization: Bearer $TOKEN\" 2)",
"Bash(1 __NEW_LINE_096044aa0eaba021__ echo \"\" echo \"=== /api/marches \\(first 2000 chars\\) ===\" curl -sk https://rla.bolbol.tn/api/marches -H \"Authorization: Bearer $TOKEN\")",
"Bash(TOKEN=\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJuYWJpbCIsImlkIjoxLCJ1c2VybmFtZSI6Im5hYmlsIiwicm9sZSI6InN1cGVyYWRtaW4iLCJyZWdpb24iOiJhbGwiLCJpYXQiOjE3NzMzNTg3MTksImV4cCI6MTc3MzM4NzUxOX0.A1XEUDQAiOOyl8R-rPO6qYl8W_aa2Os-oB23aNIu2vs\"\necho \"=== /api/marches ===\"\ncurl -sk https://rla.bolbol.tn/api/marches -H \"Authorization: Bearer $TOKEN\" 2>&1 | python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); items=d.get\\('items',[d]\\) if isinstance\\(d,dict\\) else d; print\\('Keys in response:', list\\(d.keys\\(\\)\\) if isinstance\\(d,dict\\) else 'array'\\); print\\('First item keys:', list\\(items[0].keys\\(\\)\\) if items else 'empty'\\)\" 2>&1\n\necho \"\"\necho \"=== /api/synthese ===\"\ncurl -sk https://rla.bolbol.tn/api/synthese -H \"Authorization: Bearer $TOKEN\" 2>&1 | python3 -c \"import json,sys; d=json.load\\(sys.stdin\\); print\\(json.dumps\\(d, indent=2, ensure_ascii=False\\)[:3000]\\)\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {ls /app 2>/dev/null || /usr/local/bin/docker exec rla-api ls /app}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/server.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/marches.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/en-service.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api ls /app/routes/ && /usr/local/bin/docker exec rla-api cat /app/services/baserow.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/services/calc.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/synthese.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(expect -c \"\nspawn ssh -o StrictHostKeyChecking=no -p 22222 Best0f@192.168.100.33 {/usr/local/bin/docker exec rla-api cat /app/routes/pipeline.js}\nexpect \\\\\"password:\\\\\"\nsend \\\\\"2L2u519w@ommi\\\\r\\\\\"\nexpect eof\n\" 2>&1)",
"Bash(sshpass -p '2L2u519w@ommi' ssh -p 22222 -o StrictHostKeyChecking=no Best0f@192.168.100.33 \\\\\n \"echo 'SSH OK'; docker inspect rla-api --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}'; find /volume1 -name 'server.js' -path '*rla*' 2>/dev/null | head -3\")",
"Bash(expect -c \"\nspawn ssh -p 22222 -o StrictHostKeyChecking=no Best0f@192.168.100.33 {sudo docker inspect rla-api --format '{{range .Mounts}}{{.Source}}:{{.Destination}} {{end}}' 2>/dev/null; find /volume1 -name server.js 2>/dev/null | grep -i rla | head -5; ls /volume1/web/ 2>/dev/null}\nexpect {\n \\\\\"password\\\\\" { send \\\\\"2L2u519w\\\\@ommi\\\\r\\\\\"; exp_continue }\n eof { }\n timeout { exit 1 }\n}\n\" 2>/dev/null)",
"Bash(curl -s -X POST http://192.168.100.33:9000/api/auth \\\\\n -H 'Content-Type: application/json' \\\\\n -d '{\"username\":\"bestof\",\"password\":\"2L2u519wportainer\"}' | python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('jwt','ERROR'\\)[:40]\\)\")",
"Bash(curl -s http://192.168.100.33:3005/api/health | python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d\\)\")",
"Bash(curl -s https://rla.bolbol.tn/api/health 2>/dev/null | python3 -c \"import sys,json; print\\(json.load\\(sys.stdin\\)\\)\" || echo \"Public URL check failed \\(may need VPN\\)\")",
"Bash(ls -la \"/Volumes/web/rla/Fichiers-cibles/\" && file \"/Volumes/web/rla/Fichiers-cibles/\"*)",
"Bash(ls /Volumes/web/rla/Fichiers-cibles/ 2>/dev/null && ls /Volumes/web/rla/services/export-*.js 2>/dev/null)",
"Bash(cd /Volumes/web/rla/Fichiers-cibles && unzip -p \"Marches_RLA_2025_Zone_Sud_02_2026.xlsx\" xl/worksheets/sheet1.xml 2>/dev/null | python3 -c \"import sys; from xml.etree import ElementTree as ET; root=ET.parse\\(sys.stdin\\); [print\\(c.attrib\\) for c in root.findall\\('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}c'\\)[:30]]\" 2>/dev/null || echo \"fail\")",
"Bash(cd /Volumes/web/rla/Fichiers-cibles && unzip -l \"Marches_RLA_2025_Zone_Sud_02_2026.xlsx\" 2>/dev/null | head -20)",
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nns = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Marches_RLA_2025_Zone_Sud_02_2026.xlsx'\\) as z:\n strings = []\n with z.open\\('xl/sharedStrings.xml'\\) as f:\n tree = ET.parse\\(f\\)\n for si in tree.findall\\(f'{ns}si'\\):\n t = ''.join\\(e.text or '' for e in si.iter\\(f'{ns}t'\\)\\)\n strings.append\\(t\\)\n \n def get_row_values\\(row\\):\n vals = []\n for c in row:\n v = c.find\\(f'{ns}v'\\)\n if v is not None:\n if c.get\\('t'\\) == 's':\n vals.append\\(strings[int\\(v.text\\)]\\)\n else:\n vals.append\\(v.text\\)\n else:\n vals.append\\(''\\)\n return vals\n \n # Sheet 1 - first 5 rows\n print\\('=== Sheet 1: Situation des Marchés ==='\\)\n with z.open\\('xl/worksheets/sheet1.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for row in rows[:6]:\n print\\(get_row_values\\(row\\)\\)\n \n # Sheet 2 - first 5 rows\n print\\('\\\\n=== Sheet 2: Estimation Évolution ==='\\)\n with z.open\\('xl/worksheets/sheet2.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for row in rows[:6]:\n print\\(get_row_values\\(row\\)\\)\nEOF)",
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nns = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Marches_RLA_2025_Zone_Sud_02_2026.xlsx'\\) as z:\n strings = []\n with z.open\\('xl/sharedStrings.xml'\\) as f:\n tree = ET.parse\\(f\\)\n for si in tree.findall\\(f'{ns}si'\\):\n t = ''.join\\(e.text or '' for e in si.iter\\(f'{ns}t'\\)\\)\n strings.append\\(t\\)\n \n def get_row_values\\(row\\):\n vals = []\n for c in row:\n v = c.find\\(f'{ns}v'\\)\n if v is not None:\n if c.get\\('t'\\) == 's':\n vals.append\\(strings[int\\(v.text\\)]\\)\n else:\n vals.append\\(v.text\\)\n else:\n vals.append\\(''\\)\n return vals\n \n # Sheet 1 - all non-empty rows\n print\\('=== Sheet 1: Situation des Marchés ==='\\)\n with z.open\\('xl/worksheets/sheet1.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for i, row in enumerate\\(rows[:60]\\):\n vals = get_row_values\\(row\\)\n non_empty = [v for v in vals if v]\n if non_empty:\n print\\(f\"Row {i+1}: {non_empty}\"\\)\nEOF)",
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nns = '{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Marches_RLA_2025_Zone_Sud_02_2026.xlsx'\\) as z:\n strings = []\n with z.open\\('xl/sharedStrings.xml'\\) as f:\n tree = ET.parse\\(f\\)\n for si in tree.findall\\(f'{ns}si'\\):\n t = ''.join\\(e.text or '' for e in si.iter\\(f'{ns}t'\\)\\)\n strings.append\\(t\\)\n \n def get_row_values\\(row\\):\n vals = []\n for c in row:\n v = c.find\\(f'{ns}v'\\)\n if v is not None:\n if c.get\\('t'\\) == 's':\n vals.append\\(strings[int\\(v.text\\)]\\)\n else:\n vals.append\\(v.text\\)\n else:\n vals.append\\(''\\)\n return vals\n \n # Sheet 2\n print\\('=== Sheet 2: Estimation Évolution ==='\\)\n with z.open\\('xl/worksheets/sheet2.xml'\\) as f:\n tree = ET.parse\\(f\\)\n rows = tree.findall\\(f'.//{ns}row'\\)\n for i, row in enumerate\\(rows[:40]\\):\n vals = get_row_values\\(row\\)\n non_empty = [v for v in vals if v]\n if non_empty:\n print\\(f\"Row {i+1}: {non_empty}\"\\)\nEOF)",
"Bash(python3 << 'EOF'\nimport zipfile, xml.etree.ElementTree as ET\n\nwith zipfile.ZipFile\\('/Volumes/web/rla/Fichiers-cibles/Rapport_RLA_2025_Zone_Sud_02_2026.docx'\\) as z:\n print\\(\"Files:\", z.namelist\\(\\)[:15]\\)\n \n with z.open\\('word/document.xml'\\) as f:\n content = f.read\\(\\).decode\\('utf-8'\\)\n # Extract text paragraphs\n tree = ET.fromstring\\(content\\)\n ns = '{http://schemas.openxmlformats.org/wordprocessingml/2006/main}'\n texts = []\n for p in tree.findall\\(f'.//{ns}p'\\)[:50]:\n text = ''.join\\(r.text or '' for r in p.findall\\(f'.//{ns}t'\\)\\)\n if text.strip\\(\\):\n texts.append\\(text.strip\\(\\)\\)\n for t in texts[:40]:\n print\\(t\\)\nEOF)",
"Bash(git commit:*)",
"Bash(git tag:*)",
"Bash(git push:*)",
"Bash(curl -s -X POST \"http://192.168.100.33:9000/api/auth\" \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"username\":\"bestof\",\"password\":\"2L2u519wportainer\"}' | python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('jwt','ERROR:',d\\)\\)\")",
"Bash(python3 -m json.tool)",
"Bash(git remote:*)",
"Bash(git add *)",
"Bash(git fetch *)",
"Bash(python3 -c \"import sys,json; commits=json.load\\(sys.stdin\\); [print\\(c['sha'][:10], c['commit']['message'][:70]\\) for c in commits]\")",
"Bash(python3 -c \"import sys,json; data=json.load\\(sys.stdin\\); [print\\(b['name'], b['commit']['id'][:10]\\) for b in data]\")",
"Bash(python3 -c \"import sys,json; commits=json.load\\(sys.stdin\\); [print\\(c['sha'][:10], c['commit']['message'][:80]\\) for c in commits]\")",
"Bash(curl -s -X PATCH -H 'Authorization: token 0aad77091496c9fcfab53dc2d8be79a52c781d84' -H 'Content-Type: application/json' -d '{\"sha\":\"4168f74ded2c1b79a52f2a8b0e0c9e0c7e0a2b3c\",\"force\":true}' https://gitea.bolbol.tn/api/v1/repos/bolbol/reglement-definitif/branches/main)",
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('name','?'\\), d.get\\('commit',{}\\).get\\('id','?'\\)[:10] if 'commit' in d else d\\)\")",
"Bash(curl *)",
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d['commit']['id'][:10], d['commit']['message'][:70]\\)\")",
"Bash(python3 -c \"import sys,json; d=json.load\\(sys.stdin\\); print\\(d.get\\('jwt','ERREUR: ' + str\\(d\\)\\)\\)\")",
"Bash(env)",
"Bash(ssh *)",
"Bash(sshpass *)",
"Bash(brew install *)",
"Bash(expect -v)",
"Bash(expect *)",
"Bash(mount)",
"Bash(node -e \"const b=require\\('bcryptjs'||'bcrypt'\\);b.compare\\('rla2025','\\\\$2a\\\\$10\\\\$4nqlMxOSsDV99mJHvVQJzubVLCArz6fnkQ0RSTD9p1CwdGxwibHdq'\\).then\\(r=>console.log\\('rla2025 :',r\\)\\)\")",
"Bash(node -e \"require\\('bcryptjs'\\).compare\\('rla2025','\\\\$2a\\\\$10\\\\$4nqlMxOSsDV99mJHvVQJzubVLCArz6fnkQ0RSTD9p1CwdGxwibHdq'\\).then\\(r=>console.log\\('rla2025 :',r\\)\\)\")",
"Bash(python3 -c ' *)",
"Bash(python3 -)",
"Bash(pip3 install *)",
"Bash(python3 -c \" from docx import Document from docx.shared import Pt, RGBColor, Cm import json doc = Document\\('Fichiers-cibles/Rapport_RLA_2025_Zone_Sud_03_2026.docx'\\) print\\('=== SECTIONS ==='\\) for i, section in enumerate\\(doc.sections\\): print\\(f'Section {i}: page_w={section.page_width.cm:.1f}cm page_h={section.page_height.cm:.1f}cm margin_top={section.top_margin.cm:.1f}cm'\\) print\\(\\) print\\('=== PARAGRAPHS & TABLES ==='\\) from docx.oxml.ns import qn body = doc.element.body def rgb_from_hex\\(hex_str\\): if not hex_str: return None h = hex_str.lstrip\\('#'\\) if len\\(h\\) == 6: return tuple\\(int\\(h[i:i+2],16\\) for i in \\(0,2,4\\)\\) return None def get_cell_bg\\(cell\\): shd = cell._tc.find\\(qn\\('w:tcPr'\\)\\) if shd is None: return None s = cell._tc.find\\('.//' + qn\\('w:shd'\\)\\) if s is not None: return s.get\\(qn\\('w:fill'\\)\\) return None def get_para_bg\\(para\\): pPr = para._p.find\\(qn\\('w:pPr'\\)\\) if pPr is None: return None shd = pPr.find\\(qn\\('w:shd'\\)\\) if shd is not None: return shd.get\\(qn\\('w:fill'\\)\\) return None items = [] for child in body: tag = child.tag.split\\('}'\\)[-1] if '}' in child.tag else child.tag if tag == 'p': from docx.text.paragraph import Paragraph para = Paragraph\\(child, doc\\) text = para.text.strip\\(\\) bg = get_para_bg\\(para\\) style = para.style.name if para.style else '' if text or bg: font_sizes = [] font_bolds = [] font_colors = [] for run in para.runs: if run.font.size: font_sizes.append\\(run.font.size.pt\\) font_bolds.append\\(run.font.bold\\) if run.font.color and run.font.color.type is not None: try: font_colors.append\\(str\\(run.font.color.rgb\\)\\) except: pass items.append\\({ 'type': 'para', 'style': style, 'text': text[:100], 'bg': bg, 'sizes': font_sizes[:3], 'bolds': font_bolds[:3], 'colors': font_colors[:3], }\\) elif tag == 'tbl': from docx.table import Table tbl = Table\\(child, doc\\) rows_count = len\\(tbl.rows\\) cols_count = len\\(tbl.columns\\) first_row = [] first_row_bg = [] for cell in tbl.rows[0].cells: first_row.append\\(cell.text.strip\\(\\)[:40]\\) first_row_bg.append\\(get_cell_bg\\(cell\\)\\) items.append\\({ 'type': 'table', 'rows': rows_count, 'cols': cols_count, 'header': first_row, 'header_bg': first_row_bg, }\\) for item in items: print\\(json.dumps\\(item, ensure_ascii=False\\)\\) \")",
"Bash(python3 -c \" import fitz doc = fitz.open\\('Fichiers-cibles/Marches_RLA_Marchés_En_Service___Raccordement___Medenine__Sfax_2026-03-12.pdf'\\) print\\(f'Pages: {doc.page_count}'\\) for i, page in enumerate\\(doc\\): print\\(f'--- Page {i+1} ---'\\) blocks = page.get_text\\('dict'\\) colors_seen = set\\(\\) for block in blocks['blocks']: if block.get\\('type'\\) == 0: for line in block['lines']: for span in line['spans']: c = span.get\\('color', 0\\) r = \\(c >> 16\\) & 0xFF g = \\(c >> 8\\) & 0xFF b = c & 0xFF hex_c = f'#{r:02X}{g:02X}{b:02X}' if hex_c != '#000000' and hex_c != '#FFFFFF': colors_seen.add\\(hex_c\\) txt = span['text'].strip\\(\\) if txt: print\\(f' size={span[\\\\\"size\\\\\"]:.1f} color={hex_c} font={span[\\\\\"font\\\\\"][:30]} text={txt[:60]}'\\) print\\(f' Colors used: {colors_seen}'\\) if i >= 1: break \")",
"Bash(python3 *)",
"Bash(node *)",
"Read(//Users/nabil.derouiche/.ssh/**)",
"Bash(ssh-keyscan -p 22222 192.168.100.33)",
"Bash(/usr/local/bin/brew install *)",
"Bash(/opt/homebrew/bin/brew install *)"
]
}
}

BIN
Thumbs.db Normal file

Binary file not shown.

View File

@ -109,8 +109,8 @@
.app-layout{display:flex;min-height:calc(100vh - var(--header-h));}
/* ── SIDEBAR ── */
.sidebar{width:var(--sidebar-w);flex-shrink:0;background:var(--sidebar-bg);border-right:1px solid var(--sidebar-border);position:sticky;top:var(--topbar-h);height:calc(100vh - var(--topbar-h));align-self:flex-start;overflow-y:auto;transition:margin-left var(--transition),width var(--transition);display:flex;flex-direction:column;}
.sidebar.hidden{margin-left:calc(-1 * var(--sidebar-w));width:0;overflow:hidden;}
.sidebar{width:var(--sidebar-w);flex-shrink:0;background:var(--sidebar-bg);border-right:1px solid var(--sidebar-border);position:fixed;left:0;top:var(--topbar-h);height:calc(100vh - var(--topbar-h));overflow-y:auto;transform:translateX(0);transition:transform var(--transition);display:flex;flex-direction:column;z-index:90;}
.sidebar.hidden{transform:translateX(-100%);}
.sidebar-nav{padding:12px 8px;flex:1;display:flex;flex-direction:column;gap:2px;}
.sidebar-section-label{font-size:0.68em;font-weight:700;text-transform:uppercase;letter-spacing:0.8px;color:var(--text-muted);padding:8px 10px 4px;margin-top:8px;}
.sidebar-section-label:first-child{margin-top:0;}
@ -135,7 +135,7 @@
.nav-hidden{display:none !important;}
/* ── MAIN WRAPPER ── */
.main-wrapper{flex:1;min-width:0;transition:all var(--transition);}
.main-wrapper{flex:1;min-width:0;margin-left:var(--sidebar-w);transition:margin-left var(--transition);}
/* ── SLIDES ── */
.slides-container{max-width:1400px;margin:0 auto;padding:24px;min-height:75vh;}
@ -208,10 +208,10 @@
.table-header{background:var(--primary);padding:12px 18px;display:flex;justify-content:space-between;align-items:center;color:white;}
.table-header h3{font-size:0.9em;font-weight:600;display:flex;align-items:center;gap:8px;}
.table-header .badge{background:rgba(255,255,255,0.2);padding:2px 9px;border-radius:16px;font-size:0.78em;font-weight:600;}
.table-wrapper{overflow-x:auto;}
.table-wrapper{overflow-x:auto;overflow-y:auto;max-height:560px;}
table{width:100%;border-collapse:collapse;min-width:600px;}
th,td{padding:12px 16px;text-align:left;border-bottom:1px solid var(--border-color);vertical-align:middle;}
th{background:var(--table-header);font-weight:600;font-size:0.72em;text-transform:uppercase;letter-spacing:0.6px;color:var(--text-muted);cursor:pointer;user-select:none;white-space:nowrap;}
th{background:var(--table-header);font-weight:600;font-size:0.72em;text-transform:uppercase;letter-spacing:0.6px;color:var(--text-muted);cursor:pointer;user-select:none;white-space:nowrap;position:sticky;top:0;z-index:2;}
th:hover{color:var(--accent);}
th .sort-icon{margin-left:4px;opacity:0.35;font-size:0.85em;}
th.sort-asc .sort-icon,th.sort-desc .sort-icon{opacity:1;color:var(--accent);}
@ -393,7 +393,7 @@
.logo-section h1{font-size:0.95em;}
.slides-container{padding:12px;}
.kpi-grid{grid-template-columns:1fr 1fr;}
.sidebar{position:fixed;z-index:80;height:calc(100vh - 56px);}
.sidebar{top:56px;height:calc(100vh - 56px);}
.main-wrapper{margin-left:0 !important;}
}
</style>
@ -746,6 +746,10 @@
<i class="fas fa-search"></i>
<input class="search-input-full" type="text" id="searchPipeline" placeholder="Rechercher par N° AO, description, région..." oninput="filterPipeline()">
</div>
<div class="filters-bar">
<select class="filter-select" id="pipelineFilterRegion" onchange="filterPipeline()"><option value="">Toutes régions</option></select>
<select class="filter-select" id="pipelineFilterPhase" onchange="filterPipeline()"><option value="">Toutes phases</option></select>
</div>
<div class="table-container">
<div class="table-header" style="background:linear-gradient(90deg,#4F46E5,#6366F1);">
<h3><i class="fas fa-rocket"></i> AO en cours de lancement</h3>
@ -1388,6 +1392,18 @@ function renderPipeline() {
const total = pipelineData._total_estimation ?? 0;
document.getElementById('pipelineTotalEstimation').textContent =
total > 0 ? parseNum(total).toFixed(0).replace(/\B(?=(\d{3})+(?!\d))/g,' ') + ' DT' : '—';
// Peupler filtre regions
const regSel = document.getElementById('pipelineFilterRegion');
if (regSel) {
const regs = [...new Set(pipelineData.flatMap(r => (r._regions||[]).map(rg=>rg.name)))].sort();
regSel.innerHTML = '<option value="">Toutes r\u00e9gions</option>' + regs.map(r=>'<option>'+escapeHtml(r)+'</option>').join('');
}
// Peupler filtre phases
const phSel = document.getElementById('pipelineFilterPhase');
if (phSel) {
const phases = [...new Set(pipelineData.map(r => r._phase?.label).filter(Boolean))].sort();
phSel.innerHTML = '<option value="">Toutes phases</option>' + phases.map(p=>'<option>'+escapeHtml(p)+'</option>').join('');
}
_pipelineFiltered = [...pipelineData];
const sq = document.getElementById('searchPipeline');
if (sq) sq.value = '';
@ -1642,7 +1658,9 @@ function toggleSidebar() {
}
function applySidebarState() {
const sb = document.getElementById('appSidebar');
const mw = document.getElementById('mainWrapper');
if (sb) sb.classList.toggle('hidden', !_sidebarVisible);
if (mw) mw.style.marginLeft = _sidebarVisible ? 'var(--sidebar-w)' : '0';
}
/* ── ALERT TICKER ── */
@ -1673,13 +1691,18 @@ function updateTicker() {
/* ── PIPELINE SEARCH ── */
let _pipelineFiltered = [];
function filterPipeline() {
const q = (document.getElementById('searchPipeline')?.value || '').toLowerCase();
_pipelineFiltered = q
? pipelineData.filter(r => {
const text = `${r['num-ao']||''} ${r['Description du projet']||''} ${(r._regions||[]).map(rg=>rg.name).join(' ')}`.toLowerCase();
return text.includes(q);
})
: [...pipelineData];
const q = (document.getElementById('searchPipeline')?.value || '').toLowerCase();
const region = document.getElementById('pipelineFilterRegion')?.value || '';
const phase = document.getElementById('pipelineFilterPhase')?.value || '';
_pipelineFiltered = pipelineData.filter(r => {
if (q) {
const text = `${r['num-ao']||''} ${r['Description du projet']||''} ${(r._regions||[]).map(rg=>rg.name).join(' ')}`.toLowerCase();
if (!text.includes(q)) return false;
}
if (region && !(r._regions||[]).some(rg => rg.name === region)) return false;
if (phase && (r._phase?.label || '') !== phase) return false;
return true;
});
renderPipelineTable(_pipelineFiltered);
}
function renderPipelineTable(data) {

BIN
rla.zip Normal file

Binary file not shown.