feat: generate and download ics calendar
This commit is contained in:
122
src/utils/data.ts
Normal file
122
src/utils/data.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import type { IcsCalendar, IcsEvent } from "ts-ics";
|
||||
|
||||
type WorkEvent = {
|
||||
day: number;
|
||||
start: { hours: number; minutes: number };
|
||||
end: { hours: number; minutes: number };
|
||||
};
|
||||
|
||||
const makeIcsEvent = (
|
||||
data: {
|
||||
year: number;
|
||||
month: number;
|
||||
} & WorkEvent,
|
||||
): IcsEvent => ({
|
||||
uid: `${data.year}-${data.month}-${data.day}-work@gestime-aphp-export`,
|
||||
summary: "Travail",
|
||||
stamp: { date: new Date() },
|
||||
start: {
|
||||
// - 1 because Date takes month index, e.g 0-11
|
||||
date: new Date(
|
||||
data.year,
|
||||
data.month - 1,
|
||||
data.day,
|
||||
data.start.hours,
|
||||
data.start.minutes,
|
||||
),
|
||||
},
|
||||
duration: {
|
||||
hours: Math.floor(
|
||||
(data.end.hours * 60 +
|
||||
data.end.minutes -
|
||||
(data.start.hours * 60 + data.start.minutes)) /
|
||||
60,
|
||||
),
|
||||
minutes:
|
||||
(data.end.hours * 60 +
|
||||
data.end.minutes -
|
||||
(data.start.hours * 60 + data.start.minutes)) %
|
||||
60,
|
||||
},
|
||||
location: "Hôpital Pitié-Salpêtrière",
|
||||
});
|
||||
|
||||
const makeIcsCalendar = (events: IcsEvent[]): IcsCalendar => ({
|
||||
version: "2.0",
|
||||
prodId: "-//pihkaal//Gestime APHP Export//FR",
|
||||
events,
|
||||
});
|
||||
|
||||
export async function extractData(): Promise<{
|
||||
name: string;
|
||||
data: IcsCalendar;
|
||||
} | null> {
|
||||
try {
|
||||
const [tab] = await chrome.tabs.query({
|
||||
active: true,
|
||||
currentWindow: true,
|
||||
});
|
||||
if (!tab?.id) {
|
||||
console.error("No active tab found");
|
||||
return null;
|
||||
}
|
||||
|
||||
const urlMatch = tab.url?.match(/\/(\d{2})-(\d{4})$/);
|
||||
if (!urlMatch) {
|
||||
console.error("Could not extract month and year from URL");
|
||||
return null;
|
||||
}
|
||||
const year = parseInt(urlMatch[2]!);
|
||||
const month = parseInt(urlMatch[1]!);
|
||||
|
||||
const [eventsQuery] = await chrome.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
func: () => {
|
||||
const trs = document.querySelectorAll("#clearfix-individuel table tr");
|
||||
|
||||
const events: WorkEvent[] = [];
|
||||
|
||||
// [Sam 01, RH]
|
||||
// [Dim 02, 7:00 - 19:00]
|
||||
for (const tr of trs) {
|
||||
const tds = tr.querySelectorAll("td");
|
||||
|
||||
const dateText = tds[0]?.textContent?.trim() ?? "";
|
||||
const dateMatch = dateText.match(/^[A-Z][a-z]{2}\s+(\d{2})$/);
|
||||
if (!dateMatch) continue;
|
||||
|
||||
const workText = tds[1]?.textContent?.trim() ?? "";
|
||||
const hoursMatch = workText.match(/^(\d+):(\d+) - (\d+):(\d+)$/);
|
||||
if (!hoursMatch) continue;
|
||||
|
||||
events.push({
|
||||
day: parseInt(dateMatch[1]!),
|
||||
start: {
|
||||
hours: parseInt(hoursMatch[1]!),
|
||||
minutes: parseInt(hoursMatch[2]!),
|
||||
},
|
||||
end: {
|
||||
hours: parseInt(hoursMatch[3]!),
|
||||
minutes: parseInt(hoursMatch[4]!),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return events;
|
||||
},
|
||||
});
|
||||
if (!eventsQuery?.result) return null;
|
||||
|
||||
return {
|
||||
name: `gestime-${urlMatch[1]}-${urlMatch[2]}.ics`,
|
||||
data: makeIcsCalendar(
|
||||
eventsQuery.result.map((event) =>
|
||||
makeIcsEvent({ year, month, ...event }),
|
||||
),
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("Error getting elements data:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user