feat: load year and month from page, and provide link to planning
This commit is contained in:
22
src/App.vue
22
src/App.vue
@@ -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"
|
||||||
|
|||||||
@@ -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
10
src/utils/url.ts
Normal 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")
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user