diff --git a/idriz/apps/raspored/admin.py b/idriz/apps/raspored/admin.py index 111cc5a..3d8cabf 100644 --- a/idriz/apps/raspored/admin.py +++ b/idriz/apps/raspored/admin.py @@ -88,11 +88,27 @@ def dijete(ime): }) saved = store.set_child_schedule(ime, rows) flash(f"Raspored sačuvan: {saved} časova.") + elif action == "aktivnosti": + n = int(request.form.get("anrows", "0")) + rows = [] + for i in range(n): + if request.form.get(f"a{i}_del"): + continue + rows.append({ + "dan": request.form.get(f"a{i}_dan", ""), + "pocetak": request.form.get(f"a{i}_pocetak", ""), + "kraj": request.form.get(f"a{i}_kraj", ""), + "naziv": request.form.get(f"a{i}_naziv", ""), + }) + saved = store.set_activities(ime, rows) + flash(f"Aktivnosti sačuvane: {saved}.") return redirect(url_for("raspored.dijete", ime=ime)) grid = store.child_rows(ime) + [{} for _ in range(BLANK_ROWS)] + agrid = store.activity_rows(ime) + [{} for _ in range(BLANK_ROWS)] return render_template( "raspored_dijete.html", ime=ime, dijete=ch, grid=grid, nrows=len(grid), - dani=store.DANI, active_page="raspored", + agrid=agrid, anrows=len(agrid), + dani=store.DANI, svi_dani=store.SVI_DANI, active_page="raspored", ) diff --git a/idriz/apps/raspored/manifest.py b/idriz/apps/raspored/manifest.py index 0b3152f..737c985 100644 --- a/idriz/apps/raspored/manifest.py +++ b/idriz/apps/raspored/manifest.py @@ -14,8 +14,9 @@ log = logging.getLogger("idriz.raspored") INSTRUCTIONS = """RASPORED ČASOVA (alat: raspored_upit): odgovaraš o školskom rasporedu djece (koje časove imaju, kad počinju/završavaju, koja je smjena). Podržano je više djece. - Kad dijete/roditelj pita o rasporedu ("koje časove Ahmed ima danas?", "kad Hatidža danas počinje/završava školu?", "koja je smjena za Ahmeda sutra?"), pozovi raspored_upit. -- Argumenti: `dijete` (ime kako je izgovoreno), `kada` (danas/sutra/prekosutra/juce/prekjuce, naziv dana, ili datum kao "15.09." ili "2026-09-15"), i `sta`: "casovi" (koji časovi), "pocetak" (kad mora biti u školi), "kraj" (kad završava), "smjena". -- Ako nije rečeno šta tačno, koristi sta="casovi". +- Kad pita o OBAVEZAMA ("koje obaveze Ahmed ima danas/sutra/petkom?"), koristi sta="obaveze" — alat nabroji školu (od–do) i vannastavne aktivnosti (treninzi, mekteb...) tog dana. Za samo aktivnosti koristi sta="aktivnosti". +- Argumenti: `dijete` (ime kako je izgovoreno), `kada` (danas/sutra/prekosutra/juce/prekjuce, naziv dana i u obliku "petkom"/"subotom", ili datum kao "15.09." ili "2026-09-15"), i `sta`: casovi | pocetak | kraj | smjena | obaveze | aktivnosti. +- Ako nije rečeno šta tačno, koristi sta="casovi". Vannastavne aktivnosti nisu vezane za smjenu (fiksno vrijeme, sedmično, mogu biti i vikendom). - Alat vraća gotovu rečenicu na bosanskom — izgovori je prirodno. Ne izmišljaj časove ni vremena. - Ako alat kaže da dijete nije poznato ili da smjena nije postavljena, prenesi to.""" @@ -28,8 +29,8 @@ SCHEMA = { "properties": { "dijete": {"type": "string", "description": "Ime djeteta kako je izgovoreno."}, "kada": {"type": "string", "description": "danas, sutra, prekosutra, juce, prekjuce, naziv dana, ili datum (npr. '15.09.' ili '2026-09-15'). Prazno = danas."}, - "sta": {"type": "string", "enum": ["casovi", "pocetak", "kraj", "smjena"], - "description": "Šta se traži. Podrazumijevano 'casovi'."}, + "sta": {"type": "string", "enum": ["casovi", "pocetak", "kraj", "smjena", "obaveze", "aktivnosti"], + "description": "Šta se traži: 'casovi' (školski časovi), 'pocetak'/'kraj' škole, 'smjena', 'obaveze' (škola + vannastavne aktivnosti zajedno), 'aktivnosti' (samo vannastavne). Podrazumijevano 'casovi'."}, }, "required": ["dijete"], }, @@ -78,7 +79,16 @@ def _parse_kada(s: str) -> tuple[dt.date, str]: return d, _date_fraza(d) except ValueError: pass - wd = {"ponedjeljak": 0, "utorak": 1, "srijeda": 2, "cetvrtak": 3, "petak": 4, "subota": 5, "nedjelja": 6, "nedelja": 6} + # nominativ + akuzativ + instrumental ("petkom", "subotom"...) -> weekday + wd = { + "ponedjeljak": 0, "ponedjeljkom": 0, + "utorak": 1, "utorkom": 1, + "srijeda": 2, "srijedu": 2, "srijedom": 2, + "cetvrtak": 3, "cetvrtkom": 3, + "petak": 4, "petkom": 4, + "subota": 5, "subotu": 5, "subotom": 5, + "nedjelja": 6, "nedjelju": 6, "nedjeljom": 6, "nedelja": 6, "nedeljom": 6, + } # naziv dana -> sljedeće NAILAZEĆE pojavljivanje (danas ako je taj dan; inače # ova sedmica ako još nije prošao, a sljedeća ako jeste — bitno zbog smjena). # "sljedeći/naredni/idući " uvijek gura u narednu sedmicu. @@ -96,6 +106,32 @@ def _parse_kada(s: str) -> tuple[dt.date, str]: return today, "danas" +def _obaveze(name: str, date: dt.date, fraza: str, samo_aktivnosti: bool) -> str: + stavke = [] # (vrijeme_za_sortiranje, tekst) + napomena = "" + + if not samo_aktivnosti: + res = store.lessons_for(name, date) + st = res["status"] + if st == "ok": + casovi = res["casovi"] + stavke.append((casovi[0]["pocetak"], + f"škola od {casovi[0]['pocetak']} do {casovi[-1]['kraj']} ({res['smjena']}. smjena)")) + elif st == "nema_sidra": + napomena = " (za školu nije postavljena smjena u adminu, pa je ne mogu izračunati)" + + for a in store.activities_for(name, date): + raspon = f" od {a['pocetak']} do {a['kraj']}" if a.get("pocetak") else "" + stavke.append((a.get("pocetak", "99:99"), f"{a['naziv']}{raspon}")) + + stavke.sort(key=lambda x: x[0] or "99:99") + sta_rijec = "vannastavnih aktivnosti" if samo_aktivnosti else "obaveza" + if not stavke: + return f'Reci djetetu: "{name} {fraza} nema {sta_rijec}.{napomena}"' + spisak = "; ".join(t for _, t in stavke) + return f'Reci djetetu prirodno na bosanskom: "{name} {fraza} ima: {spisak}.{napomena}"' + + async def raspored_upit(ctx: AppContext, args: dict) -> str: ime_izgovoreno = (args.get("dijete") or "").strip() name = store.match_child(ime_izgovoreno) @@ -107,6 +143,11 @@ async def raspored_upit(ctx: AppContext, args: dict) -> str: date, fraza = _parse_kada(args.get("kada", "")) sta = (args.get("sta") or "casovi").strip().lower() + + # OBAVEZE / AKTIVNOSTI: rade i vikendom i kad nema škole + if sta in ("obaveze", "aktivnosti"): + return _obaveze(name, date, fraza, samo_aktivnosti=(sta == "aktivnosti")) + res = store.lessons_for(name, date) st = res["status"] diff --git a/idriz/apps/raspored/store.py b/idriz/apps/raspored/store.py index 9cd369e..a72a220 100644 --- a/idriz/apps/raspored/store.py +++ b/idriz/apps/raspored/store.py @@ -17,7 +17,8 @@ from pathlib import Path RASPORED_PATH = Path(os.environ.get("IDRIZ_RASPORED_PATH", str(Path.home() / ".local/share/idriz/raspored.json"))) -DANI = ["Ponedjeljak", "Utorak", "Srijeda", "Četvrtak", "Petak"] # radni dani (redoslijed) +DANI = ["Ponedjeljak", "Utorak", "Srijeda", "Četvrtak", "Petak"] # školski (radni) dani +SVI_DANI = ["Ponedjeljak", "Utorak", "Srijeda", "Četvrtak", "Petak", "Subota", "Nedjelja"] # za aktivnosti DAN_PO_WEEKDAY = {0: "Ponedjeljak", 1: "Utorak", 2: "Srijeda", 3: "Četvrtak", 4: "Petak"} DAN_PUNI = ["ponedjeljak", "utorak", "srijeda", "četvrtak", "petak", "subota", "nedjelja"] @@ -30,12 +31,18 @@ def norm(s: str) -> str: _DAN_NORM = {norm(d): d for d in DANI} +_DAN_ANY_NORM = {norm(d): d for d in SVI_DANI} def canonical_dan(s: str) -> str | None: return _DAN_NORM.get(norm(s)) +def canonical_dan_any(s: str) -> str | None: + """Kao canonical_dan ali dopušta i subotu/nedjelju (za aktivnosti).""" + return _DAN_ANY_NORM.get(norm(s)) + + # ---------- učitavanje/snimanje ---------- def _load() -> dict: @@ -81,7 +88,8 @@ def add_child(name: str, razred: str = "") -> bool: data = _load() if name in data["djeca"]: return False - data["djeca"][name] = {"razred": razred.strip(), "sidro": None, "raspored": _empty_raspored()} + data["djeca"][name] = {"razred": razred.strip(), "sidro": None, + "raspored": _empty_raspored(), "aktivnosti": []} _save(data) return True @@ -193,6 +201,47 @@ def child_rows(name: str) -> list[dict]: return out +# ---------- vannastavne aktivnosti (fiksno vrijeme, sedmično, van smjena) ---------- + +def activity_rows(name: str) -> list[dict]: + ch = get_child(name) + if not ch: + return [] + akt = list(ch.get("aktivnosti", [])) + akt.sort(key=lambda a: (SVI_DANI.index(a["dan"]) if a.get("dan") in SVI_DANI else 9, a.get("pocetak", ""))) + return akt + + +def set_activities(name: str, rows: list[dict]) -> int: + """Zamijeni listu aktivnosti djeteta. Svaka: {dan, pocetak, kraj, naziv}. Dan može biti i vikend.""" + data = _load() + ch = data["djeca"].get(name) + if not ch: + return 0 + out = [] + for r in rows: + dan = canonical_dan_any(r.get("dan", "")) + naziv = (r.get("naziv") or "").strip() + if not dan or not naziv: + continue + out.append({"dan": dan, "pocetak": _clean_time(r.get("pocetak", "")), + "kraj": _clean_time(r.get("kraj", "")), "naziv": naziv}) + out.sort(key=lambda a: (SVI_DANI.index(a["dan"]), a["pocetak"])) + ch["aktivnosti"] = out + _save(data) + return len(out) + + +def activities_for(name: str, date: dt.date) -> list[dict]: + ch = get_child(name) + if not ch: + return [] + dan = SVI_DANI[date.weekday()] + akt = [a for a in ch.get("aktivnosti", []) if a.get("dan") == dan] + akt.sort(key=lambda a: a.get("pocetak", "")) + return akt + + # ---------- CSV import ---------- CSV_HEADER = ["dijete", "razred", "smjena", "dan", "redni_broj_casa", "pocetak", "kraj", "predmet"] @@ -234,7 +283,7 @@ def import_csv(text: str) -> dict: for ime, entry in po_djetetu.items(): ch = data["djeca"].get(ime) if not ch: - ch = {"razred": "", "sidro": None, "raspored": _empty_raspored()} + ch = {"razred": "", "sidro": None, "raspored": _empty_raspored(), "aktivnosti": []} data["djeca"][ime] = ch if entry["razred"]: ch["razred"] = entry["razred"] diff --git a/idriz/apps/raspored/templates/raspored_dijete.html b/idriz/apps/raspored/templates/raspored_dijete.html index 125a1a2..5609ecd 100644 --- a/idriz/apps/raspored/templates/raspored_dijete.html +++ b/idriz/apps/raspored/templates/raspored_dijete.html @@ -75,4 +75,34 @@

Prazni redovi na dnu su za dodavanje novih časova. Za brisanje označi "Obriši" pa Sačuvaj.

+ +
+

Vannastavne aktivnosti (treninzi, mekteb...)

+

Fiksno vrijeme, ponavljaju se sedmično, nisu vezane za smjenu — mogu biti i vikendom.

+
+ + +
+ + + + {% for a in agrid %} + {% set i = loop.index0 %} + + + + + + + + {% endfor %} + +
DanPočetakKrajNazivObriši
+
+
+
+
{% endblock %}