Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 26 additions & 37 deletions src/components/Notifications.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { faClose, faEllipsisH, faInbox, faPowerOff, faServer, faTrashCan } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { memo, type RefObject, useCallback, useRef, type useState } from "react";
import { memo, type RefObject, useCallback, useRef } from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router";
import { useShallow } from "zustand/react/shallow";
Expand All @@ -12,10 +12,6 @@ import Button from "./Button.js";
import ConfirmButton from "./ConfirmButton.js";
import SourceDot from "./SourceDot.js";

type NotificationsProps = {
setShowNotifications: ReturnType<typeof useState<boolean>>[1];
};

type SourceNotificationsProps = { sourceIdx: number; readyState: number };

type NotificationProps = {
Expand Down Expand Up @@ -97,43 +93,36 @@ const SourceNotifications = memo(({ sourceIdx, readyState }: SourceNotifications
);
});

const Notifications = memo(({ setShowNotifications }: NotificationsProps) => {
const Notifications = memo(() => {
const { t } = useTranslation("common");
const readyStates = useAppStore((state) => state.readyStates);
const clearAllNotifications = useAppStore((state) => state.clearAllNotifications);

return (
<div
className="drawer-side justify-items-end z-99"
style={{ pointerEvents: "auto", visibility: "visible", overflowY: "auto", opacity: "100%" }}
>
{/** biome-ignore lint/a11y/noStaticElementInteractions: special case */}
<span className="drawer-overlay" onClick={() => setShowNotifications(false)} />
<aside className="bg-base-100 min-h-screen w-80" style={{ translate: "0%" }}>
<div className="flex items-center gap-2 p-2">
<FontAwesomeIcon icon={faInbox} />
<span className="font-semibold text-md">{t("notifications")}</span>
</div>
<ul className="menu w-full px-1 py-0">
{API_URLS.map((_v, idx) => (
// biome-ignore lint/suspicious/noArrayIndexKey: static
<SourceNotifications key={`${idx}`} sourceIdx={idx} readyState={readyStates[idx]} />
))}
{MULTI_INSTANCE && (
<ConfirmButton
className="btn btn-sm btn-warning btn-outline mt-5"
onClick={clearAllNotifications}
title={t("clear_all")}
modalDescription={t("dialog_confirmation_prompt")}
modalCancelLabel={t("cancel")}
>
<FontAwesomeIcon icon={faTrashCan} />
{t("clear_all")}
</ConfirmButton>
)}
</ul>
</aside>
</div>
<>
<div className="flex items-center gap-2 p-2">
<FontAwesomeIcon icon={faInbox} />
<span className="font-semibold text-md">{t("notifications")}</span>
</div>
<ul className="menu w-full px-1 py-0">
{API_URLS.map((_v, idx) => (
// biome-ignore lint/suspicious/noArrayIndexKey: static
<SourceNotifications key={`${idx}`} sourceIdx={idx} readyState={readyStates[idx]} />
))}
{MULTI_INSTANCE && (
<ConfirmButton
className="btn btn-sm btn-warning btn-outline mt-5"
onClick={clearAllNotifications}
title={t("clear_all")}
modalDescription={t("dialog_confirmation_prompt")}
modalCancelLabel={t("cancel")}
>
<FontAwesomeIcon icon={faTrashCan} />
{t("clear_all")}
</ConfirmButton>
)}
</ul>
</>
);
});

Expand Down
24 changes: 17 additions & 7 deletions src/components/navbar/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { memo, useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, NavLink, type NavLinkRenderProps } from "react-router";
import { useAppStore } from "../../store.js";
import Button from "../Button.js";
import Notifications from "../Notifications.js";
import LanguageSwitcher from "./LanguageSwitcher.js";
import PermitJoinButton from "./PermitJoinButton.js";
Expand Down Expand Up @@ -109,14 +108,14 @@ const NavBar = memo(({ showNotifications, setShowNotifications }: NavBarProps) =
<PermitJoinButton />
<LanguageSwitcher />
<ThemeSwitcher />
<Button<boolean> className="drawer-button btn" item={!showNotifications} onClick={setShowNotifications}>
<label htmlFor="notifications-drawer" className="drawer-button btn" onClick={() => setShowNotifications(!showNotifications)}>
<FontAwesomeIcon icon={faInbox} />
{notificationsAlert[0] ? (
<span className="status status-primary animate-bounce" />
) : notificationsAlert[1] ? (
<span className="status status-error animate-bounce" />
) : null}
</Button>
</label>
</div>
</div>
);
Expand All @@ -126,10 +125,21 @@ const NavBarWithNotifications = memo(() => {
const [showNotifications, setShowNotifications] = useState(false);

return (
<>
<NavBar showNotifications={showNotifications} setShowNotifications={setShowNotifications} />
{showNotifications && <Notifications setShowNotifications={setShowNotifications} />}
</>
<div className="drawer drawer-end w-auto">
<input id="notifications-drawer" type="checkbox" className="drawer-toggle" />
<div className="drawer-content">
<NavBar showNotifications={showNotifications} setShowNotifications={setShowNotifications} />
</div>
<div className="drawer-side">
<label
htmlFor="notifications-drawer"
aria-label="close sidebar"
className="drawer-overlay"
onClick={() => setShowNotifications(false)}
/>
<aside className="bg-base-100 min-h-screen w-80">{showNotifications && <Notifications />}</aside>
</div>
</div>
);
});

Expand Down
105 changes: 42 additions & 63 deletions src/components/table/TableFiltersDrawer.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { faFilter, faRotateLeft } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import type { Column } from "@tanstack/react-table";
import { Fragment, type JSX, useEffect } from "react";
import { Fragment, type JSX } from "react";
import { useTranslation } from "react-i18next";
import type { useTable } from "../../hooks/useTable.js";
import { MULTI_INSTANCE } from "../../store.js";
Expand Down Expand Up @@ -214,74 +214,53 @@ function BooleanFilter<T>({ column, label }: FilterProps<T>) {

interface TableFiltersDrawerProps<T> extends Pick<ReturnType<typeof useTable<T>>, "resetFilters"> {
columns: Column<T>[];
onClose: () => void;
}

export default function TableFiltersDrawer<T>({ columns, resetFilters, onClose }: TableFiltersDrawerProps<T>): JSX.Element {
export default function TableFiltersDrawer<T>({ columns, resetFilters }: TableFiltersDrawerProps<T>): JSX.Element {
const { t } = useTranslation("common");

useEffect(() => {
const close = (e: KeyboardEvent) => {
if (e.key === "Escape") {
e.stopPropagation();
onClose();
}
};

window.addEventListener("keydown", close);

return () => window.removeEventListener("keydown", close);
}, [onClose]);

return (
<div
className="drawer-side justify-items-end z-99"
style={{ pointerEvents: "auto", visibility: "visible", overflowY: "auto", opacity: "100%" }}
>
{/** biome-ignore lint/a11y/noStaticElementInteractions: special case */}
<span className="drawer-overlay" onClick={onClose} />
<aside className="bg-base-100 min-h-screen w-80" style={{ translate: "0%" }}>
<div className="flex items-center gap-2 p-2">
<FontAwesomeIcon icon={faFilter} />
<span className="font-semibold text-md">{t("advanced_search")}</span>
</div>
<div className="flex flex-col gap-2 px-2 pb-2">
{columns.map((col) => {
const meta = col.columnDef.meta;

if (!meta || !meta.filterVariant) {
return null;
}
<>
<div className="flex items-center gap-2 p-2">
<FontAwesomeIcon icon={faFilter} />
<span className="font-semibold text-md">{t("advanced_search")}</span>
</div>
<div className="flex flex-col gap-2 px-2 pb-2">
{columns.map((col) => {
const meta = col.columnDef.meta;

const label = typeof col.columnDef.header === "string" && col.columnDef.header ? col.columnDef.header : t(col.id);
if (!meta || !meta.filterVariant) {
return null;
}

return (
<Fragment key={col.id}>
<div className="flex flex-row w-full gap-2">
{meta.filterVariant === "range" ? (
<RangeFilter column={col} label={label} />
) : meta.filterVariant === "select" ? (
<SelectFilter column={col} label={label} />
) : meta.filterVariant === "boolean" ? (
<BooleanFilter column={col} label={label} />
) : meta.filterVariant === "arrSelect" ? (
<ArrSelectFilter column={col} label={label} />
) : (
<TextFilter column={col} label={label} />
)}
</div>
{meta.tooltip && <p className="label text-xs ml-auto">{meta.tooltip}</p>}
</Fragment>
);
})}
</div>
<div className="flex flex-row justify-end gap-2 p-3">
<Button className="btn btn-sm btn-warning btn-outline mt-5" onClick={resetFilters}>
<FontAwesomeIcon icon={faRotateLeft} />
{t("reset")}
</Button>
</div>
</aside>
</div>
const label = typeof col.columnDef.header === "string" && col.columnDef.header ? col.columnDef.header : t(col.id);

return (
<Fragment key={col.id}>
<div className="flex flex-row w-full gap-2">
{meta.filterVariant === "range" ? (
<RangeFilter column={col} label={label} />
) : meta.filterVariant === "select" ? (
<SelectFilter column={col} label={label} />
) : meta.filterVariant === "boolean" ? (
<BooleanFilter column={col} label={label} />
) : meta.filterVariant === "arrSelect" ? (
<ArrSelectFilter column={col} label={label} />
) : (
<TextFilter column={col} label={label} />
)}
</div>
{meta.tooltip && <p className="label text-xs ml-auto">{meta.tooltip}</p>}
</Fragment>
);
})}
</div>
<div className="flex flex-row justify-end gap-2 p-3">
<Button className="btn btn-sm btn-warning btn-outline mt-5" onClick={resetFilters}>
<FontAwesomeIcon icon={faRotateLeft} />
{t("reset")}
</Button>
</div>
</>
);
}
59 changes: 37 additions & 22 deletions src/components/table/TableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,43 @@ export default function TableHeader<T>({ table, resetFilters, globalFilter, colu
<FontAwesomeIcon icon={faClose} />
</Button>
</div>
<div className="join">
<Button<boolean>
item={!drawerOpen}
onClick={setDrawerOpen}
className="btn btn-sm btn-info btn-outline join-item"
title={t("advanced_search")}
>
<FontAwesomeIcon icon={faFilter} />
{t("advanced_search")}
{activeFiltersCount > 0 ? <span className="badge badge-info badge-xs">{activeFiltersCount}</span> : null}
</Button>
<Button<void>
className="btn btn-sm btn-square btn-warning btn-outline join-item"
title={t("reset")}
onClick={() => {
resetFilters();
}}
disabled={activeFiltersCount === 0}
>
<FontAwesomeIcon icon={faRotateLeft} />
</Button>
<div className="drawer drawer-end w-auto">
<input id="table-filters-drawer" type="checkbox" className="drawer-toggle" />
<div className="drawer-content">
<div className="join">
<label
htmlFor="table-filters-drawer"
className="drawer-button btn btn-sm btn-info btn-outline join-item"
onClick={() => setDrawerOpen(!drawerOpen)}
title={t("advanced_search")}
>
<FontAwesomeIcon icon={faFilter} />
{t("advanced_search")}
{activeFiltersCount > 0 ? <span className="badge badge-info badge-xs">{activeFiltersCount}</span> : null}
</label>
<Button<void>
className="btn btn-sm btn-square btn-warning btn-outline join-item"
title={t("reset")}
onClick={() => {
resetFilters();
}}
disabled={activeFiltersCount === 0}
>
<FontAwesomeIcon icon={faRotateLeft} />
</Button>
</div>
</div>
<div className="drawer-side">
<label
htmlFor="table-filters-drawer"
aria-label="close sidebar"
className="drawer-overlay"
onClick={() => setDrawerOpen(false)}
/>
<aside className="bg-base-100 min-h-screen w-80">
{drawerOpen && <TableFiltersDrawer columns={columns} resetFilters={resetFilters} />}
</aside>
</div>
</div>
<div className="">
<span className="label">
Expand Down Expand Up @@ -96,7 +112,6 @@ export default function TableHeader<T>({ table, resetFilters, globalFilter, colu
)}
</div>
)}
{drawerOpen && <TableFiltersDrawer columns={columns} resetFilters={resetFilters} onClose={() => setDrawerOpen(false)} />}
</>
);
}