Hem utilizat Python perquè es més fácil amb flask per fer la web dinàmica
- Guardar informació: Com guardar noms d’usuaris guardats a la BBDD de Mysql.
- Registratse: És fàcil afegir noves funcions sense haver de tornar a començar de zero.
Aquest es el codig que hem utilitzat per fer el formulari:
from http.server import HTTPServer, BaseHTTPRequestHandler
import sqlite3
import json
from datetime import datetime
# — CONFIGURACIÓ DE LA BASE DE DADES —
def inicialitzar_db():
conexion = sqlite3.connect(“usuarios.db”)
cursor = conexion.cursor()
cursor.execute(“””
CREATE TABLE IF NOT EXISTS usuarios (
id INTEGER PRIMARY KEY AUTOINCREMENT,
nombre TEXT NOT NULL,
apellido TEXT NOT NULL,
email TEXT NOT NULL,
instituto TEXT,
hora TEXT,
acompanantes INTEGER,
fecha_registro TEXT,
UNIQUE(nombre, apellido)
)
“””)
conexion.commit()
conexion.close()
def obtenir_estat_cupos():
franges = [“16:00”, “16:15”, “16:30”, “16:45”, “17:00”, “17:15”, “17:30”, “17:45”, “18:00”, “18:15”, “18:30”, “18:45”]
disponibles = {}
conexion = sqlite3.connect(“usuarios.db”)
cursor = conexion.cursor()
for f in franges:
hora_pare = f.split(“:”)[0]
cursor.execute(“SELECT SUM(acompanantes) FROM usuarios WHERE hora LIKE ?”, (f”{hora_pare}:%”,))
total_hora = cursor.fetchone()[0] or 0
cupos_restants = 20 – total_hora
disponibles[f] = {“lliures”: cupos_restants, “rang”: f”{hora_pare}:00″}
conexion.close()
return disponibles
class ServidorSMX(BaseHTTPRequestHandler):
def do_GET(self):
if self.path == “/admin”:
# — SISTEMA DE CONTRASEÑA —
auth_header = self.headers.get(‘Authorization’)
# El usuario es ‘admin’ y la contraseña es ‘ICastellbisbal’
# En Basic Auth, esto se envía codificado en Base64 como ‘admin:ICastellbisbal’
# Cadena Base64 resultante: YWRtaW46S thereICastellbisbal
password_correcta = “ICastellbisbal”
if auth_header is None or auth_header != password_correcta:
self.send_response(401)
self.send_header(‘WWW-Authenticate’, ‘Basic realm=”Acces Administrador”‘)
self.send_header(‘Content-type’, ‘text/html’)
self.end_headers()
self.wfile.write(b”No autoritzat”)
return
# — FIN SISTEMA DE CONTRASEÑA —
self.send_response(200)
self.send_header(“Content-type”, “text/html; charset=utf-8”)
self.end_headers()
conexion = sqlite3.connect(“usuarios.db”)
cursor = conexion.cursor()
cursor.execute(“SELECT nombre, apellido, instituto, email, hora, acompanantes FROM usuarios ORDER BY hora ASC”)
files = cursor.fetchall()
conexion.close()
total_inscrits = sum(f[5] for f in files)
tabla_html = “”.join([f”<tr><td>{f[0].capitalize()}</td><td>{f[1].capitalize()}</td><td>{f[2] or ‘-‘}</td><td>{f[3]}</td><td>{f[4]}</td><td>{f[5]}</td></tr>” for f in files])
html_admin = f”””<html><head><meta charset=’UTF-8′><title>Panell d’Administració</title>
<style>body{{font-family:’Segoe UI’,sans-serif;background:#0f172a;color:white;padding:40px;}}
table{{width:100%;border-collapse:collapse;background:#1e293b;border-radius:12px;overflow:hidden;}}
th,td{{padding:15px;text-align:left;border-bottom:1px solid #334155;}}
th{{background:#4f46e5;color:white;text-transform:uppercase;font-size:12px;letter-spacing:1px;}}
tr:hover{{background:#334155;}}</style></head>
<body><h1>Llistat de Registres</h1><p>Total persones inscrites: {total_inscrits}</p>
<table><thead><tr><th>Nom</th><th>Cognom</th><th>Institut</th><th>Email</th><th>Torn</th><th>Persones</th></tr></thead>
<tbody>{tabla_html}</tbody></table></body></html>”””
self.wfile.write(html_admin.encode(“utf-8”))
return
# Resto del código para la página principal…
self.send_response(200)
# … (aquí sigue el código del formulario que ya tenías)
self.send_response(200)
self.send_header(“Content-type”, “text/html; charset=utf-8”)
self.end_headers()
cupos = obtenir_estat_cupos()
grid_html = “”
for h, i in cupos.items():
clase = “disponible” if i[‘lliures’] > 0 else “esgotat”
disabled = “disabled” if i[‘lliures’] <= 0 else “”
grid_html += f”””
<label class=”radio-card {clase}”>
<input type=”radio” name=”hora” value=”{h}” required {disabled}>
<div class=”card-content”>
<span class=”franja-tag”>Franja {i[‘rang’]}h</span>
<span class=”hora-txt”>{h}</span>
<span class=”libres-txt”>{i[‘lliures’]} lliures</span>
</div>
</label>
“””
html = f”””
<!DOCTYPE html>
<html lang=”ca”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>Registre de l’Horari</title>
<link href=”https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;800&display=swap” rel=”stylesheet”>
<style>
:root {{ –primary: #6366f1; –primary-hover: #4f46e5; –bg: #0f172a; }}
body, html {{ height: 100%; margin: 0; font-family: ‘Plus Jakarta Sans’, sans-serif; background: var(–bg); color: #1e293b; }}
.bg-image {{ position: fixed; width: 100%; height: 100%; z-index: -1; background: radial-gradient(circle at top right, #4f46e5, transparent), url(‘https://images.unsplash.com/photo-1451187580459-43490279c0fa?q=80&w=2072&auto=format&fit=crop’); background-size: cover; background-position: center; filter: brightness(0.6); }}
.content-wrapper {{ display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 20px; box-sizing: border-box; }}
.tarjeta {{ background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(12px); padding: 40px; border-radius: 24px; width: 100%; max-width: 550px; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5); border: 1px solid rgba(255,255,255,0.3); }}
h2 {{ text-align: center; font-weight: 800; font-size: 28px; margin-bottom: 8px; color: #0f172a; }}
p.subtitle {{ text-align: center; color: #64748b; margin-bottom: 30px; font-size: 14px; }}
.form-group {{ margin-bottom: 16px; }}
.form-label {{ display: block; font-weight: 600; font-size: 13px; margin-bottom: 6px; color: #334155; }}
input {{ width: 100%; padding: 12px 16px; border-radius: 12px; border: 1px solid #e2e8f0; background: white; box-sizing: border-box; transition: 0.2s; font-size: 15px; }}
input:focus {{ outline: none; border-color: var(–primary); box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.1); }}
.grid-horas {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(105px, 1fr)); gap: 12px; margin-top: 10px; }}
.radio-card {{ cursor: pointer; position: relative; }}
.radio-card input {{ position: absolute; opacity: 0; }}
.card-content {{ border: 2px solid #f1f5f9; border-radius: 16px; padding: 12px; text-align: center; background: white; transition: all 0.3s ease; }}
.franja-tag {{ font-size: 10px; color: #94a3b8; font-weight: 600; display: block; margin-bottom: 4px; }}
.hora-txt {{ font-weight: 800; font-size: 16px; color: #1e293b; display: block; }}
.libres-txt {{ font-size: 11px; color: #10b981; font-weight: 700; margin-top: 4px; display: block; }}
.radio-card:hover:not(.esgotat) .card-content {{ border-color: #cbd5e1; transform: translateY(-2px); }}
.radio-card input:checked + .card-content {{ border-color: var(–primary); background: #f5f3ff; box-shadow: 0 10px 15px -3px rgba(99, 102, 241, 0.2); }}
.esgotat {{ opacity: 0.5; cursor: not-allowed; filter: grayscale(1); }}
.btn {{ width: 100%; padding: 16px; background: var(–primary); color: white; border: none; border-radius: 16px; margin-top: 24px; cursor: pointer; font-weight: 700; font-size: 16px; transition: 0.3s; box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.3); }}
.btn:hover {{ background: var(–primary-hover); transform: translateY(-1px); }}
.btn-alt {{ background: #f1f5f9; color: #475569; box-shadow: none; margin-top: 12px; }}
.resum-item {{ background: #f8fafc; padding: 14px; border-radius: 12px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; border: 1px solid #e2e8f0; }}
.resum-label {{ color: #64748b; font-size: 13px; font-weight: 600; }}
.resum-val {{ color: #0f172a; font-weight: 700; }}
.hidden {{ display: none; }}
</style>
</head>
<body>
<div class=”bg-image”></div>
<div class=”content-wrapper”>
<div class=”tarjeta” id=”box-form”>
<h2>Registre de l’Horari</h2>
<p class=”subtitle”>Reserva la teva plaça</p>
<form id=”mainForm” onsubmit=”mostrarResum(event)”>
<div style=”display: flex; gap: 15px;”>
<div class=”form-group” style=”flex:1;”>
<label class=”form-label”>Nom</label>
<input type=”text” id=”nom” placeholder=”Ex. Joan” required>
</div>
<div class=”form-group” style=”flex:1;”>
<label class=”form-label”>Cognom</label>
<input type=”text” id=”cog” placeholder=”Ex. Vila” required>
</div>
</div>
<div class=”form-group”>
<label class=”form-label”>Correu Electrònic</label>
<input type=”email” id=”email” placeholder=”correu@exemple.com” required>
</div>
<div class=”form-group”>
<label class=”form-label”>Institut / Centre Educatiu</label>
<input type=”text” id=”insti” placeholder=”Nom del teu centre (opcional)”>
</div>
<div class=”form-group”>
<label class=”form-label”>Nombre de persones (Màx. 4)</label>
<input type=”number” id=”gent” min=”1″ max=”4″ value=”1″ required>
</div>
<label class=”form-label”>Selecciona el teu torn</label>
<div class=”grid-horas”>{grid_html}</div>
<button type=”submit” class=”btn”>CONTINUAR</button>
</form>
</div>
<div class=”tarjeta hidden” id=”box-confirm”>
<h2>Verifica la reserva</h2>
<p class=”subtitle”>Comprova que les dades siguin correctes</p>
<div id=”resum-content” style=”margin-top: 20px;”></div>
<button onclick=”enviarDades()” class=”btn”>CONFIRMAR REGISTRE</button>
<button onclick=”tornarEnrera()” class=”btn btn-alt”>MODIFICAR DADES</button>
</div>
</div>
<script>
let dadesTemporals = {{}};
function mostrarResum(e) {{
e.preventDefault();
const horaSel = document.querySelector(‘input[name=”hora”]:checked’);
const numGent = parseInt(document.getElementById(‘gent’).value);
if(numGent > 4) {{ alert(“El màxim permès són 4 persones.”); return; }}
if(!horaSel) {{ alert(“Si us plau, selecciona un torn.”); return; }}
dadesTemporals = {{
nom: document.getElementById(‘nom’).value,
cog: document.getElementById(‘cog’).value,
email: document.getElementById(‘email’).value,
insti: document.getElementById(‘insti’).value || “No indicat”,
gent: numGent,
hora: horaSel.value
}};
document.getElementById(‘resum-content’).innerHTML = `
<div class=”resum-item”><span class=”resum-label”>Nom complet</span><span class=”resum-val”>${{dadesTemporals.nom}} ${{dadesTemporals.cog}}</span></div>
<div class=”resum-item”><span class=”resum-label”>Institut</span><span class=”resum-val”>${{dadesTemporals.insti}}</span></div>
<div class=”resum-item”><span class=”resum-label”>Assistents</span><span class=”resum-val”>${{dadesTemporals.gent}}</span></div>
<div class=”resum-item” style=”border: 2px solid var(–primary);”><span class=”resum-label”>Hora triada</span><span class=”resum-val” style=”color:var(–primary);”>${{dadesTemporals.hora}}h</span></div>
`;
document.getElementById(‘box-form’).classList.add(‘hidden’);
document.getElementById(‘box-confirm’).classList.remove(‘hidden’);
}}
function tornarEnrera() {{
document.getElementById(‘box-confirm’).classList.add(‘hidden’);
document.getElementById(‘box-form’).classList.remove(‘hidden’);
}}
async function enviarDades() {{
try {{
const r = await fetch(‘/’, {{method:’POST’, body:JSON.stringify(dadesTemporals)}});
const res = await r.json();
if(res.ok) {{
alert(‘Registre realitzat amb èxit! T’esperem.’);
location.reload();
}} else {{
alert(res.msg);
tornarEnrera();
}}
}} catch(e) {{
alert(“Error de connexió amb el servidor.”);
}}
}}
</script>
</body>
</html>
“””
self.wfile.write(html.encode(“utf-8”))
def do_POST(self):
content_length = int(self.headers[‘Content-Length’])
data = json.loads(self.rfile.read(content_length).decode(‘utf-8’))
# Validació de seguretat al servidor
if data[‘gent’] > 4:
resp = {“ok”: False, “msg”: “Error: El màxim de persones és 4.”}
else:
conexion = sqlite3.connect(“usuarios.db”)
cursor = conexion.cursor()
hora_pare = data[‘hora’].split(“:”)[0]
cursor.execute(“SELECT SUM(acompanantes) FROM usuarios WHERE hora LIKE ?”, (f”{hora_pare}:%”,))
actual = cursor.fetchone()[0] or 0
lliures = 20 – actual
if data[‘gent’] > lliures:
resp = {“ok”: False, “msg”: f”Ho sentim, només queden {max(0, lliures)} places lliures.”}
else:
try:
cursor.execute(“INSERT INTO usuarios (nombre, apellido, email, instituto, hora, acompanantes, fecha_registro) VALUES (?, ?, ?, ?, ?, ?, ?)”,
(data[‘nom’].lower(), data[‘cog’].lower(), data[‘email’], data[‘insti’], data[‘hora’], data[‘gent’], datetime.now().isoformat()))
conexion.commit()
resp = {“ok”: True}
except:
resp = {“ok”: False, “msg”: “Aquest nom i cognom ja estan registrats.”}
conexion.close()
self.send_response(200); self.send_header(‘Content-Type’, ‘application/json’); self.end_headers()
self.wfile.write(json.dumps(resp).encode(‘utf-8’))
if __name__ == “__main__”:
inicialitzar_db()
server = HTTPServer((“0.0.0.0”, 5000), ServidorSMX)
server.allow_reuse_address = True
print(“🚀 Servidor en català actiu a http://localhost:5000”)
server.serve_forever()
Basicament, son lineas per importar les llibreries de Mysql, també de tot el que es veu a la web: Nom, centre, hora… i pero últim, en que servidor anirá.
