mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 13:01:00 +00:00
chore(release): v1.4.15
Bumps version to 1.4.15 and adds the corresponding CHANGELOG entry covering backup completeness, achievements UI, onboarding tour, doctor-report v2, auto-deploy on GHCR push, empty-states sweep, notification + integration status UI, AI hallucination guardrails, mobile UX fixes, and the BD-Zielbereich percentage fix. Includes the formatting drift left over by the v1.4.15 development phases (Prettier sweep on touched files) and a typecheck fix in `dashboard-layout.test.ts` so `pnpm typecheck`, `pnpm lint`, `pnpm format:check`, `pnpm test`, and `pnpm test:integration` all pass green on the release tag. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1b1214bcb3
commit
4dcaa08b42
32 files changed
+639
-295
No files matched your search
@@ -64,9 +64,7 @@ describe("BackupsSection — empty state", () => {
|
||||
it("includes the localized title and description", () => {
|
||||
const html = render();
|
||||
expect(html).toContain("No backups yet");
|
||||
expect(html).toContain(
|
||||
"Backups run automatically every Sunday at 03:00",
|
||||
);
|
||||
expect(html).toContain("Backups run automatically every Sunday at 03:00");
|
||||
});
|
||||
|
||||
it("exposes the Backup-now CTA inside the empty card", () => {
|
||||
|
||||
@@ -75,9 +75,7 @@ describe("UserManagementSection — empty state", () => {
|
||||
it("carries the localized title and description", () => {
|
||||
const html = render();
|
||||
expect(html).toContain("No users in this view");
|
||||
expect(html).toContain(
|
||||
"There are no users matching the current filter.",
|
||||
);
|
||||
expect(html).toContain("There are no users matching the current filter.");
|
||||
});
|
||||
|
||||
it("does not render the reset CTA when the default filter is active", () => {
|
||||
|
||||
@@ -296,105 +296,109 @@ export function UserManagementSection() {
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Desktop table: kept verbatim — only the wrapping
|
||||
<>
|
||||
{/* Desktop table: kept verbatim — only the wrapping
|
||||
`md:block hidden` swaps it out for the card-list below at
|
||||
`< md`. Hiding the table-only wrapper (instead of just
|
||||
the cells) saves DOM weight on mobile too. */}
|
||||
<div className="mt-4 hidden overflow-x-auto md:block">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-muted-foreground border-b text-xs">
|
||||
<th className="px-3 py-2 text-left font-medium">
|
||||
{t("admin.users")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left font-medium">
|
||||
{t("admin.userEmail")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-center font-medium">
|
||||
{t("admin.userRole")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-center font-medium">
|
||||
{t("admin.userPasskeys")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right font-medium">
|
||||
{t("admin.userCreated")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right font-medium">
|
||||
{t("admin.userActions")}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{filteredUsers.map((u, i) => (
|
||||
<tr key={u.id} className={i % 2 === 0 ? "bg-muted/30" : ""}>
|
||||
<td className="px-3 py-2 font-medium">{u.username}</td>
|
||||
<td className="text-muted-foreground px-3 py-2 text-xs">
|
||||
{u.email || "—"}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<Badge
|
||||
variant={u.role === "ADMIN" ? "default" : "secondary"}
|
||||
className="text-xs"
|
||||
>
|
||||
{u.role}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">{u.passkeyCount}</td>
|
||||
<td className="text-muted-foreground px-3 py-2 text-right text-xs whitespace-nowrap">
|
||||
{formatDate(u.createdAt)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
{renderUserActions(u)}
|
||||
</div>
|
||||
</td>
|
||||
<div className="mt-4 hidden overflow-x-auto md:block">
|
||||
<table className="w-full text-sm">
|
||||
<thead>
|
||||
<tr className="text-muted-foreground border-b text-xs">
|
||||
<th className="px-3 py-2 text-left font-medium">
|
||||
{t("admin.users")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-left font-medium">
|
||||
{t("admin.userEmail")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-center font-medium">
|
||||
{t("admin.userRole")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-center font-medium">
|
||||
{t("admin.userPasskeys")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right font-medium">
|
||||
{t("admin.userCreated")}
|
||||
</th>
|
||||
<th className="px-3 py-2 text-right font-medium">
|
||||
{t("admin.userActions")}
|
||||
</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</thead>
|
||||
<tbody className="divide-border divide-y">
|
||||
{filteredUsers.map((u, i) => (
|
||||
<tr key={u.id} className={i % 2 === 0 ? "bg-muted/30" : ""}>
|
||||
<td className="px-3 py-2 font-medium">{u.username}</td>
|
||||
<td className="text-muted-foreground px-3 py-2 text-xs">
|
||||
{u.email || "—"}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
<Badge
|
||||
variant={u.role === "ADMIN" ? "default" : "secondary"}
|
||||
className="text-xs"
|
||||
>
|
||||
{u.role}
|
||||
</Badge>
|
||||
</td>
|
||||
<td className="px-3 py-2 text-center">
|
||||
{u.passkeyCount}
|
||||
</td>
|
||||
<td className="text-muted-foreground px-3 py-2 text-right text-xs whitespace-nowrap">
|
||||
{formatDate(u.createdAt)}
|
||||
</td>
|
||||
<td className="px-3 py-2 text-right">
|
||||
<div className="flex items-center justify-end gap-1">
|
||||
{renderUserActions(u)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Mobile card list: each user renders as a self-contained
|
||||
{/* Mobile card list: each user renders as a self-contained
|
||||
card with the meta line + a flex-wrapping action row.
|
||||
All four actions stay visible and tap-targetable; nothing
|
||||
is hidden behind a horizontal scroll. */}
|
||||
<ul
|
||||
className="mt-4 space-y-2 md:hidden"
|
||||
data-testid="admin-users-mobile-list"
|
||||
>
|
||||
{filteredUsers.map((u) => (
|
||||
<li
|
||||
key={u.id}
|
||||
className="bg-muted/30 border-border rounded-lg border p-3"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate font-medium">{u.username}</span>
|
||||
<Badge
|
||||
variant={u.role === "ADMIN" ? "default" : "secondary"}
|
||||
className="text-[10px]"
|
||||
>
|
||||
{u.role}
|
||||
</Badge>
|
||||
<ul
|
||||
className="mt-4 space-y-2 md:hidden"
|
||||
data-testid="admin-users-mobile-list"
|
||||
>
|
||||
{filteredUsers.map((u) => (
|
||||
<li
|
||||
key={u.id}
|
||||
className="bg-muted/30 border-border rounded-lg border p-3"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="truncate font-medium">
|
||||
{u.username}
|
||||
</span>
|
||||
<Badge
|
||||
variant={u.role === "ADMIN" ? "default" : "secondary"}
|
||||
className="text-[10px]"
|
||||
>
|
||||
{u.role}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-muted-foreground truncate text-xs">
|
||||
{u.email || "—"}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-[11px]">
|
||||
{t("admin.userPasskeys")}: {u.passkeyCount} ·{" "}
|
||||
{formatDate(u.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-muted-foreground truncate text-xs">
|
||||
{u.email || "—"}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-[11px]">
|
||||
{t("admin.userPasskeys")}: {u.passkeyCount} ·{" "}
|
||||
{formatDate(u.createdAt)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-1">
|
||||
{renderUserActions(u)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
<div className="mt-2 flex flex-wrap items-center gap-1">
|
||||
{renderUserActions(u)}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
)
|
||||
) : (
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
|
||||
Reference in new issue
Block a user