diff --git a/content-scripts/components/Header.tsx b/content-scripts/components/Navbar/Header.tsx
similarity index 93%
rename from content-scripts/components/Header.tsx
rename to content-scripts/components/Navbar/Header.tsx
index 319ad7a..45b36df 100644
--- a/content-scripts/components/Header.tsx
+++ b/content-scripts/components/Navbar/Header.tsx
@@ -1,10 +1,11 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import jsx from "texsaur";
+import { AuthSession } from "../../types";
import HeaderLinks from "./HeaderLinks";
import Authentication from "./HeaderAuthentication";
-import { AuthSession } from "../types";
+//TODO(thePeras): I think we should move this to a separate file dedicated to constants or a folder like `data`
const HEADER_LINKS = {
Estudantes: {
Bolsas: "web_base.gera_pagina?p_pagina=242366",
diff --git a/content-scripts/components/HeaderAuthentication.tsx b/content-scripts/components/Navbar/HeaderAuthentication.tsx
similarity index 97%
rename from content-scripts/components/HeaderAuthentication.tsx
rename to content-scripts/components/Navbar/HeaderAuthentication.tsx
index 9b03328..9886d24 100644
--- a/content-scripts/components/HeaderAuthentication.tsx
+++ b/content-scripts/components/Navbar/HeaderAuthentication.tsx
@@ -1,8 +1,8 @@
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import jsx from "texsaur";
-import { AuthSession } from "../types";
-import { togglePopover } from "../modules/utilities/popover";
-import Icon from "./Icon";
+import { AuthSession } from "../../types";
+import { togglePopover } from "../../modules/utilities/popover";
+import Icon from "../Icon";
interface Props {
auth: AuthSession | null;
diff --git a/content-scripts/components/HeaderLinks.tsx b/content-scripts/components/Navbar/HeaderLinks.tsx
similarity index 100%
rename from content-scripts/components/HeaderLinks.tsx
rename to content-scripts/components/Navbar/HeaderLinks.tsx
diff --git a/content-scripts/index.js b/content-scripts/index.js
index d701920..58682f3 100644
--- a/content-scripts/index.js
+++ b/content-scripts/index.js
@@ -1,11 +1,10 @@
import {
injectOverrideFunctions,
reverseDateDirection,
- currentAccountPage,
addSortTableActions,
} from "./modules/initialize";
import { injectAllChanges, userPreferences } from "./modules/options";
-import constructNewData from "./modules/utilities/constructNewData";
+import constructNewData from "./modules/options/constructNewData";
import { getStorage, setStorage } from "./modules/utilities/storage";
import { rememberLogin } from "./modules/login";
import { replaceIcons } from "./modules/icons";
@@ -18,6 +17,7 @@ import { fixPagination } from "./modules/pagination";
import { changeLayout } from "./modules/layout";
import { addStarIconToCard } from "./modules/favorite-course";
import { createComponentsPage } from "./pages/components_page";
+import { currentAccountPage } from "./pages/current_account_page";
/*--
- Docs: https://developer.chrome.com/docs/extensions/reference/storage/#synchronous-response-to-storage-updates
diff --git a/content-scripts/modules/icons/constants.js b/content-scripts/modules/icons/constants.js
deleted file mode 100644
index 551be22..0000000
--- a/content-scripts/modules/icons/constants.js
+++ /dev/null
@@ -1,240 +0,0 @@
-export const IMG_ICON_MAP = Object.freeze({
- Acreditado: "checkbox-circle",
- Alerta: "alert",
- Apagar: "close-circle",
- Aprovado: "checkbox-circle",
- Arroba: "at",
- Ascendente: "arrow-up-s",
- AscendenteS: "arrow-up-s",
- "atalho_administracao.gif": "admin",
- "atalho_computador.gif": "computer",
- "atalho_coracao.gif": "heart",
- "atalho_curso.gif": "honour",
- "atalho_default.gif": "share-forward",
- "atalho_disciplina.gif": "book",
- "atalho_fabrica.gif": "building-3",
- "atalho_grafico.gif": "bar-chart",
- "atalho_instituicao.gif": "bank",
- "atalho_laboratorio.gif": "test-tube",
- "atalho_lampada.gif": "lightbulb",
- "atalho_lupa.gif": "search",
- "atalho_martelo.gif": "hammer",
- "atalho_mundo.gif": "earth",
- "atalho_olho.gif": "eye",
- "atalho_pessoa.gif": "user",
- "atalho_predio.gif": "building-4",
- "atalho_roda_dentada.gif": "settings-4",
- "atalho_smile_admirado.gif": "emotion",
- "atalho_smile_feliz.gif": "emotion-happy",
- "atalho_smile_triste.gif": "emotion-unhappy",
- "atalho_tomada.gif": "plug",
- AtencaoNPisca: "alert",
- AtencaoPisca: "error-warning",
- BotaoColapsar: "close-circle",
- BotaoMin: "close-circle",
- BotaoPersonalisar: "edit",
- BotaoRestaurar: "add-circle",
- Calendario: "calendar",
- "CERT-Certidao": "file-paper-2",
- "CERT-Visto": "checkbox-circle",
- Comentario: "edit",
- Completo: "checkbox-circle",
- CriarNovo: "add-circle",
- Descendente: "arrow-down-s",
- DescendenteS: "arrow-down-s",
- Documento: "file-text",
- // Documento: "file-text",
- DocumentoCriar: "file-add",
- EditarPerfil: "edit",
- EnderecoEmail: "mail",
- Erro: "error-warning",
- EstadoSem1_off: "arrow-up-circle",
- EstadoSem1: "checkbox-circle",
- EstadoSem2: "arrow-down-circle",
- EstadoSem3: "refresh",
- EstadoSem4: "close-circle",
- "facebook_32.png": "facebook-box",
- Fazer: "indeterminate-circle",
- "FEST-Selecionado": "radio-button",
- "FEST-Selecionar": "checkbox-blank-circle",
- "GENT-ContactoAguarda": "error-warning",
- "GENT-ContactoOK": "checkbox-circle",
- "GPAG-Documento-Impresso": "printer",
- "GPAG-Documento": "file-text",
- "GPAG-MB": "bank-card-2",
- "In_Logo_Web4Print_CMYK_1in.jpg": "linkedin-box",
- Informa: "information",
- Inscrito: "indeterminate-circle",
- "instagram40x40.png": "instagram",
- "IPUP-Formula": "functions",
- "IT-AlocAdm": "admin",
- "IT-Email": "mail",
- "IT-Fotos4x4": "layout-grid",
- "IT-Fotos6x6": "grid",
- JanelaFechar: "close-circle",
- LegendaSemaforos: "question",
- Limpar: "eraser",
- ListaMais: "add-circle",
- ListaMenos: "close-circle",
- LOV: "arrow-left",
- Lupa: "search",
- // Lupa: "search",
- MarcaTemporal: "checkbox-circle",
- MarcaTemporalActivo: "play-circle",
- MarcaTemporalOff: "checkbox-blank-circle",
- Mensagem: "chat-4",
- MensagemBBom: "emotion-laugh",
- MensagemBom: "emotion-happy",
- MensagemFilhos: "question-answer",
- MensagemGrande: "sticky-note",
- MensagemMau: "emotion-sad",
- MensagemMMau: "emotion-unhappy",
- MensagemNao: "thumb-down",
- MensagemNeutro: "emotion-normal",
- MensagemNova: "folder-unknown",
- MensagemResponder: "edit",
- MensagemSemFilhos: "chat-off",
- MensagemSim: "thumb-up",
- MensagemVelha: "folder",
- MensagemVelhaV: "folder-open",
- MenuContextoDetalhesMaisG: "zoom-in",
- MenuContextoDetalhesMenosG: "zoom-out",
- MenuContextoEditarG: "edit",
- MenuContextoImagemPisoDescerOff: "arrow-right-down",
- MenuContextoImagemPisoSubirOff: "arrow-right-up",
- MenuContextoInicioG: "",
- MoodleIcon: undefined,
- NaoInscrito: "close-circle",
- Novo: "star",
- Obr: "asterisk",
- PaginaWeb: "global",
- Pasta: "folder-open",
- PastaFechada: "folder",
- Pessoa: "user",
- Prior1: "arrow-up-double",
- Prior2: "arrow-up-s",
- Prior3: "subtract",
- Prior4: "arrow-down-s",
- Prior5: "arrow-down-double",
- QuadradoMaisPeq: "add-circle",
- QuadradoMenosPeq: "close-circle",
- QuadradoPequenoMais: "add-circle",
- QuadradoPequenoMenos: "close-circle",
- // "ResultadoActual": "map-pin", // TODO (toino): find better icon
- // "ResultadoAnt": "arrow-left-s",
- // "ResultadoAntFin": "skip-back",
- // "ResultadoAntInt": "more",
- // "ResultadoSeg": "arrow-right-s",
- // "ResultadoSegFin": "skip-forward",
- // "ResultadoSegInt": "more",
- SemAmarelo: "indeterminate-circle",
- SemPermissoes: "error-warning",
- SemRegistos: "forbid",
- SemVerde: "checkbox-circle",
- SemVermelho: "error-warning",
- SetaDir: "arrow-right",
- "SUMARIOS-Anexo": "attachment",
- "SUMARIOS-AnexoOff": "attachment",
- "SUMARIOS-VerSumario": "file-text",
- "SUMARIOS-VerSumarioOff": "file-text",
- Telef: "phone",
- Visto: "checkbox-circle",
- "youtube_32.png": "youtube",
- ZipPeq: "file-zip",
-});
-
-export const FA_ICON_MAP = Object.freeze({
- bars: "menu",
- "ellipsis-v": "more-2",
- "envelope-o": "mail",
- fax: "printer", // TODO (toino): find better icon
- laptop: "computer",
- phone: "phone",
- "plus-circle": "add-circle",
- unlock: "login-circle",
- envelope: "mail-unread",
-});
-
-export const BANNER_ICON_MAP = Object.freeze({
- alerta: "alert",
- info: "information",
- informa: "information",
-});
-
-export const BG_IMAGE_ICON_MAP = Object.freeze({
- ".acao.adicionar-elemento": "add-circle",
- ".acao.adicionar": "add-circle",
- ".acao.eliminar": "close-circle",
- ".acao.limpar": "eraser",
- ".EmAprovacao-28-14": "indeterminate-circle",
- ".Impossibilidade-20-20": "error-warning",
- ".Pausa-20-20": "pause",
- ".terminar-sessao": "logout-circle",
-});
-
-export const EVENTS = Object.freeze([
- "animationcancel",
- "animationend",
- "animationiteration",
- "animationstart",
- "afterscriptexecute",
- "auxclick",
- "beforescriptexecute",
- "blur",
- "click",
- "compositionend",
- "compositionstart",
- "compositionupdate",
- "contextmenu",
- "copy",
- "cut",
- "dblclick",
- "DOMActivate",
- "DOMMouseScroll",
- "error",
- "focusin",
- "focusout",
- "focus",
- "fullscreenchange",
- "fullscreenerror",
- "gesturechange",
- "gestureend",
- "gesturestart",
- "gotpointercapture",
- "keydown",
- "keypress",
- "keyup",
- "lostpointercapture",
- "mousedown",
- "mouseenter",
- "mouseleave",
- "mousemove",
- "mouseout",
- "mouseover",
- "mouseup",
- "mousewheel",
- "paste",
- "pointercancel",
- "pointerdown",
- "pointerenter",
- "pointerleave",
- "pointermove",
- "pointerout",
- "pointerover",
- "pointerup",
- "scroll",
- "select",
- "touchcancel",
- "touchend",
- "touchmove",
- "touchstart",
- "transitioncancel",
- "transitionend",
- "transitionrun",
- "transitionstart",
- "webkitmouseforcechanged",
- "webkitmouseforcedown",
- "webkitmouseforceup",
- "webkitmouseforcewillbegin",
- "wheel",
-]);
diff --git a/content-scripts/modules/icons/constants.ts b/content-scripts/modules/icons/constants.ts
new file mode 100644
index 0000000..ac97bdb
--- /dev/null
+++ b/content-scripts/modules/icons/constants.ts
@@ -0,0 +1,240 @@
+/*
+ NitSig is replacing the icons with new ones from RemixIcon.
+ Only choose icons from RemixIcon to ensure consistency.
+ https://remixicon.com/
+*/
+
+export const IMG_ICON_MAP: { readonly [key: string]: string | undefined } =
+ Object.freeze({
+ Acreditado: "checkbox-circle",
+ Alerta: "alert",
+ Apagar: "close-circle",
+ Aprovado: "checkbox-circle",
+ Arroba: "at",
+ Ascendente: "arrow-up-s",
+ AscendenteS: "arrow-up-s",
+ "atalho_administracao.gif": "admin",
+ "atalho_computador.gif": "computer",
+ "atalho_coracao.gif": "heart",
+ "atalho_curso.gif": "honour",
+ "atalho_default.gif": "share-forward",
+ "atalho_disciplina.gif": "book",
+ "atalho_fabrica.gif": "building-3",
+ "atalho_grafico.gif": "bar-chart",
+ "atalho_instituicao.gif": "bank",
+ "atalho_laboratorio.gif": "test-tube",
+ "atalho_lampada.gif": "lightbulb",
+ "atalho_lupa.gif": "search",
+ "atalho_martelo.gif": "hammer",
+ "atalho_mundo.gif": "earth",
+ "atalho_olho.gif": "user",
+ "atalho_pessoa.gif": "user",
+ "atalho_predio.gif": "building-4",
+ "atalho_roda_dentada.gif": "settings-4",
+ "atalho_smile_admirado.gif": "emotion",
+ "atalho_smile_feliz.gif": "emotion-happy",
+ "atalho_smile_triste.gif": "emotion-unhappy",
+ "atalho_tomada.gif": "plug",
+ AtencaoNPisca: "alert",
+ AtencaoPisca: "error-warning",
+ BotaoColapsar: "close-circle",
+ BotaoMin: "close-circle",
+ BotaoPersonalisar: "edit",
+ BotaoRestaurar: "add-circle",
+ Calendario: "calendar",
+ "CERT-Certidao": "file-paper-2",
+ "CERT-Visto": "checkbox-circle",
+ Comentario: "edit",
+ Completo: "checkbox-circle",
+ CriarNovo: "add-circle",
+ Descendente: "arrow-down-s",
+ DescendenteS: "arrow-down-s",
+ Documento: "file-text",
+ DocumentoCriar: "file-add",
+ EditarPerfil: "edit",
+ EnderecoEmail: "mail",
+ Erro: "error-warning",
+ EstadoSem1_off: "arrow-up-circle",
+ EstadoSem1: "checkbox-circle",
+ EstadoSem2: "arrow-down-circle",
+ EstadoSem3: "refresh",
+ EstadoSem4: "close-circle",
+ "facebook_32.png": "facebook-box",
+ Fazer: "indeterminate-circle",
+ "FEST-Selecionado": "radio-button",
+ "FEST-Selecionar": "checkbox-blank-circle",
+ "GENT-ContactoAguarda": "error-warning",
+ "GENT-ContactoOK": "checkbox-circle",
+ "GPAG-Documento-Impresso": "printer",
+ "GPAG-Documento": "file-text",
+ "GPAG-MB": "bank-card-2",
+ "In_Logo_Web4Print_CMYK_1in.jpg": "linkedin-box",
+ Informa: "information",
+ Inscrito: "indeterminate-circle",
+ "instagram40x40.png": "instagram",
+ "IPUP-Formula": "functions",
+ "IT-AlocAdm": "admin",
+ "IT-Email": "mail",
+ "IT-Fotos4x4": "layout-grid",
+ "IT-Fotos6x6": "grid",
+ JanelaFechar: "close-circle",
+ LegendaSemaforos: "question",
+ Limpar: "eraser",
+ ListaMais: "add-circle",
+ ListaMenos: "close-circle",
+ LOV: "arrow-left",
+ Lupa: "search",
+ MarcaTemporal: "checkbox-circle",
+ MarcaTemporalActivo: "play-circle",
+ MarcaTemporalOff: "checkbox-blank-circle",
+ Mensagem: "chat-4",
+ MensagemBBom: "emotion-laugh",
+ MensagemBom: "emotion-happy",
+ MensagemFilhos: "question-answer",
+ MensagemGrande: "sticky-note",
+ MensagemMau: "emotion-sad",
+ MensagemMMau: "emotion-unhappy",
+ MensagemNao: "thumb-down",
+ MensagemNeutro: "emotion-normal",
+ MensagemNova: "folder-unknown",
+ MensagemResponder: "edit",
+ MensagemSemFilhos: "chat-off",
+ MensagemSim: "thumb-up",
+ MensagemVelha: "folder",
+ MensagemVelhaV: "folder-open",
+ MenuContextoDetalhesMaisG: "zoom-in",
+ MenuContextoDetalhesMenosG: "zoom-out",
+ MenuContextoEditarG: "edit",
+ MenuContextoImagemPisoDescerOff: "arrow-right-down",
+ MenuContextoImagemPisoSubirOff: "arrow-right-up",
+ MenuContextoInicioG: "",
+ MoodleIcon: undefined,
+ NaoInscrito: "close-circle",
+ Novo: "star",
+ Obr: "asterisk",
+ PaginaWeb: "global",
+ Pasta: "folder-open",
+ PastaFechada: "folder",
+ Pessoa: "user",
+ Prior1: "arrow-up-double",
+ Prior2: "arrow-up-s",
+ Prior3: "subtract",
+ Prior4: "arrow-down-s",
+ Prior5: "arrow-down-double",
+ QuadradoMaisPeq: "add-circle",
+ QuadradoMenosPeq: "close-circle",
+ QuadradoPequenoMais: "add-circle",
+ QuadradoPequenoMenos: "close-circle",
+ SemAmarelo: "indeterminate-circle",
+ SemPermissoes: "error-warning",
+ SemRegistos: "forbid",
+ SemVerde: "checkbox-circle",
+ SemVermelho: "error-warning",
+ SetaDir: "arrow-right",
+ "SUMARIOS-Anexo": "attachment",
+ "SUMARIOS-AnexoOff": "attachment",
+ "SUMARIOS-VerSumario": "file-text",
+ "SUMARIOS-VerSumarioOff": "file-text",
+ Telef: "phone",
+ Visto: "checkbox-circle",
+ "youtube_32.png": "youtube",
+ ZipPeq: "file-zip",
+ });
+
+export const FA_ICON_MAP: { readonly [key: string]: string } = Object.freeze({
+ bars: "menu",
+ "ellipsis-v": "more-2",
+ "envelope-o": "mail",
+ fax: "printer", // TODO (toino): find better icon
+ laptop: "computer",
+ phone: "phone",
+ "plus-circle": "add-circle",
+ unlock: "login-circle",
+ envelope: "mail-unread",
+});
+
+export const BANNER_ICON_MAP: { readonly [key: string]: string } =
+ Object.freeze({
+ alerta: "alert",
+ info: "information",
+ informa: "information",
+ });
+
+export const BG_IMAGE_ICON_MAP: { readonly [key: string]: string } =
+ Object.freeze({
+ ".acao.adicionar-elemento": "add-circle",
+ ".acao.adicionar": "add-circle",
+ ".acao.eliminar": "close-circle",
+ ".acao.limpar": "eraser",
+ ".EmAprovacao-28-14": "indeterminate-circle",
+ ".Impossibilidade-20-20": "error-warning",
+ ".Pausa-20-20": "pause",
+ ".terminar-sessao": "logout-circle",
+ });
+
+export const EVENTS: readonly string[] = Object.freeze([
+ "animationcancel",
+ "animationend",
+ "animationiteration",
+ "animationstart",
+ "afterscriptexecute",
+ "auxclick",
+ "beforescriptexecute",
+ "blur",
+ "click",
+ "compositionend",
+ "compositionstart",
+ "compositionupdate",
+ "contextmenu",
+ "copy",
+ "cut",
+ "dblclick",
+ "DOMActivate",
+ "DOMMouseScroll",
+ "error",
+ "focusin",
+ "focusout",
+ "focus",
+ "fullscreenchange",
+ "fullscreenerror",
+ "gesturechange",
+ "gestureend",
+ "gesturestart",
+ "gotpointercapture",
+ "keydown",
+ "keypress",
+ "keyup",
+ "lostpointercapture",
+ "mousedown",
+ "mouseenter",
+ "mouseleave",
+ "mousemove",
+ "mouseout",
+ "mouseover",
+ "mouseup",
+ "mousewheel",
+ "paste",
+ "pointercancel",
+ "pointerdown",
+ "pointerenter",
+ "pointerleave",
+ "pointermove",
+ "pointerout",
+ "pointerover",
+ "pointerup",
+ "scroll",
+ "select",
+ "touchcancel",
+ "touchend",
+ "touchmove",
+ "touchstart",
+ "transitioncancel",
+ "transitionend",
+ "transitionrun",
+ "transitionstart",
+ "webkitmouseforcechanged",
+ "webkitmouseforcedown",
+ "webkitmouseforceup",
+ "webkitmouseforcewillbegin",
+ "wheel",
+]);
diff --git a/content-scripts/modules/icons/index.js b/content-scripts/modules/icons/index.ts
similarity index 54%
rename from content-scripts/modules/icons/index.js
rename to content-scripts/modules/icons/index.ts
index 393b2dd..7c162b5 100644
--- a/content-scripts/modules/icons/index.js
+++ b/content-scripts/modules/icons/index.ts
@@ -6,21 +6,33 @@ import {
EVENTS,
} from "./constants";
-const addCSS = () => {
- if (!document.querySelector('link[href$="remixicon.css"]'))
+/**
+ * Adds a link to the Remix Icon stylesheet if it's not already included in the document.
+ */
+const addCSS = (): void => {
+ if (!document.querySelector('link[href$="remixicon.css"]')) {
+ // TODO (thePeras): Why we don't shipped the css file with the extension already?
document.head.innerHTML +=
'';
+ }
};
-const replaceImages = () => {
- /** @type {(i: HTMLImageElement) => any} */
- const handleImage = (i) => {
+/**
+ * Replaces elements with corresponding icons based on their src attributes.
+ */
+const replaceImages = (): void => {
+ /**
+ * Handles the replacement of an individual image element with its corresponding icon.
+ * @param {HTMLImageElement} i - The image element to process.
+ */
+ const handleImage = (i: HTMLImageElement): void => {
const icon = IMG_ICON_MAP[i.src.substring(i.src.lastIndexOf("/") + 1)];
- let span = null;
+ let span: HTMLSpanElement | null = null;
- if (i.nextElementSibling?.matches(".se-icon"))
- span = i.nextElementSibling;
+ if (i.nextElementSibling?.matches(".se-icon")) {
+ span = i.nextElementSibling as HTMLSpanElement;
+ }
if (icon === undefined) {
span?.remove();
@@ -41,7 +53,7 @@ const replaceImages = () => {
copyEvents(i, span);
}
- let size = Math.max(
+ const size = Math.max(
Math.round(Math.max(i.width, i.height) / 24) * 24,
24,
);
@@ -55,13 +67,17 @@ const replaceImages = () => {
new MutationObserver((ms) =>
ms.forEach((m) => {
- if (m.target instanceof HTMLImageElement) handleImage(m.target);
- else if (m.addedNodes)
+ if (m.target instanceof HTMLImageElement) {
+ handleImage(m.target);
+ } else if (m.addedNodes) {
m.addedNodes.forEach((n) => {
- if (n instanceof HTMLImageElement) handleImage(n);
- else if (n instanceof HTMLElement)
+ if (n instanceof HTMLImageElement) {
+ handleImage(n);
+ } else if (n instanceof HTMLElement) {
n.querySelectorAll("img").forEach(handleImage);
+ }
});
+ }
}),
).observe(document, {
subtree: true,
@@ -72,42 +88,55 @@ const replaceImages = () => {
document.querySelectorAll("img").forEach(handleImage);
};
-const copyAttrs = (el1, el2) => {
- for (const attr of el1.attributes)
- if (!attr.name.startsWith("on"))
+/**
+ * Copies all attributes from one HTML element to another, excluding event attributes.
+ */
+const copyAttrs = (source: HTMLElement, target: HTMLElement): void => {
+ for (const attr of Array.from(source.attributes)) {
+ if (!attr.name.startsWith("on")) {
try {
- el2.setAttribute(attr.name, attr.value);
+ target.setAttribute(attr.name, attr.value);
} catch (error) {
console.error(error);
}
+ }
+ }
};
-const copyEvents = (el1, el2) => {
- for (const event of EVENTS)
- el2.addEventListener(event, (e) => {
- el1.dispatchEvent(new e.constructor(e.type, e));
+/**
+ * Copies specified event listeners from one element to another.
+ */
+const copyEvents = (source: HTMLElement, target: HTMLElement): void => {
+ for (const event of EVENTS) {
+ target.addEventListener(event, (e: Event) => {
+ source.dispatchEvent(new Event(e.type, e));
e.stopPropagation();
});
+ }
};
/**
- * @param {HTMLElement} el1
- * @param {HTMLElement} el2
+ * Replaces one HTML element with another while preserving attributes and child nodes.
+ * @param {HTMLElement} el1 - The element to be replaced.
+ * @param {HTMLElement} el2 - The element that will replace the first element.
*/
-const replaceWithKeepAttrs = (el1, el2) => {
- for (const attr of el1.attributes)
+const replaceWithAttrs = (el1: HTMLElement, el2: HTMLElement): void => {
+ for (const attr of Array.from(el1.attributes)) {
try {
el2.setAttribute(attr.name, attr.value);
} catch (error) {
console.error(error);
}
+ }
- el2.append(...el1.children);
-
+ el2.append(...Array.from(el1.children));
el1.replaceWith(el2);
};
-const replaceFA = () => {
+/**
+ * Replaces Font Awesome icons in the document with their corresponding Remix Icons.
+ */
+const replaceFA = (): void => {
document.querySelectorAll(".fa").forEach((i) => {
i.classList.remove("fa", "fa-fw");
@@ -123,7 +152,10 @@ const replaceFA = () => {
});
};
-const replaceBgImages = () => {
+/**
+ * Replaces background images in elements with corresponding icons based on a mapping.
+ */
+const replaceBgImages = (): void => {
Object.entries(BG_IMAGE_ICON_MAP).forEach(([selector, icon]) => {
document.querySelectorAll(selector).forEach((i) => {
if (icon === "") return i.classList.add("se-hidden-icon");
@@ -134,13 +166,16 @@ const replaceBgImages = () => {
i.classList.add("se-remove-icon");
- if (i.tagName == "img") replaceWithKeepAttrs(i, span);
+ if (i.tagName === "img") replaceWithAttrs(i as HTMLElement, span);
else i.insertBefore(span, i.firstChild);
});
});
};
-const replaceBanners = () => {
+/**
+ * Replaces banners in the document with corresponding icons based on a mapping.
+ */
+const replaceBanners = (): void => {
Object.entries(BANNER_ICON_MAP).forEach(([k, v]) => {
document.querySelectorAll(`.${k}`).forEach((i) => {
const span = document.createElement("span");
@@ -155,13 +190,16 @@ const replaceBanners = () => {
});
};
-// TODO (toino): handle this mess of a page
-// https://sigarra.up.pt/feup/pt/FEST_GERAL.INQ_RAIDES_EDIT
-
-export const replaceIcons = () => {
+/**
+ * Initiates the icon replacement process for the document.
+ */
+export const replaceIcons = (): void => {
addCSS();
replaceImages();
replaceFA();
replaceBgImages();
replaceBanners();
};
+
+// TODO (toino): handle this mess of a page
+// https://sigarra.up.pt/feup/pt/FEST_GERAL.INQ_RAIDES_EDIT
diff --git a/content-scripts/modules/initialize.js b/content-scripts/modules/initialize.js
index a99b138..4b6134a 100644
--- a/content-scripts/modules/initialize.js
+++ b/content-scripts/modules/initialize.js
@@ -1,21 +1,5 @@
-import throttle from "./utilities/throttle";
-import { getPath } from "./utilities/sigarra";
import { isDate, reverseDate } from "./utilities/date";
-// Resize Listener
-export const addResizeListener = () => {
- window.addEventListener(
- "resize",
- throttle(async () => {
- /*
-
- Code here
-
- */
- }, 1000),
- );
-};
-
// Append override-functions.js to the page
export const injectOverrideFunctions = () => {
const script = document.createElement("script");
@@ -35,254 +19,6 @@ export const reverseDateDirection = () => {
});
};
-export const currentAccountPage = () => {
- if (getPath() != "gpag_ccorrente_geral.conta_corrente_view") return;
-
- const contaCorrente = document.getElementById(
- "GPAG_CCORRENTE_GERAL_CONTA_CORRENTE_VIEW",
- );
- if (contaCorrente) {
- let tabs = contaCorrente.querySelectorAll(".tab");
-
- // merge "Crédito" and "Débito" collumns and remove collumns
- tabs.forEach((tab, tab_index) => {
- let creditColumnIndex;
- let columnsToRemove = [];
- let rows = [...tab.querySelectorAll("thead > tr, tbody > tr")];
- if (rows.length == 0) return;
-
- let headerTitles = document.querySelectorAll(
- "ul.ui-tabs-nav > li > a",
- );
- headerTitles = [...headerTitles].map((title) => title.textContent);
- let headerCells = rows[0].querySelectorAll("th");
- headerCells.forEach((th, index) => {
- if (th.innerHTML == "Débito") {
- th.innerHTML = "Valor";
- } else if (th.innerHTML == "Crédito") {
- creditColumnIndex = index;
- // Colspan
- let colSpan = headerCells[0].colSpan;
- if (colSpan > 1) creditColumnIndex += colSpan;
- th.remove();
- }
-
- // Remove "Valor Pago" Column
- if (th.innerHTML == "Valor Pago") {
- columnsToRemove.push(index + headerCells[0].colSpan - 1);
- th.remove();
- }
-
- // Remove "Valor em Falta" Column
- if (th.innerHTML == "Valor em Falta") {
- th.innerHTML = "";
- th.colSpan = 1;
- columnsToRemove.push(index + headerCells[0].colSpan - 1);
- }
-
- // Rename "Juros em Mora" Column
- if (th.innerHTML == "Juros de Mora") {
- th.innerHTML = "Juros";
- // Remove "Juros de Mora" Column in "Juros de mora Proprinas" tab
- if (headerTitles[tab_index] == "Juros de mora Propinas") {
- columnsToRemove.push(
- index + headerCells[0].colSpan - 1,
- );
- th.remove();
- }
- }
-
- // Remove "Débito em Falta" Column
- if (th.innerHTML == "Débito em Falta") {
- columnsToRemove.push(index + headerCells[0].colSpan - 1);
- th.remove();
- }
-
- // Remove "Valor em Falta" Column
- if (th.innerHTML == "Documento") {
- th.colSpan = 1;
- columnsToRemove.push(index + headerCells[0].colSpan - 1);
- }
-
- // Remove "Estado" Column
- if (th.innerHTML == "Estado") {
- columnsToRemove.push(index + headerCells[0].colSpan - 1);
- th.remove();
- }
- });
-
- rows.shift();
-
- rows.forEach((row) => {
- let cells = [...row.querySelectorAll("td")];
- columnsToRemove.forEach((columnIndex) => {
- if (!cells[0].classList.contains("credito")) {
- cells[columnIndex].remove();
- }
- });
- });
-
- if (creditColumnIndex) {
- rows.forEach((row, index) => {
- let isGeralExtract =
- headerTitles[tab_index] == "Extrato Geral";
-
- let cells = [...row.querySelectorAll("td")];
- let debitCell = cells[creditColumnIndex - 1];
-
- if (debitCell.innerHTML == " ") {
- debitCell.innerHTML = "";
- debitCell.classList.add("n");
- if (isGeralExtract) {
- debitCell.classList.add("positive");
- debitCell.innerHTML = "+";
- }
- debitCell.innerHTML +=
- cells[creditColumnIndex].innerHTML;
- } else {
- if (isGeralExtract) {
- debitCell.classList.add("negative");
- debitCell.innerHTML = "-" + debitCell.innerHTML;
- }
- }
- cells[creditColumnIndex].remove();
- if (cells[0].classList.contains("credito")) {
- //remove "Multibanco - SIBS" row
- //TODO: adicionar data a "pago em"
-
- //change the last cell of the last row to the value of the last cell of the current row
- let lastRowCells =
- rows[index - 1].querySelectorAll("td");
-
- let document_file =
- cells[cells.length - 1].querySelector("a");
- if (document_file) {
- lastRowCells[lastRowCells.length - 1].innerHTML =
- "";
- lastRowCells[lastRowCells.length - 1].appendChild(
- document_file,
- );
- lastRowCells[
- lastRowCells.length - 1
- ].style.paddingRight = "0.6rem";
- }
- row.remove();
- }
- });
- }
- });
-
- // Change "Data" collumn position in "Extrato Geral" tab
- const geralExtractTable = document.querySelector("#tab_extracto_geral");
- if (geralExtractTable) {
- geralExtractTable.querySelectorAll("tr").forEach((row) => {
- let cells = [
- ...row.querySelectorAll("td"),
- ...row.querySelectorAll("th"),
- ];
- // len = cells.length;
- row.insertBefore(cells[1], cells[0]);
- });
- }
-
- // Switch "Refência" action button to the right
- if (tabs.length > 0) {
- tabs[0].querySelectorAll("tbody > tr").forEach((row) => {
- let cells = [
- ...row.querySelectorAll("td"),
- ...row.querySelectorAll("th"),
- ];
- let len = cells.length;
- row.insertBefore(cells[len - 1], cells[len - 2]);
- });
- }
-
- let statusProperties = {
- Pago: {
- class: "success",
- text: "Pago",
- },
- "Não pago mas prazo ainda não foi excedido": {
- class: "pending",
- text: "Pendente",
- },
- Anulado: {
- class: "cancelled",
- text: "Anulado",
- },
- "Prazo excedido": {
- class: "danger",
- text: "Excedido",
- },
- };
-
- // Improve the status badge
- tabs.forEach((tab) => {
- tab.querySelectorAll("tbody > tr").forEach((row) => {
- let cells = [...row.querySelectorAll("td")];
- if (cells.length == 0) return;
-
- // Get title atriuibute from the first cell
- const cellStatus =
- cells[0].querySelector("img")?.getAttribute("title") ??
- null;
- if (cellStatus == null) return;
-
- // Creating a new status badge
- let statusDiv = document.createElement("div");
- statusDiv.innerHTML = statusProperties[cellStatus].text;
- statusDiv.classList.add("badge");
- statusDiv.classList.add(
- "badge-" + statusProperties[cellStatus].class,
- );
- statusDiv.title = cellStatus;
-
- cells[0].innerHTML = statusDiv.outerHTML;
- });
- });
-
- // Remove "Movimentos" h2
- contaCorrente.previousElementSibling.remove();
-
- // Create Balance and NIF cards
- const saldo = document.querySelector(
- ".formulario #span_saldo_total",
- ).textContent;
- const saldoCard = document.createElement("div");
- saldoCard.classList.add("card");
- const title = document.createElement("p");
- title.innerHTML = "Saldo";
- const saldoValue = document.createElement("h3");
- saldoValue.innerHTML = saldo + "€";
- saldoCard.appendChild(title);
- saldoCard.appendChild(saldoValue);
-
- const nif = Array.from(
- document.querySelectorAll(".formulario .formulario-legenda"),
- ).filter((el) => el.innerHTML.includes("N.I.F."))[0].nextElementSibling
- .innerHTML;
- const nifCard = document.createElement("div");
- nifCard.classList.add("card");
- const nifTitle = document.createElement("p");
- nifTitle.innerHTML = "NIF";
- const nifValue = document.createElement("h3");
- nifValue.innerHTML = nif;
- nifCard.appendChild(nifTitle);
- nifCard.appendChild(nifValue);
-
- let accountDetails = document.createElement("div");
- accountDetails.style.display = "flex";
- accountDetails.style.gap = "1rem";
- accountDetails.style.marginBottom = "0.5rem";
-
- accountDetails.appendChild(saldoCard);
- accountDetails.appendChild(nifCard);
- contaCorrente.insertBefore(accountDetails, contaCorrente.firstChild);
-
- return;
- }
-};
-
// We are now doing this on our own table component (components/table.tsx), but this still works for regular tables that we didn't reimplement yet
export const addSortTableActions = () => {
document.querySelectorAll("th").forEach((th) => {
diff --git a/content-scripts/modules/layout.ts b/content-scripts/modules/layout.ts
index de21a86..96b7eda 100644
--- a/content-scripts/modules/layout.ts
+++ b/content-scripts/modules/layout.ts
@@ -1,5 +1,5 @@
import { AuthSession } from "../types";
-import Header from "../components/Header";
+import Header from "../components/Navbar/Header";
import { fetchSigarraPage } from "./utilities/pageUtils";
import Button from "../components/Button";
diff --git a/content-scripts/modules/options/addStyle.ts b/content-scripts/modules/options/addStyle.ts
new file mode 100644
index 0000000..18aa36c
--- /dev/null
+++ b/content-scripts/modules/options/addStyle.ts
@@ -0,0 +1,15 @@
+import removeElement from "../utilities/removeElement";
+
+// Utility function to inject CSS into the page
+export default function addStyles(id: string, css: string): void {
+ // If the element already exists, remove it
+ removeElement(id);
+
+ const head = document.querySelector("head");
+ if (head) {
+ const style = document.createElement("style");
+ style.id = id;
+ style.textContent = css;
+ head.appendChild(style);
+ }
+}
diff --git a/content-scripts/modules/options/addStyles.js b/content-scripts/modules/options/addStyles.js
deleted file mode 100644
index 5c696e8..0000000
--- a/content-scripts/modules/options/addStyles.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import removeElement from "../utilities/removeElement";
-
-// Utility function to inject CSS into page
-export default function addStyles(id, css) {
- // First remove before adding
- removeElement(id);
-
- const head = document.querySelector("head");
- const style = document.createElement("style");
- style.id = id;
- style.textContent = `${css}`;
- head.appendChild(style);
-}
diff --git a/content-scripts/modules/utilities/constructNewData.js b/content-scripts/modules/options/constructNewData.ts
similarity index 60%
rename from content-scripts/modules/utilities/constructNewData.js
rename to content-scripts/modules/options/constructNewData.ts
index 3aac2c6..face0d9 100644
--- a/content-scripts/modules/utilities/constructNewData.js
+++ b/content-scripts/modules/options/constructNewData.ts
@@ -1,12 +1,19 @@
// Utility function to create data for `injectAllChanges()`
-export default function constructNewData(changes) {
+
+type Changes = {
+ [key: string]: chrome.storage.StorageChange;
+};
+
+export default function constructNewData(changes: Changes): {
+ [key: string]: unknown;
+} {
// Creates an array of objects from changes
// The value of each object is the new value
- const newValuesArray = Object.entries(changes).map((item) => {
- const itemKey = item[0];
- const itemValue = item[1]?.newValue;
- return { [itemKey]: itemValue };
- });
+ const newValuesArray = Object.entries(changes).map(
+ ([itemKey, itemValue]) => {
+ return { [itemKey]: itemValue?.newValue };
+ },
+ );
// Recreate a hash map to pass to `injectAllChanges()`
const newChangesData = Object.fromEntries(
diff --git a/content-scripts/modules/options/index.js b/content-scripts/modules/options/index.ts
similarity index 56%
rename from content-scripts/modules/options/index.js
rename to content-scripts/modules/options/index.ts
index e0e2d01..b280d8d 100644
--- a/content-scripts/modules/options/index.js
+++ b/content-scripts/modules/options/index.ts
@@ -1,9 +1,15 @@
import { useNavBar, hideShortcuts, changeFont } from "./options";
// Array of user preferences, passed to `injectAllChanges`
-export const userPreferences = ["navbar", "shortcuts", "autoLogin", "font"];
+export const userPreferences: string[] = [
+ "navbar",
+ "shortcuts",
+ "autoLogin",
+ "font",
+];
-export const injectAllChanges = (data) => {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const injectAllChanges = (data: any): void => {
Promise.all([
hideShortcuts(data?.shortcuts),
useNavBar(data?.navbar),
diff --git a/content-scripts/modules/options/options.js b/content-scripts/modules/options/options.ts
similarity index 68%
rename from content-scripts/modules/options/options.js
rename to content-scripts/modules/options/options.ts
index 3c340e4..0caba78 100644
--- a/content-scripts/modules/options/options.js
+++ b/content-scripts/modules/options/options.ts
@@ -1,16 +1,16 @@
-import addStyles from "./addStyles";
+import addStyles from "./addStyle";
import removeElement from "../utilities/removeElement";
-export const hideShortcuts = async (shortcuts) => {
+export const hideShortcuts = async (shortcuts: string): Promise => {
switch (shortcuts) {
case "on":
addStyles(
"se-hide-shortcuts",
`
- #caixa-atalhos{
- display: none
- }
- `,
+ #caixa-atalhos {
+ display: none;
+ }
+ `,
);
break;
@@ -20,16 +20,16 @@ export const hideShortcuts = async (shortcuts) => {
}
};
-export const changeFont = async (font) => {
+export const changeFont = async (font: string): Promise => {
switch (font) {
case "on":
addStyles(
"se-change-font",
`
- * {
- font-family: Roboto, sans-serif;
- }
- `,
+ * {
+ font-family: Roboto, sans-serif;
+ }
+ `,
);
break;
@@ -39,16 +39,16 @@ export const changeFont = async (font) => {
}
};
-export const useNavBar = async (navbar) => {
+export const useNavBar = async (navbar: string): Promise => {
switch (navbar) {
case "on":
addStyles(
"se-use-navbar",
`
- #colunaprincipal, #rodape, #ferramentas{
+ #colunaprincipal, #rodape, #ferramentas {
display: none !important;
}
- `,
+ `,
);
removeElement("#se-dont-use-navbar");
break;
@@ -62,7 +62,7 @@ export const useNavBar = async (navbar) => {
#colunaextra #caixa-campus {
display: none !important;
}
- `,
+ `,
);
removeElement("#se-use-navbar");
break;
diff --git a/content-scripts/modules/utilities/storage.js b/content-scripts/modules/utilities/storage.js
index 1f9e059..3370b3d 100644
--- a/content-scripts/modules/utilities/storage.js
+++ b/content-scripts/modules/utilities/storage.js
@@ -6,7 +6,6 @@
/*
- Get storage with storage.local
- k => "[key]" (String)
-- Don't need to throttle
*/
export const getStorage = async (k) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -22,10 +21,6 @@ export const getStorage = async (k) => {
/*--
- Set storage with storage.local
- kv => {key: value} (Single key value pair)
-- Throttle function to prevent hitting API limits
-- The maximum number of set, remove, or clear operations = 120
- - 1 min = 60000 ms
- - 60000 ms / 120 operations = 500 ms/operation
--*/
export const setStorage = async (kv) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
diff --git a/content-scripts/modules/utilities/throttle.js b/content-scripts/modules/utilities/throttle.js
deleted file mode 100644
index 6a1274b..0000000
--- a/content-scripts/modules/utilities/throttle.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*--
-- Simple utility throttle function with no return value
-- Example usage:
- const throttledFunc = throttle(function() {
- // This function will only be called at most once every 1000 milliseconds
- }, 1000)
---*/
-export default function throttle(func, limit) {
- let lastFunc;
- let lastRan;
- return function () {
- const args = arguments;
- if (!lastRan) {
- func.apply(this, args);
- lastRan = Date.now();
- } else {
- clearTimeout(lastFunc);
- lastFunc = setTimeout(
- function () {
- if (Date.now() - lastRan >= limit) {
- func.apply(this, args);
- lastRan = Date.now();
- }
- },
- limit - (Date.now() - lastRan),
- );
- }
- };
-}
diff --git a/content-scripts/pages/current_account_page.js b/content-scripts/pages/current_account_page.js
new file mode 100644
index 0000000..8098c41
--- /dev/null
+++ b/content-scripts/pages/current_account_page.js
@@ -0,0 +1,242 @@
+import { getPath } from "../modules/utilities/sigarra";
+
+// TODO: Use our table, create a card component for the balance and NIF and use them
+export const currentAccountPage = () => {
+ if (getPath() != "gpag_ccorrente_geral.conta_corrente_view") return;
+
+ const contaCorrente = document.getElementById(
+ "GPAG_CCORRENTE_GERAL_CONTA_CORRENTE_VIEW",
+ );
+
+ if (!contaCorrente) return;
+
+ let tabs = contaCorrente.querySelectorAll(".tab");
+
+ // merge "Crédito" and "Débito" collumns and remove collumns
+ tabs.forEach((tab, tab_index) => {
+ let creditColumnIndex;
+ let columnsToRemove = [];
+ let rows = [...tab.querySelectorAll("thead > tr, tbody > tr")];
+ if (rows.length == 0) return;
+
+ let headerTitles = document.querySelectorAll("ul.ui-tabs-nav > li > a");
+ headerTitles = [...headerTitles].map((title) => title.textContent);
+ let headerCells = rows[0].querySelectorAll("th");
+ headerCells.forEach((th, index) => {
+ if (th.innerHTML == "Débito") {
+ th.innerHTML = "Valor";
+ } else if (th.innerHTML == "Crédito") {
+ creditColumnIndex = index;
+ // Colspan
+ let colSpan = headerCells[0].colSpan;
+ if (colSpan > 1) creditColumnIndex += colSpan;
+ th.remove();
+ }
+
+ // Remove "Valor Pago" Column
+ if (th.innerHTML == "Valor Pago") {
+ columnsToRemove.push(index + headerCells[0].colSpan - 1);
+ th.remove();
+ }
+
+ // Remove "Valor em Falta" Column
+ if (th.innerHTML == "Valor em Falta") {
+ th.innerHTML = "";
+ th.colSpan = 1;
+ columnsToRemove.push(index + headerCells[0].colSpan - 1);
+ }
+
+ // Rename "Juros em Mora" Column
+ if (th.innerHTML == "Juros de Mora") {
+ th.innerHTML = "Juros";
+ // Remove "Juros de Mora" Column in "Juros de mora Proprinas" tab
+ if (headerTitles[tab_index] == "Juros de mora Propinas") {
+ columnsToRemove.push(index + headerCells[0].colSpan - 1);
+ th.remove();
+ }
+ }
+
+ // Remove "Débito em Falta" Column
+ if (th.innerHTML == "Débito em Falta") {
+ columnsToRemove.push(index + headerCells[0].colSpan - 1);
+ th.remove();
+ }
+
+ // Remove "Valor em Falta" Column
+ if (th.innerHTML == "Documento") {
+ th.colSpan = 1;
+ columnsToRemove.push(index + headerCells[0].colSpan - 1);
+ }
+
+ // Remove "Estado" Column
+ if (th.innerHTML == "Estado") {
+ columnsToRemove.push(index + headerCells[0].colSpan - 1);
+ th.remove();
+ }
+ });
+
+ rows.shift();
+
+ rows.forEach((row) => {
+ let cells = [...row.querySelectorAll("td")];
+ columnsToRemove.forEach((columnIndex) => {
+ if (!cells[0].classList.contains("credito")) {
+ cells[columnIndex].remove();
+ }
+ });
+ });
+
+ if (creditColumnIndex) {
+ rows.forEach((row, index) => {
+ let isGeralExtract = headerTitles[tab_index] == "Extrato Geral";
+
+ let cells = [...row.querySelectorAll("td")];
+ let debitCell = cells[creditColumnIndex - 1];
+
+ if (debitCell.innerHTML == " ") {
+ debitCell.innerHTML = "";
+ debitCell.classList.add("n");
+ if (isGeralExtract) {
+ debitCell.classList.add("positive");
+ debitCell.innerHTML = "+";
+ }
+ debitCell.innerHTML += cells[creditColumnIndex].innerHTML;
+ } else {
+ if (isGeralExtract) {
+ debitCell.classList.add("negative");
+ debitCell.innerHTML = "-" + debitCell.innerHTML;
+ }
+ }
+ cells[creditColumnIndex].remove();
+ if (cells[0].classList.contains("credito")) {
+ //remove "Multibanco - SIBS" row
+ //TODO: adicionar data a "pago em"
+
+ //change the last cell of the last row to the value of the last cell of the current row
+ let lastRowCells = rows[index - 1].querySelectorAll("td");
+
+ let document_file =
+ cells[cells.length - 1].querySelector("a");
+ if (document_file) {
+ lastRowCells[lastRowCells.length - 1].innerHTML = "";
+ lastRowCells[lastRowCells.length - 1].appendChild(
+ document_file,
+ );
+ lastRowCells[
+ lastRowCells.length - 1
+ ].style.paddingRight = "0.6rem";
+ }
+ row.remove();
+ }
+ });
+ }
+ });
+
+ // Change "Data" collumn position in "Extrato Geral" tab
+ const geralExtractTable = document.querySelector("#tab_extracto_geral");
+ if (geralExtractTable) {
+ geralExtractTable.querySelectorAll("tr").forEach((row) => {
+ let cells = [
+ ...row.querySelectorAll("td"),
+ ...row.querySelectorAll("th"),
+ ];
+ // len = cells.length;
+ row.insertBefore(cells[1], cells[0]);
+ });
+ }
+
+ // Switch "Refência" action button to the right
+ if (tabs.length > 0) {
+ tabs[0].querySelectorAll("tbody > tr").forEach((row) => {
+ let cells = [
+ ...row.querySelectorAll("td"),
+ ...row.querySelectorAll("th"),
+ ];
+ let len = cells.length;
+ row.insertBefore(cells[len - 1], cells[len - 2]);
+ });
+ }
+
+ let statusProperties = {
+ Pago: {
+ class: "success",
+ text: "Pago",
+ },
+ "Não pago mas prazo ainda não foi excedido": {
+ class: "pending",
+ text: "Pendente",
+ },
+ Anulado: {
+ class: "cancelled",
+ text: "Anulado",
+ },
+ "Prazo excedido": {
+ class: "danger",
+ text: "Excedido",
+ },
+ };
+
+ // Improve the status badge
+ tabs.forEach((tab) => {
+ tab.querySelectorAll("tbody > tr").forEach((row) => {
+ let cells = [...row.querySelectorAll("td")];
+ if (cells.length == 0) return;
+
+ // Get title atriuibute from the first cell
+ const cellStatus =
+ cells[0].querySelector("img")?.getAttribute("title") ?? null;
+ if (cellStatus == null) return;
+
+ // Creating a new status badge
+ let statusDiv = document.createElement("div");
+ statusDiv.innerHTML = statusProperties[cellStatus].text;
+ statusDiv.classList.add("badge");
+ statusDiv.classList.add(
+ "badge-" + statusProperties[cellStatus].class,
+ );
+ statusDiv.title = cellStatus;
+
+ cells[0].innerHTML = statusDiv.outerHTML;
+ });
+ });
+
+ // Remove "Movimentos" h2
+ contaCorrente.previousElementSibling.remove();
+
+ // Create Balance and NIF cards
+ const saldo = document.querySelector(
+ ".formulario #span_saldo_total",
+ ).textContent;
+ const saldoCard = document.createElement("div");
+ saldoCard.classList.add("card");
+ const title = document.createElement("p");
+ title.innerHTML = "Saldo";
+ const saldoValue = document.createElement("h3");
+ saldoValue.innerHTML = saldo + "€";
+ saldoCard.appendChild(title);
+ saldoCard.appendChild(saldoValue);
+
+ const nif = Array.from(
+ document.querySelectorAll(".formulario .formulario-legenda"),
+ ).filter((el) => el.innerHTML.includes("N.I.F."))[0].nextElementSibling
+ .innerHTML;
+ const nifCard = document.createElement("div");
+ nifCard.classList.add("card");
+ const nifTitle = document.createElement("p");
+ nifTitle.innerHTML = "NIF";
+ const nifValue = document.createElement("h3");
+ nifValue.innerHTML = nif;
+ nifCard.appendChild(nifTitle);
+ nifCard.appendChild(nifValue);
+
+ let accountDetails = document.createElement("div");
+ accountDetails.style.display = "flex";
+ accountDetails.style.gap = "1rem";
+ accountDetails.style.marginBottom = "0.5rem";
+
+ accountDetails.appendChild(saldoCard);
+ accountDetails.appendChild(nifCard);
+ contaCorrente.insertBefore(accountDetails, contaCorrente.firstChild);
+
+ return;
+};