feat(34-02): extract hardcoded strings from thread/candidate components

- CandidateCard: replace all hardcoded titles and badge text with t()
- CandidateListItem: add useTranslation, replace winner/delete/open labels and +/- Notes badge
- CandidateForm: add useTranslation, replace all form labels, placeholders, validation errors, submit button
- ComparisonTable: move STATUS_LABELS inside component with t(), replace all ATTRIBUTE_ROWS labels, View button, impact row labels
- StatusBadge: refactor STATUS_CONFIG to STATUS_ICONS + runtime STATUS_LABELS via t()
- CreateThreadModal: replace title, thread name label, category label, placeholder, cancel/submit buttons, error messages
- AddToThreadModal: replace modal titles, labels, placeholders, back/cancel/submit buttons, error messages
- threads.json: extend candidateForm with category, notes, pros, cons, product link labels and all placeholders
This commit is contained in:
2026-04-18 13:44:26 +02:00
parent c5af1247c0
commit 6fd8874970
8 changed files with 158 additions and 84 deletions

View File

@@ -1,13 +1,14 @@
import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { LucideIcon } from "../lib/iconData";
const STATUS_CONFIG = {
researching: { icon: "search", label: "Researching" },
ordered: { icon: "truck", label: "Ordered" },
arrived: { icon: "check", label: "Arrived" },
const STATUS_ICONS = {
researching: "search",
ordered: "truck",
arrived: "check",
} as const;
type CandidateStatus = keyof typeof STATUS_CONFIG;
type CandidateStatus = keyof typeof STATUS_ICONS;
interface StatusBadgeProps {
status: CandidateStatus;
@@ -15,10 +16,15 @@ interface StatusBadgeProps {
}
export function StatusBadge({ status, onStatusChange }: StatusBadgeProps) {
const { t } = useTranslation("threads");
const [isOpen, setIsOpen] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
const config = STATUS_CONFIG[status];
const STATUS_LABELS: Record<CandidateStatus, string> = {
researching: t("statusBadge.researching"),
ordered: t("statusBadge.ordered"),
arrived: t("statusBadge.arrived"),
};
useEffect(() => {
if (!isOpen) return;
@@ -56,14 +62,13 @@ export function StatusBadge({ status, onStatusChange }: StatusBadgeProps) {
}}
className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-600 hover:bg-gray-200 transition-colors cursor-pointer"
>
<LucideIcon name={config.icon} size={14} className="text-gray-500" />
{config.label}
<LucideIcon name={STATUS_ICONS[status]} size={14} className="text-gray-500" />
{STATUS_LABELS[status]}
</button>
{isOpen && (
<div className="absolute right-0 top-full mt-1 z-20 bg-white border border-gray-200 rounded-lg shadow-lg py-1 min-w-[150px]">
{(Object.keys(STATUS_CONFIG) as CandidateStatus[]).map((key) => {
const option = STATUS_CONFIG[key];
{(Object.keys(STATUS_ICONS) as CandidateStatus[]).map((key) => {
const isActive = key === status;
return (
<button
@@ -79,12 +84,12 @@ export function StatusBadge({ status, onStatusChange }: StatusBadgeProps) {
}`}
>
<LucideIcon
name={option.icon}
name={STATUS_ICONS[key]}
size={14}
className={isActive ? "text-gray-700" : "text-gray-400"}
/>
<span className={isActive ? "text-gray-900" : "text-gray-600"}>
{option.label}
{STATUS_LABELS[key]}
</span>
{isActive && (
<LucideIcon