feat: read "Занятия в Полоцке" as a day's town, show it in all clients
- Parser 14 (server/places.js): "Занятия в Полоцке/Новополоцке/г. Новополоцк" over a day's first row is no longer a lesson but a note of the day for its column (`places`); lessons of that day get `place`. A lesson below the note in the same cell keeps its own subject.
- A town on its own line ("Новополоцк", "г.Новополоцк") or after a room ("246(Новополоцк)") becomes the lesson's `place` and leaves the subject and room. "История Полоцка" is not a town.
- API 14: `place` on lessons and `places` on days of /api/timetable, `place` in the directory. The day notes are shared by the server and the PWA (shared/places.js); the same note in every subgroup reads as the whole group's.
- Android, PWA and iOS: a "Занятия в Полоцке" card above the day's lessons (day pages, calendar, overview, week view, desk stand), "Полоцк" under the date in the week grid, "249Ж · Полоцк" on lesson cards, a "Город" row in details, the town in widgets, tile, reminders and change lists.
- Snapshot lines show the town and the day notes; tests for notes, towns in cells and the API.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1
shared/lessons.d.ts
vendored
1
shared/lessons.d.ts
vendored
@@ -6,6 +6,7 @@ interface LessonLike {
|
||||
subject: string;
|
||||
teacher: string;
|
||||
room: string;
|
||||
place?: string;
|
||||
type: string;
|
||||
condition: string;
|
||||
variant: "all" | "upper" | "lower";
|
||||
|
||||
@@ -27,6 +27,8 @@ const FIELDS = [
|
||||
["day", "День"],
|
||||
["date", "Дата"],
|
||||
["room", "Аудитория"],
|
||||
// "Полоцк": the town a cell or the day's note names (parser 14).
|
||||
["place", "Город"],
|
||||
["teacher", "Преподаватель"],
|
||||
["condition", "Недели"],
|
||||
["type", "Вид занятия"],
|
||||
|
||||
23
shared/places.d.ts
vendored
Normal file
23
shared/places.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
/** "Занятия в Полоцке" as the parser keeps it: a day of a group, or of some of its subgroups. */
|
||||
export interface PlaceNote {
|
||||
day: number;
|
||||
date: string | null;
|
||||
groups: string[];
|
||||
subgroups: { group: string; label: string }[];
|
||||
place: string;
|
||||
text: string;
|
||||
}
|
||||
/** A note of one day for one group; `subgroup` null for the whole group. */
|
||||
export interface DayPlace {
|
||||
place: string;
|
||||
text: string;
|
||||
subgroup: string | null;
|
||||
}
|
||||
export function placesOn(
|
||||
places: PlaceNote[] | null | undefined,
|
||||
group: string,
|
||||
labels: string[],
|
||||
day: number,
|
||||
date?: string | null,
|
||||
): DayPlace[];
|
||||
export function placesFor(list: DayPlace[], subgroup: string | null | undefined): DayPlace[];
|
||||
34
shared/places.js
Normal file
34
shared/places.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// "Занятия в Полоцке": Word marks the days a group spends in another town over the day's first
|
||||
// row; the parser keeps them as notes of the day (server/places.js). The notes of a day for one
|
||||
// group, shared by the server's timetable and the browser.
|
||||
|
||||
/**
|
||||
* The notes on a weekday (or a date) of the group: `{place, text, subgroup}`, the subgroup null
|
||||
* for the whole group. The same note in every subgroup (`labels`) is the whole group's.
|
||||
*/
|
||||
export function placesOn(places, group, labels, day, date = null) {
|
||||
const on = (places || [])
|
||||
.filter((p) => p.groups.includes(group) && p.day === day && (!p.date || p.date === date))
|
||||
.map((p) => ({ place: p.place, text: p.text, subgroup: p.subgroups.find((s) => s.group === group)?.label || null }));
|
||||
const whole = on.filter((p) => !p.subgroup);
|
||||
for (const p of on)
|
||||
if (
|
||||
p.subgroup &&
|
||||
!whole.some((w) => w.text === p.text) &&
|
||||
labels.length &&
|
||||
labels.every((label) => on.some((o) => o.subgroup === label && o.text === p.text))
|
||||
)
|
||||
whole.push({ ...p, subgroup: null });
|
||||
const parts = on.filter((p) => p.subgroup && !whole.some((w) => w.text === p.text));
|
||||
return [...whole, ...parts].filter(
|
||||
(p, i, all) => all.findIndex((o) => o.text === p.text && o.subgroup === p.subgroup) === i,
|
||||
);
|
||||
}
|
||||
|
||||
/** The notes for the student's subgroup; a note for their own subgroup reads as the whole day's. */
|
||||
export function placesFor(list, subgroup) {
|
||||
return list
|
||||
.filter((p) => !subgroup || !p.subgroup || p.subgroup === subgroup)
|
||||
.map((p) => (subgroup ? { ...p, subgroup: null } : p))
|
||||
.filter((p, i, all) => all.findIndex((o) => o.text === p.text && o.subgroup === p.subgroup) === i);
|
||||
}
|
||||
Reference in New Issue
Block a user