Files
geointel/src/client/components/AppTile.tsx
T
Jens d5ea270329
GeoIntel release gates / Compile, test, contracts and builds (push) Canceled after 0s
GeoIntel release gates / Python and npm vulnerability policy (push) Canceled after 0s
GeoIntel release gates / GIS image, SBOM and container scan (push) Canceled after 0s
Update GeoIntel project files
2026-07-25 23:43:08 +02:00

128 lines
3.2 KiB
TypeScript

import { ArrowUpRight, Box, Globe2 } from "lucide-react";
import type { DockApp, Favorite } from "../../shared/contracts";
import { effectiveAppUrl, favoriteIconUrl } from "../../shared/domain";
import { ServiceIcon } from "./ServiceIcon";
const serviceDescriptions: Array<[RegExp, string]> = [
[/plex/i, "Media server & streaming"],
[/jellyfin/i, "De vrije software mediaserver"],
[/sonarr/i, "PVR voor tv-series"],
[/radarr/i, "PVR voor films"],
[/home.?assistant|ha-core/i, "Centrale automatisering"],
[/adguard|pihole|pi-hole/i, "Netwerkbeveiliging & filter"],
[/nextcloud/i, "Bestandshosting"],
[/gitea|github|gitlab/i, "Code en samenwerking"],
[/immich/i, "Foto's en herinneringen"],
[/paperless/i, "Documenten en archief"],
];
function describeService(app: DockApp, categoryName: string) {
const haystack = `${app.displayName} ${app.technicalName}`;
return (
serviceDescriptions.find(([pattern]) => pattern.test(haystack))?.[1] ??
`${categoryName} service`
);
}
function statusLabel(status: DockApp["status"]) {
return status === "online"
? "Online"
: status === "offline"
? "Offline"
: "Status onbekend";
}
export function AppTile({
app,
externalAccess,
showStatus,
index,
categoryName,
variant = "standard",
}: {
app: DockApp;
externalAccess: boolean;
showStatus: boolean;
index: number;
categoryName: string;
variant?: "standard" | "feature";
}) {
const url = effectiveAppUrl(app, externalAccess);
const content = (
<>
<ServiceIcon
name={app.displayName}
icon={app.icon}
hint={`${app.technicalName} ${app.image ?? ""}`}
index={index}
/>
<span className="tile-copy">
<strong>{app.displayName}</strong>
<small>{describeService(app, categoryName)}</small>
</span>
{showStatus && (
<span className={`tile-status ${app.status}`}>
<span /> {statusLabel(app.status)}
</span>
)}
<ArrowUpRight className="tile-arrow" size={17} aria-hidden="true" />
</>
);
return url ? (
<a
className={`app-tile ${variant}`}
href={url}
target="_blank"
rel="noopener noreferrer"
data-testid="app-tile"
>
{content}
</a>
) : (
<div
className={`app-tile ${variant} disabled`}
aria-label={`${app.displayName} heeft geen URL`}
>
{content}
</div>
);
}
export function FavoriteTile({
favorite,
index,
}: {
favorite: Favorite;
index: number;
}) {
return (
<a
className="app-tile"
href={favorite.url}
target="_blank"
rel="noopener noreferrer"
data-testid="favorite-tile"
>
<ServiceIcon
name={favorite.name}
icon={favoriteIconUrl(favorite)}
index={index + 2}
/>
<span className="tile-copy">
<strong>{favorite.name}</strong>
<small>Favoriet</small>
</span>
<Globe2 className="status-icon" size={15} aria-hidden="true" />
<ArrowUpRight className="tile-arrow" size={17} aria-hidden="true" />
</a>
);
}
export function EmptyIcon() {
return (
<div className="empty-icon" aria-hidden="true">
<Box size={28} />
</div>
);
}