diff --git a/frontend/src/components/DownloadCenter.tsx b/frontend/src/components/DownloadCenter.tsx
index d191c5d..074fff0 100644
--- a/frontend/src/components/DownloadCenter.tsx
+++ b/frontend/src/components/DownloadCenter.tsx
@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import {
@@ -275,6 +275,56 @@ function QuotaModal({ userId, email, onClose }: { userId: number; email: string;
);
}
+// Pick ANY user to set a download quota for — even one who hasn't downloaded anything yet (the
+// footprint list below only shows users who already have files).
+function QuotaUserPicker({ onPick }: { onPick: (u: { id: number; email: string }) => void }) {
+ const { t } = useTranslation();
+ const [q, setQ] = useState("");
+ const [open, setOpen] = useState(false);
+ const users = useQuery({ queryKey: ["admin-users"], queryFn: api.adminUsers });
+ const list = (users.data ?? []).filter((u) => !u.is_demo);
+ const filtered = useMemo(() => {
+ const s = q.trim().toLowerCase();
+ const base = s
+ ? list.filter((u) => u.email.toLowerCase().includes(s) || (u.display_name ?? "").toLowerCase().includes(s))
+ : list;
+ return base.slice(0, 8);
+ }, [q, list]);
+ return (
+
+
{
+ setQ(e.target.value);
+ setOpen(true);
+ }}
+ onFocus={() => setOpen(true)}
+ onBlur={() => setTimeout(() => setOpen(false), 150)}
+ placeholder={t("downloads.admin.quotaPickPlaceholder")}
+ className={inputCls}
+ />
+ {open && filtered.length > 0 && (
+
+ {filtered.map((u) => (
+
+ ))}
+
+ )}
+
+ );
+}
+
function AdminSystem() {
const { t } = useTranslation();
const storage = useLiveQuery(["admin-storage"], api.adminDownloadStorage, { intervalMs: 5000 });
@@ -298,9 +348,13 @@ function AdminSystem() {
)}
- {s && s.per_user.length > 0 && (
+ {s && (
-
{t("downloads.admin.perUser")}
+
+
{t("downloads.admin.perUser")}
+ {/* Set a quota for any user, regardless of whether they've downloaded anything. */}
+
+
{s.per_user.map((u) => (
@@ -315,6 +369,9 @@ function AdminSystem() {
))}
+ {s.per_user.length === 0 && (
+
{t("downloads.admin.noFootprint")}
+ )}
)}
diff --git a/frontend/src/i18n/locales/de/downloads.json b/frontend/src/i18n/locales/de/downloads.json
index 6cb36a9..13cc467 100644
--- a/frontend/src/i18n/locales/de/downloads.json
+++ b/frontend/src/i18n/locales/de/downloads.json
@@ -157,7 +157,9 @@
"reset": "Auf Standard zurücksetzen",
"save": "Speichern",
"custom": "individuell",
- "default": "Standard"
+ "default": "Standard",
+ "quotaPickPlaceholder": "Kontingent für einen Nutzer festlegen…",
+ "noFootprint": "Noch keine Downloads."
},
"cols": {
"title": "Titel",
diff --git a/frontend/src/i18n/locales/en/downloads.json b/frontend/src/i18n/locales/en/downloads.json
index c46fd2d..f96cd82 100644
--- a/frontend/src/i18n/locales/en/downloads.json
+++ b/frontend/src/i18n/locales/en/downloads.json
@@ -157,7 +157,9 @@
"reset": "Reset to default",
"save": "Save",
"custom": "custom",
- "default": "default"
+ "default": "default",
+ "quotaPickPlaceholder": "Set a user's quota…",
+ "noFootprint": "No downloads yet."
},
"cols": {
"title": "Title",
diff --git a/frontend/src/i18n/locales/hu/downloads.json b/frontend/src/i18n/locales/hu/downloads.json
index 7362d83..1a097ea 100644
--- a/frontend/src/i18n/locales/hu/downloads.json
+++ b/frontend/src/i18n/locales/hu/downloads.json
@@ -157,7 +157,9 @@
"reset": "Visszaállítás alapértékre",
"save": "Mentés",
"custom": "egyedi",
- "default": "alapértelmezett"
+ "default": "alapértelmezett",
+ "quotaPickPlaceholder": "Kvóta beállítása egy felhasználónak…",
+ "noFootprint": "Még nincs letöltés."
},
"cols": {
"title": "Cím",