feat(import): bulk tool import (CSV/JSON/YAML) for admins; NetBox-style help button
Build & Push Docker Image / build (push) Successful in 2m49s
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:
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user