| |
| rubrica_asst_lecco [2025/09/25 10:57] – creata neoadmin | rubrica_asst_lecco [2025/09/26 17:06] (versione attuale) – neoadmin |
|---|
| <html> | <html> |
| <head> | <head> |
| <meta charset="utf-8"> | <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Rubrica Contatti — v12 FULL (banner vivace + favicon)</title> | <title>Rubrica ASST Lecco — Modificabile</title> |
| <link rel="icon" href="favicon.ico" type="image/x-icon"> | <style> |
| <style> | body { |
| :root { | font-family: Arial, sans-serif; |
| --panel: #ffffff; | max-width: 1200px; |
| --panel-2: #f8fafc; | margin: 0 auto; |
| --border: #dcd7c9; | padding: 20px; |
| --text: #0f172a; | background-color: #f5ece1; |
| --muted: #475569; | } |
| --primary: #2563eb; | .header { text-align: center; margin-bottom: 30px; color: #2c5530; } |
| --primary-700: #1d4ed8; | .search-container { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; } |
| --primary-50: #eef2ff; | .search-box { width: 100%; padding: 12px; font-size: 16px; border: 2px solid #ddd; border-radius: 4px; box-sizing: border-box; margin-bottom: 10px; } |
| --danger: #dc2626; | .search-box:focus { outline: none; border-color: #4CAF50; } |
| --ok: #16a34a; | .button-container { display: flex; gap: 10px; flex-wrap: wrap; } |
| --shadow: 0 8px 24px rgba(15, 23, 42, 0.08); | .btn { background-color: #2c5530; color: white; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-size: 14px; } |
| --shadow-soft: 0 2px 10px rgba(15, 23, 42, 0.06); | .btn:hover { background-color: #1a3a1e; } |
| --radius: 14px; | .btn.secondary { background-color: #666; } |
| } | .btn.warn { background-color: #ff4444; } |
| body { | .btn.warn:hover { background-color: #cc0000; } |
| margin: 0; color: var(--text); | .results-container { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } |
| font-family: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; | .result-item { padding: 15px; border-bottom: 1px solid #eee; display: flex; justify-content: space-between; align-items: center; gap: 12px; } |
| line-height: 1.4; | .result-item:last-child { border-bottom: none; } |
| background: linear-gradient(180deg,#faf9f7 0%,#f3f6fb 100%); | .result-item:hover { background-color: #f9f9f9; } |
| } | .contact-info { flex-grow: 1; } |
| .container { max-width: 1200px; margin: 0 auto; padding: 0 16px; } | .contact-name { font-weight: bold; color: #333; margin-bottom: 4px; } |
| | .contact-details { color: #666; font-size: 14px; } |
| | .contact-phone { font-weight: bold; color: #2c5530; margin-right: 10px; } |
| | .no-results { text-align: center; padding: 40px; color: #666; font-style: italic; } |
| | .stats { text-align: center; margin-bottom: 20px; color: #666; } |
| | .add-form { display: none; margin-top: 15px; padding: 15px; border: 2px solid #2c5530; border-radius: 8px; background: #f9f9f9; } |
| | .add-form h3 { margin-top: 0; color: #2c5530; } |
| | .form-row { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; } |
| | .form-input { padding: 8px; border: 1px solid #ddd; border-radius: 4px; flex: 1; min-width: 150px; } |
| | .hidden { display: none; } |
| | /* Modal modifica */ |
| | .modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.35); display: none; align-items: center; justify-content: center; padding: 16px; } |
| | .modal { width: 100%; max-width: 640px; background: #fff; border-radius: 10px; box-shadow: 0 10px 30px rgba(0,0,0,0.25); overflow: hidden; } |
| | .modal header { background: #2c5530; color: #fff; padding: 14px 16px; font-weight: bold; } |
| | .modal .content { padding: 16px; } |
| | .modal .actions { display: flex; gap: 10px; padding: 16px; justify-content: flex-end; border-top: 1px solid #eee; } |
| | .row-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } |
| | @media (max-width: 640px){ .row-2col { grid-template-columns: 1fr; } } |
| | </style> |
| |
| /* Banner vivace */ | <style> |
| .banner-wrap { position: sticky; top: 0; z-index: 10; } | .top-banner{ |
| .banner { | position: sticky; top: 0; z-index: 1000; |
| position: relative; | margin: 0 0 24px 0; padding: 18px 20px; |
| border-bottom: 1px solid var(--border); | background: linear-gradient(135deg, #2c5530, #1a3a1e); |
| box-shadow: var(--shadow-soft); | color:#fff; |
| overflow: hidden; | border-bottom-left-radius:16px; border-bottom-right-radius:16px; |
| background: linear-gradient(90deg, #60a5fa, #2563eb); | box-shadow: 0 6px 18px rgba(0,0,0,0.15); |
| color: white; | } |
| } | .banner-inner{ max-width:1200px; margin:0 auto; display:flex; align-items:center; gap:14px; } |
| .banner-inner { display: grid; grid-template-columns: 56px 1fr auto; gap: 14px; align-items: center; padding: 16px 0; } | .app-icon{ width:44px; height:44px; display:grid; place-items:center; |
| .logo { width: 56px; height: 56px; border-radius: 14px; background: rgba(255,255,255,0.2); display:flex; align-items:center; justify-content:center; box-shadow: inset 0 0 6px rgba(255,255,255,0.4); } | background: rgba(255,255,255,.12); border:1px solid rgba(255,255,255,.2); border-radius:12px; flex:0 0 auto; } |
| .logo svg { width: 32px; height: 32px; color: white; } | .app-icon svg{ width:26px; height:26px; fill:#fff; } |
| h1 { margin: 0; font-size: 24px; font-weight: 800; letter-spacing: 0.2px; } | .app-title{ font-size: clamp(22px, 2.4vw, 28px); font-weight:800; letter-spacing:.2px; line-height:1.1;} |
| .subtitle { margin: 2px 0 0; color: #e0eaff; font-size: 14px; } | .subtitle{ opacity:.9; font-size:14px; line-height:1.2; } |
| .wave { position: absolute; inset: auto 0 0 0; height: 24px; } | |
| .wave svg { display: block; width: 100%; height: 100%; } | |
| |
| .toolbar { | .icon-btn{ |
| margin: 14px 0 0; | width:38px; height:38px; border:none; border-radius:10px; |
| display: grid; grid-template-columns: 1fr auto auto auto auto auto; gap: 10px; align-items: center; | display:grid; place-items:center; cursor:pointer; background:#f1f1f1; |
| } | transition: background .2s ease, transform .04s ease; |
| input[type="text"] { | } |
| width: 100%; padding: 12px 14px; border-radius: var(--radius); | .icon-btn:hover{ background:#e6e6e6; } |
| border: 1px solid var(--border); background: var(--panel-2); color: var(--text); font-size: 15px; | .icon-btn:active{ transform: translateY(1px); } |
| } | .icon-btn svg{ width:18px; height:18px; } |
| input[type="text"]:focus { border-color: var(--primary); box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.15); background: #fff; } | .icon-btn.edit{ background:#e9f6ec; } |
| button { | .icon-btn.edit:hover{ background:#d9efdf; } |
| border: 1px solid var(--border); background: white; | .icon-btn.delete{ background:#fdeaea; } |
| color: var(--text); padding: 10px 14px; border-radius: var(--radius); cursor: pointer; | .icon-btn.delete:hover{ background:#f9dede; } |
| box-shadow: var(--shadow-soft); | .icon-btn.delete svg{ fill:#b42323; } |
| } | .icon-btn.edit svg{ fill:#1f7a36; } |
| button.primary { background: #2563eb; color: white; border-color: #1e40af; } | </style> |
| button.danger { color: #b91c1c; border-color: #fecaca; background: linear-gradient(180deg, #fff, #fff5f5); } | |
| button.ok { color: #166534; border-color: #bbf7d0; background: linear-gradient(180deg, #fff, #f0fff7); } | |
| .pill { padding: 6px 10px; border-radius: 999px; border: 1px solid var(--border); background: #fff; color: var(--muted); font-size: 12px; box-shadow: var(--shadow-soft); } | |
| |
| main { padding: 18px 0 24px; } | </head> |
| .card { | <body> |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: var(--radius); | |
| box-shadow: var(--shadow); | |
| padding: 14px; | |
| } | |
| details { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 12px; margin: 14px 0; box-shadow: var(--shadow-soft); } | |
| summary { cursor: pointer; font-weight: 600; color: var(--primary-700); } | |
| |
| table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); } | <div class="top-banner" role="banner"> |
| thead th { background: var(--primary-50); text-align: left; padding: 12px; border-bottom: 1px solid var(--border); white-space: nowrap; color: var(--primary-700); font-weight: 700; } | <div class="banner-inner"> |
| tbody td { padding: 10px 12px; border-bottom: 1px solid var(--border); vertical-align: middle; } | <div class="app-icon" aria-hidden="true"> |
| tbody tr:nth-child(odd) { background: #ffffff; } | <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.62 10.79a15.05 15.05 0 006.59 6.59l2.2-2.2a1 1 0 011.02-.24c1.12.37 2.33.57 3.57.57a1 1 0 011 1V21a1 1 0 01-1 1C10.3 22 2 13.7 2 3a1 1 0 011-1h3.5a1 1 0 011 1c0 1.24.2 2.45.57 3.57a1 1 0 01-.24 1.02l-2.2 2.2z"></path></svg> |
| tbody tr:nth-child(even) { background: #f9fbff; } | </div> |
| tbody tr:hover { background: #eef5ff; } | <div> |
| | <div class="app-title">Rubrica ASST Lecco</div> |
| | <div class="subtitle">Cerca, aggiungi, elimina e <strong>modifica</strong> i contatti</div> |
| | </div> |
| | </div> |
| | </div> |
| |
| .actions { display: flex; gap: 8px; justify-content: flex-end; } | <div class="header"> |
| | <h1>☎ Rubrica ASST Lecco</h1> |
| | <p>Cerca, aggiungi, elimina e ORA modifica i contatti</p> |
| | </div> |
| |
| .pagination { display: flex; gap: 10px; justify-content: center; align-items: center; margin: 16px 0 40px; } | <div class="search-container"> |
| .hint { color: var(--muted); font-size: 12px; margin-top: 6px; } | <input type="text" id="searchBox" class="search-box" placeholder="Cerca per nome, cognome, ruolo, reparto o numero..." autofocus> |
| | <div class="button-container"> |
| | <button class="btn" onclick="showAddForm()">Aggiungi Contatto</button> |
| | <button class="btn" onclick="resetContacts()">Ripristina Cancellati</button> |
| | <button class="btn" onclick="exportData()">Esporta Database</button> |
| | <button class="btn" onclick="importData()">Carica Database</button> |
| | </div> |
| |
| /* Mobile */ | <div id="addContactForm" class="add-form"> |
| @media (max-width: 640px) { | <h3>Aggiungi Nuovo Contatto</h3> |
| .banner-inner { grid-template-columns: 40px 1fr; gap: 10px; } | <div class="form-row"> |
| .toolbar { grid-template-columns: 1fr; gap: 8px; } | <input type="text" id="newName" placeholder="Nome" class="form-input"> |
| #searchAll { font-size: 16px; padding: 14px; } | <input type="text" id="newSurname" placeholder="Cognome" class="form-input"> |
| .toolbar button, .toolbar .pill { width: 100%; } | <input type="text" id="newPhone" placeholder="Telefono" class="form-input"> |
| .form-grid { grid-template-columns: 1fr 1fr; } | </div> |
| thead th, tbody td { padding: 10px; } | <div class="form-row"> |
| } | <input type="text" id="newRole" placeholder="Ruolo" class="form-input"> |
| </style> | <input type="text" id="newDepartment" placeholder="Reparto" class="form-input"> |
| </head> | </div> |
| <body> | <div class="form-row"> |
| <div class="banner-wrap"> | <button onclick="addContact()" class="btn">Salva</button> |
| <div class="banner"> | <button onclick="hideAddForm()" class="btn secondary">Annulla</button> |
| <div class="container banner-inner"> | </div> |
| <div class="logo" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M7 4h10a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z"/> | |
| <path d="M16 2v4M8 2v4M7 10h10M9.5 14.5a2.5 2.5 0 1 0 5 0a2.5 2.5 0 0 0-5 0Z"/> | |
| </svg> | |
| </div> | |
| <div> | |
| <h1>Rubrica Contatti</h1> | |
| <div class="subtitle">Ricerca su qualsiasi campo · +2000 contatti · Aggiungi / Modifica / Elimina</div> | |
| </div> | </div> |
| <span class="pill" id="countBadgeTop">0 contatti</span> | |
| </div> | |
| <div class="wave" aria-hidden="true"> | |
| <svg viewBox="0 0 1440 80" preserveAspectRatio="none"> | |
| <path d="M0,40 C240,80 480,0 720,40 C960,80 1200,0 1440,40 L1440,80 L0,80 Z" fill="#bfdbfe"></path> | |
| </svg> | |
| </div> | |
| </div> | |
| </div> | |
| |
| <main class="container"> | <input type="file" id="fileInput" accept=".json" class="hidden"> |
| <div class="toolbar"> | |
| <input id="searchAll" type="text" placeholder="Cerca in qualsiasi campo… (maiuscole/accents ignorati)"> | |
| <button id="btnClear">Pulisci</button> | |
| <button id="btnExport">Esporta JSON</button> | |
| <button id="btnReset">Ripristina dati originali</button> | |
| <button id="btnCleanNow">Pulisci dati nel browser</button> | |
| <span class="pill" id="countBadge">0 contatti</span> | |
| </div> | </div> |
| |
| <details id="addPanel" class="card" style="margin-top:14px;"> | <div class="stats" id="statsContainer">Caricamento contatti...</div> |
| <summary>Nuovo contatto</summary> | |
| <div class="form-grid"> | |
| <input id="newName" type="text" placeholder="Nome"> | |
| <input id="newSurname" type="text" placeholder="Cognome"> | |
| <input id="newRole" type="text" placeholder="Ruolo / Email"> | |
| <input id="newPhone" type="text" placeholder="Telefono / Interno"> | |
| <input id="newDept" type="text" placeholder="Dipartimento"> | |
| </div> | |
| <div style="margin-top:10px; display:flex; gap:10px; flex-wrap: wrap;"> | |
| <button id="btnSaveNew" class="primary">Salva contatto</button> | |
| <button id="btnCancelNew">Annulla</button> | |
| </div> | |
| <div class="hint">Pulizia automatica: valori tipo “XXX”, “xx”, “x.x.x.” e simili vengono trasformati in campi vuoti al caricamento.</div> | |
| </details> | |
| |
| <div class="card"> | <div class="results-container"> |
| <table id="grid"> | <div id="resultsContainer"> |
| <thead> | <div class="no-results">Digita per iniziare la ricerca...</div> |
| <tr> | </div> |
| <th>#</th> | |
| <th>Nome</th> | |
| <th>Cognome</th> | |
| <th>Ruolo / Email</th> | |
| <th>Telefono</th> | |
| <th>Dipartimento</th> | |
| <th style="text-align:right;">Azioni</th> | |
| </tr> | |
| </thead> | |
| <tbody id="gridBody"></tbody> | |
| </table> | |
| </div> | </div> |
| |
| <div class="pagination"> | <!-- Modal Modifica Contatto --> |
| <button id="prevPage">←</button> | <div id="editModal" class="modal-backdrop" role="dialog" aria-modal="true" aria-hidden="true"> |
| <span id="pageInfo" class="pill">Pagina 1 / 1</span> | <div class="modal"> |
| <button id="nextPage">→</button> | <header>Modifica contatto</header> |
| <span style="margin-left:12px;"></span> | <div class="content"> |
| <label for="pageSize">per pagina:</label> | <div class="row-2col"> |
| <select id="pageSize"> | <input type="text" id="editName" class="form-input" placeholder="Nome"> |
| <option>25</option> | <input type="text" id="editSurname" class="form-input" placeholder="Cognome"> |
| <option selected>50</option> | </div> |
| <option>100</option> | <div class="row-2col" style="margin-top:10px;"> |
| <option>200</option> | <input type="text" id="editPhone" class="form-input" placeholder="Telefono"> |
| <option>500</option> | <input type="text" id="editRole" class="form-input" placeholder="Ruolo"> |
| </select> | </div> |
| | <div class="form-row" style="margin-top:10px;"> |
| | <input type="text" id="editDepartment" class="form-input" placeholder="Reparto"> |
| | </div> |
| | </div> |
| | <div class="actions"> |
| | <button class="btn secondary" onclick="closeEditModal()">Annulla (Esc)</button> |
| | <button class="btn" onclick="saveEdit()">Salva modifiche (Invio)</button> |
| | </div> |
| | </div> |
| </div> | </div> |
| </main> | |
| |
| <script> | <script> |
| // === Dataset incorporato === | // ---- Database iniziale (uguale all'originale) ---- |
| const INITIAL_DATA = [{"name": "XXX", "surname": "Crespi", "role": "c.crespi@asst-lecco.it", "phone": "2012", "department": "XXX"}, {"name": "XXX", "surname": "Cucuzza", "role": "s.cucuzza@asst-lecco.it", "phone": "2018", "department": "XXX"}, {"name": "XXX", "surname": "Puricelli", "role": "l.puricelli@asst-lecco.it", "phone": "2017", "department": "XXX"}, {"name": "XXX", "surname": "Scattaretica", "role": "l.scattaretica@asst-lecco.it", "phone": "2090", "department": "XXX"}, {"name": "XXX", "surname": "Grossi", "role": "am.grossi@asst-lecco.it", "phone": "2386", "department": "XXX"}, {"name": "XXX", "surname": "Caligiuri", "role": "m.caligiuri@asst-lecco.it", "phone": "2397", "department": "XXX"}, {"name": "XXX", "surname": "Rusconi", "role": "k.rusconi@asst-lecco.it", "phone": "4056", "department": "XXX"}, {"name": "XXX", "surname": "Meoli", "role": "s.meoli@asst-lecco.it", "phone": "2060", "department": "XXX"}, {"name": "Aula", "surname": "Magna", "role": "XXX", "phone": "2716", "department": "XXX"}, {"name": "XXX", "surname": "XXX", "role": "Saletta riunioni piano +2 Blu", "phone": "2002", "department": "XXX"}, {"name": "XXX", "surname": "XXX", "role": "Saletta Conferenze piano 0 (Ingresso) Bianca", "phone": "3669", "department": "XXX"}, {"name": "XXX", "surname": "XXX", "role": "Saletta riunioni piano 0 (Lato mensa) Gialla", "phone": "4031", "department": "XXX"}, {"name": "XXX", "surname": "Andreotti", "role": "e.andreotti@asst-lecco.it", "phone": "2190", "department": "XXX"}, {"name": "XXX", "surname": "Mastronardi", "role": "a.mastronardi@asst-lecco.it", "phone": "2199", "department": "XXX"}, {"name": "XXX", "surname": "Dell’oro", "role": "f.delloro@asst-lecco.it", "phone": "2409", "department": "XXX"}, {"name": "XXX", "surname": "Cesana", "role": "fe.cesana@asst-lecco.it", "phone": "4062", "department": "XXX"}, {"name": "XXX", "surname": "Sacco", "role": "v.sacco@asst-lecco.it", "phone": "4499", "department": "XXX"}, {"name": "XXX", "surname": "Marasca", "role": "s.marasca@asst-lecco.it", "phone": "2117", "department": "XXX"}, {"name": "Pasquale", "surname": "Giofrè", "role": "p.giofre@asst-lecco.it", "phone": "2407", "department": "XXX"}, {"name": "Sportello", "surname": "Accettazione", "role": "s.fiore@asst-lecco.it", "phone": "3143", "department": "XXX"}, {"name": "Sportello", "surname": "Accettazione", "role": "XXX", "phone": "2194", "department": "XXX"}, {"name": "Prenotazione", "surname": "Ricoveri", "role": "XXX", "phone": "2302", "department": "XXX"}, {"name": "Gestione", "surname": "FAX", "role": "XXX", "phone": "4046", "department": "XXX"}, {"name": "XXX", "surname": "Balduani", "role": "s.balduani@asst-lecco.it", "phone": "4010", "department": "XXX"}, {"name": "XXX", "surname": "Piazzoli", "role": "b.piazzoli@asst-lecco.it", "phone": "2453", "department": "XXX"}, {"name": "PUNTO", "surname": "DMI", "role": "XXX", "phone": "3725", "department": "XXX"}, {"name": "XXX", "surname": "XXX", "role": "CASSA 3750", "phone": "2195", "department": "XXX"}, {"name": "XXX", "surname": "Mambretti", "role": "XXX", "phone": "4012", "department": "c.mambretti@asst-lecco.it"}, |