feat: load year and month from page, and provide link to planning

This commit is contained in:
Pihkaal
2025-11-05 18:15:51 +01:00
parent eb08d76bf2
commit d6850fb2cb
3 changed files with 86 additions and 49 deletions

View File

@@ -1,10 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { extractData } from "@/utils/data";
import { generateIcsCalendar } from "ts-ics"; import { generateIcsCalendar } from "ts-ics";
import { extractData } from "@/utils/data";
import { isPlanningUrl } from "@/utils/url";
import { onMounted } from "vue";
const error = ref<string | null>(null); const error = ref<string | null>(null);
const onPlanningPage = ref(false);
onMounted(async () => (onPlanningPage.value = await isPlanningUrl()));
const downloadCalendar = async () => { const downloadCalendar = async () => {
error.value = null; error.value = null;
@@ -60,7 +66,7 @@ const downloadCalendar = async () => {
</template> </template>
<div class="space-y-4"> <div class="space-y-4">
<div class="space-y-2"> <div v-if="onPlanningPage" class="space-y-2">
<UButton <UButton
loading-auto loading-auto
label="Download Calendar" label="Download Calendar"
@@ -79,6 +85,18 @@ const downloadCalendar = async () => {
/> />
</div> </div>
<UButton
v-else
label="Go to my planning"
icon="i-lucide-calendar"
size="lg"
block
color="secondary"
variant="subtle"
to="https://gestime.aphp.fr/planning/agent"
target="_blank"
/>
<UButton <UButton
label="Import in Google Calendar" label="Import in Google Calendar"
icon="i-lucide-external-link" icon="i-lucide-external-link"

View File

@@ -1,4 +1,5 @@
import type { IcsCalendar, IcsEvent } from "ts-ics"; import type { IcsCalendar, IcsEvent } from "ts-ics";
import { isPlanningUrl } from "./url";
type WorkEvent = { type WorkEvent = {
day: number; day: number;
@@ -47,6 +48,53 @@ const makeIcsCalendar = (events: IcsEvent[]): IcsCalendar => ({
events, events,
}); });
const dataExtractor = () => {
// extract year and month
const monthSelect = document.getElementById(
"select-pagemois",
) as HTMLSelectElement;
const yearSelect = document.getElementById(
"select-pagean",
) as HTMLSelectElement;
// extract events
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 {
month: parseInt(monthSelect.value),
year: parseInt(yearSelect.value),
events,
};
};
export async function extractData(): Promise<{ export async function extractData(): Promise<{
name: string; name: string;
data: IcsCalendar; data: IcsCalendar;
@@ -55,62 +103,23 @@ export async function extractData(): Promise<{
active: true, active: true,
currentWindow: true, currentWindow: true,
}); });
if (!tab?.id) { if (!tab?.id || !(await isPlanningUrl())) {
throw new Error("URL incorrecte"); throw new Error("URL incorrecte");
} }
const urlMatch = tab.url?.match(/\/(\d{2})-(\d{4})$/);
if (!urlMatch) {
throw new Error("URL incorrecte");
}
const year = parseInt(urlMatch[2]!);
const month = parseInt(urlMatch[1]!);
try { try {
const [eventsQuery] = await chrome.scripting.executeScript({ const [query] = await chrome.scripting.executeScript({
target: { tabId: tab.id }, target: { tabId: tab.id },
func: () => { func: dataExtractor,
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) throw undefined; if (!query?.result) throw undefined;
const { year, month, events } = query.result;
return { return {
name: `gestime-${urlMatch[1]}-${urlMatch[2]}.ics`, name: `gestime-${year}-${month}.ics`,
data: makeIcsCalendar( data: makeIcsCalendar(
eventsQuery.result.map((event) => events.map((event) => makeIcsEvent({ year, month, ...event })),
makeIcsEvent({ year, month, ...event }),
),
), ),
}; };
} catch { } catch {

10
src/utils/url.ts Normal file
View File

@@ -0,0 +1,10 @@
export const isPlanningUrl = async () => {
const [tab] = await chrome.tabs.query({
active: true,
currentWindow: true,
});
return (
tab?.url !== undefined &&
tab.url.startsWith("https://gestime.aphp.fr/planning/agent")
);
};