feat(import): bulk tool import (CSV/JSON/YAML) for admins; NetBox-style help button
Build & Push Docker Image / build (push) Successful in 2m49s

- Add POST /admin/tools/import with format auto-detect, CSV delimiters
  (comma/semicolon/tab), per-row validation via CreateToolBody, bulk insert,
  audit log entries per imported tool; gated by new 'tool-import' feature
  flag (premium/enterprise; admins always pass)
- Add tool-import-dialog UI (format tabs, delimiter select, textarea, file
  upload, result/error list) behind hasFeature('tool-import')
- Replace FieldHelp question marks and bare GuideHelp links with a NetBox-style
  'Hilfe/Help' outline button (HelpCircle + text) in form headers only
- Sync locales to 482 keys per language (de/en), update handbook docs
  (administration import section, index/plaene feature tables), regenerate
  API client + zod schemas, add yaml dependency
This commit is contained in:
opencode
2026-08-04 23:09:11 +02:00
parent d47db6d386
commit 8f2fd89847
38 changed files with 870 additions and 159 deletions
+7 -13
View File
@@ -60,7 +60,6 @@ import {
} from "@/components/ui/alert-dialog";
import { customFetch } from "@workspace/api-client-react";
import { recordRecentTool } from "@/lib/recent-tools";
import { FieldHelp } from "@/components/field-help";
import { GuideHelp } from "@/components/guide-help";
const ratingSchema = z.object({
@@ -616,9 +615,8 @@ export default function ToolDetail() {
<Dialog open={costDialogOpen} onOpenChange={(o) => { if (!o) setCostDialogOpen(false); }}>
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<DialogTitle>
{editCost ? t("detail.edit") + " " + t("detail.costs") : t("detail.addCost")}
<GuideHelp guide="kosten" label={t("detail.costs")} />
</DialogTitle>
<DialogDescription>{t("detail.costDialogSub")}</DialogDescription>
</DialogHeader>
@@ -768,12 +766,12 @@ export default function ToolDetail() {
{isReviewFormOpen && (
<Card className="border-primary shadow-sm">
<CardHeader>
<CardTitle className="flex items-center gap-2">
{t("detail.addReview")}
<GuideHelp guide="bewerten" label={t("detail.addReview")} />
</CardTitle>
<CardDescription>{t("detail.shareExperience", { name: tool.name })}</CardDescription>
<CardHeader className="flex flex-row items-start justify-between gap-4">
<div className="space-y-1.5">
<CardTitle>{t("detail.addReview")}</CardTitle>
<CardDescription>{t("detail.shareExperience", { name: tool.name })}</CardDescription>
</div>
<GuideHelp guide="bewerten" label={t("detail.addReview")} />
</CardHeader>
<CardContent>
<Form {...form}>
@@ -786,7 +784,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.usefulness")}
<FieldHelp schema="RatingInput" field="usefulness">{t("detail.usefulness")}</FieldHelp>
</FormLabel>
<div className="py-2">
<RatingStars
@@ -807,7 +804,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.usability")}
<FieldHelp schema="RatingInput" field="usability">{t("detail.usability")}</FieldHelp>
</FormLabel>
<div className="py-2">
<RatingStars
@@ -830,7 +826,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.commentOptional")}
<FieldHelp schema="RatingInput" field="comment">{t("detail.commentLabel")}</FieldHelp>
</FormLabel>
<FormControl>
<Textarea
@@ -851,7 +846,6 @@ export default function ToolDetail() {
<FormItem>
<FormLabel className="inline-flex items-center gap-1.5">
{t("detail.nameOptional")}
<FieldHelp schema="RatingInput" field="reviewerName">{t("detail.nameLabel")}</FieldHelp>
</FormLabel>
<FormControl>
<Input placeholder={t("detail.anonymousPlaceholder")} {...field} />