feat(i18n): localize the GitHub and GitLab task surfaces (#2283)
Co-authored-by: Kandev Agent <agent@kandev.dev>
This commit is contained in:
@@ -16,6 +16,7 @@ import { formatRelativeTime } from "@/lib/utils";
|
||||
import type { GitHubIssue, TaskIssueLink } from "@/lib/types/github";
|
||||
import type { LaunchPayload, TaskPreset } from "./quick-task-launcher";
|
||||
import { TaskRowIndicator } from "./task-row-indicator";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type IssueListProps = {
|
||||
items: GitHubIssue[];
|
||||
@@ -35,13 +36,14 @@ function StartTaskMenu({
|
||||
presets: TaskPreset[];
|
||||
onStartTask: IssueListProps["onStartTask"];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const launch = (preset: TaskPreset) => onStartTask({ kind: "issue", issue, preset });
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button size="sm" variant="outline" className="h-7 gap-1 cursor-pointer">
|
||||
<IconPlus className="h-3.5 w-3.5" />
|
||||
Task
|
||||
{t("github:task")}
|
||||
<IconChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -94,6 +96,7 @@ function IssueRow({
|
||||
onStartTask: IssueListProps["onStartTask"];
|
||||
tasks: TaskIssueLink[] | undefined;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const StateIcon = issue.state === "open" ? IconCircle : IconCircleCheck;
|
||||
const stateClass =
|
||||
issue.state === "open"
|
||||
@@ -121,7 +124,10 @@ function IssueRow({
|
||||
</span>
|
||||
<span>·</span>
|
||||
<span className="whitespace-nowrap">
|
||||
by {issue.author_login} · opened {formatRelativeTime(issue.created_at)}
|
||||
{t("github:byAuthorOpenedAgo", {
|
||||
author: issue.author_login,
|
||||
time: formatRelativeTime(issue.created_at),
|
||||
})}
|
||||
</span>
|
||||
<IssueLabels labels={issue.labels} />
|
||||
<TaskRowIndicator
|
||||
@@ -149,6 +155,7 @@ function IssueListBody({
|
||||
onStartTask,
|
||||
issueKeyToTasks,
|
||||
}: IssueListProps) {
|
||||
const { t } = useTranslation();
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex justify-center py-10">
|
||||
@@ -162,7 +169,7 @@ function IssueListBody({
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-10 text-muted-foreground text-sm">
|
||||
No issues match this filter.
|
||||
{t("github:noIssuesMatchThisFilter")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Input } from "@kandev/ui/input";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { formatRelativeTime } from "@/lib/utils";
|
||||
import { RepoFilterCombobox } from "./repo-filter-combobox";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type ListToolbarProps = {
|
||||
title: string;
|
||||
@@ -33,11 +34,12 @@ function RefreshControls({
|
||||
onRefresh: () => void;
|
||||
showUpdatedPrefix: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
{lastFetchedAt && !loading && (
|
||||
<span className="text-xs text-muted-foreground whitespace-nowrap">
|
||||
{showUpdatedPrefix ? "Updated " : ""}
|
||||
{showUpdatedPrefix ? t("github:updated") : ""}
|
||||
{formatRelativeTime(lastFetchedAt.toISOString())}
|
||||
</span>
|
||||
)}
|
||||
@@ -47,7 +49,7 @@ function RefreshControls({
|
||||
className="h-8 w-8 cursor-pointer"
|
||||
onClick={onRefresh}
|
||||
disabled={loading}
|
||||
title="Refresh"
|
||||
title={t("github:refresh")}
|
||||
>
|
||||
<IconRefresh className={cn("h-4 w-4", loading && "animate-spin")} />
|
||||
</Button>
|
||||
@@ -69,6 +71,7 @@ export function ListToolbar({
|
||||
repoOptions,
|
||||
onRefresh,
|
||||
}: ListToolbarProps) {
|
||||
const { t } = useTranslation();
|
||||
const dirty = customQuery !== committedQuery;
|
||||
return (
|
||||
<div className="px-4 sm:px-6 py-2.5 border-b shrink-0 flex flex-col md:flex-row md:items-center md:flex-wrap gap-2 md:gap-3">
|
||||
@@ -94,7 +97,7 @@ export function ListToolbar({
|
||||
repoFilter={repoFilter}
|
||||
onRepoFilterChange={onRepoFilterChange}
|
||||
repoOptions={repoOptions}
|
||||
ariaLabel="Filter GitHub results by repository"
|
||||
ariaLabel={t("github:filterGithubResultsByRepository")}
|
||||
triggerClassName="w-full md:w-[220px] h-8 border border-input bg-background hover:bg-secondary/50 px-2 py-1.5 text-xs/relaxed"
|
||||
className="md:min-w-[360px]"
|
||||
testId="github-repo-filter-trigger"
|
||||
@@ -113,12 +116,12 @@ export function ListToolbar({
|
||||
onBlur={() => {
|
||||
if (dirty) onCommitCustomQuery();
|
||||
}}
|
||||
placeholder='Custom query — press Enter. e.g. "is:open review-requested:@me"'
|
||||
placeholder={t("github:customQueryPressEnterEG")}
|
||||
className="h-8 pr-20"
|
||||
/>
|
||||
{dirty && (
|
||||
<span className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] uppercase tracking-wider text-muted-foreground hidden sm:inline">
|
||||
Press Enter
|
||||
{t("github:pressEnter")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ import type { LaunchPayload, TaskPreset } from "./quick-task-launcher";
|
||||
import { PRStatusBadges } from "./pr-status-badges";
|
||||
import { prStatusKey, usePRStatuses } from "./use-pr-statuses";
|
||||
import { PRRowTaskIndicator } from "./pr-row-task-indicator";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type PRListProps = {
|
||||
workspaceId: string | null;
|
||||
@@ -59,6 +60,7 @@ function StartTaskMenu({
|
||||
presets: TaskPreset[];
|
||||
onStartTask: PRListProps["onStartTask"];
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const launch = (preset: TaskPreset) => onStartTask({ kind: "pr", pr, preset });
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -70,7 +72,7 @@ function StartTaskMenu({
|
||||
data-testid="pr-start-task-trigger"
|
||||
>
|
||||
<IconPlus className="h-3.5 w-3.5" />
|
||||
Task
|
||||
{t("github:task")}
|
||||
<IconChevronDown className="h-3 w-3" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -112,6 +114,7 @@ function PRRow({
|
||||
tasks: TaskPR[] | undefined;
|
||||
}) {
|
||||
const { Icon: StateIcon, className: stateIconClass } = prStateIcon(pr);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className="flex items-start gap-3 px-4 py-3 hover:bg-muted/40 transition-colors"
|
||||
@@ -134,7 +137,10 @@ function PRRow({
|
||||
</span>
|
||||
<span>·</span>
|
||||
<span className="whitespace-nowrap">
|
||||
by {pr.author_login} · opened {formatRelativeTime(pr.created_at)}
|
||||
{t("github:byAuthorOpenedAgo", {
|
||||
author: pr.author_login,
|
||||
time: formatRelativeTime(pr.created_at),
|
||||
})}
|
||||
</span>
|
||||
<PRStatusBadges pr={pr} status={status} />
|
||||
<PRRowTaskIndicator tasks={tasks} />
|
||||
@@ -160,6 +166,7 @@ function PRListBody({
|
||||
onStartTask,
|
||||
prKeyToTasks,
|
||||
}: PRListProps) {
|
||||
const { t } = useTranslation();
|
||||
const statuses = usePRStatuses(workspaceId, items);
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -174,7 +181,7 @@ function PRListBody({
|
||||
if (items.length === 0) {
|
||||
return (
|
||||
<div className="text-center py-10 text-muted-foreground text-sm">
|
||||
No pull requests match this filter.
|
||||
{t("github:noPullRequestsMatchThisFilter")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
|
||||
import type { TaskPR } from "@/lib/types/github";
|
||||
import { TaskRowIndicator } from "./task-row-indicator";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type PRRowTaskIndicatorProps = {
|
||||
tasks: TaskPR[] | undefined;
|
||||
};
|
||||
|
||||
export function PRRowTaskIndicator({ tasks }: PRRowTaskIndicatorProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<TaskRowIndicator
|
||||
tasks={tasks?.map((task) => ({
|
||||
@@ -16,7 +18,7 @@ export function PRRowTaskIndicator({ tasks }: PRRowTaskIndicatorProps) {
|
||||
fallbackTitle: task.pr_title,
|
||||
}))}
|
||||
testIdPrefix="pr-row-task-indicator"
|
||||
emptyLabel="No task created yet"
|
||||
emptyLabel={t("github:noTaskCreatedYet")}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
import type { Icon } from "@tabler/icons-react";
|
||||
import type { GitHubPR, GitHubPRStatus } from "@/lib/types/github";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type StatusChipProps = {
|
||||
Icon: Icon;
|
||||
@@ -38,14 +39,17 @@ function StatusChip({ Icon, label, tone, title }: StatusChipProps) {
|
||||
}
|
||||
|
||||
function ChecksChip({ status }: { status: GitHubPRStatus }) {
|
||||
const { t } = useTranslation();
|
||||
const { checks_state: state, checks_total: total, checks_passing: passing } = status;
|
||||
if (!state) return null;
|
||||
const label = total > 0 ? `${passing}/${total}` : "";
|
||||
if (state === "success")
|
||||
return <StatusChip Icon={IconCheck} label={label || "Checks passed"} tone="success" />;
|
||||
return <StatusChip Icon={IconCheck} label={label || t("github:checksPassed")} tone="success" />;
|
||||
if (state === "failure")
|
||||
return <StatusChip Icon={IconX} label={label || "Checks failed"} tone="failure" />;
|
||||
return <StatusChip Icon={IconClockHour4} label={label || "Checks running"} tone="pending" />;
|
||||
return <StatusChip Icon={IconX} label={label || t("github:checksFailed")} tone="failure" />;
|
||||
return (
|
||||
<StatusChip Icon={IconClockHour4} label={label || t("github:checksRunning")} tone="pending" />
|
||||
);
|
||||
}
|
||||
|
||||
function ReviewChip({
|
||||
@@ -55,16 +59,20 @@ function ReviewChip({
|
||||
state: GitHubPRStatus["review_state"];
|
||||
pending: number;
|
||||
}) {
|
||||
if (state === "approved") return <StatusChip Icon={IconCheck} label="Approved" tone="success" />;
|
||||
const { t } = useTranslation();
|
||||
if (state === "approved")
|
||||
return <StatusChip Icon={IconCheck} label={t("github:approved")} tone="success" />;
|
||||
if (state === "changes_requested")
|
||||
return <StatusChip Icon={IconAlertTriangle} label="Changes requested" tone="failure" />;
|
||||
return (
|
||||
<StatusChip Icon={IconAlertTriangle} label={t("github:changesRequested")} tone="failure" />
|
||||
);
|
||||
if (pending > 0)
|
||||
return (
|
||||
<StatusChip
|
||||
Icon={IconClockHour4}
|
||||
label={`${pending} pending`}
|
||||
label={t("github:pending", { pending })}
|
||||
tone="pending"
|
||||
title={`${pending} pending review(s)`}
|
||||
title={t("github:pendingReviewS", { pending })}
|
||||
/>
|
||||
);
|
||||
return null;
|
||||
@@ -77,15 +85,17 @@ function MergeableChip({
|
||||
state: GitHubPRStatus["mergeable_state"];
|
||||
prState: GitHubPR["state"];
|
||||
}) {
|
||||
if (prState === "merged") return <StatusChip Icon={IconGitMerge} label="Merged" tone="success" />;
|
||||
const { t } = useTranslation();
|
||||
if (prState === "merged")
|
||||
return <StatusChip Icon={IconGitMerge} label={t("github:merged")} tone="success" />;
|
||||
if (state === "draft")
|
||||
return <StatusChip Icon={IconGitPullRequestDraft} label="Draft" tone="neutral" />;
|
||||
return <StatusChip Icon={IconGitPullRequestDraft} label={t("github:draft")} tone="neutral" />;
|
||||
if (state === "dirty")
|
||||
return <StatusChip Icon={IconAlertTriangle} label="Conflicts" tone="failure" />;
|
||||
return <StatusChip Icon={IconAlertTriangle} label={t("github:conflicts")} tone="failure" />;
|
||||
if (state === "blocked")
|
||||
return <StatusChip Icon={IconAlertTriangle} label="Blocked" tone="pending" />;
|
||||
return <StatusChip Icon={IconAlertTriangle} label={t("github:blocked")} tone="pending" />;
|
||||
if (state === "behind")
|
||||
return <StatusChip Icon={IconAlertTriangle} label="Behind base" tone="pending" />;
|
||||
return <StatusChip Icon={IconAlertTriangle} label={t("github:behindBase")} tone="pending" />;
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { IntegrationScopeBar } from "@/components/integrations/presets-scope-bar
|
||||
import { PR_PRESETS, ISSUE_PRESETS, type PresetOption } from "./search-bar";
|
||||
import type { SavedPreset } from "./use-saved-presets";
|
||||
import type { SidebarSelection } from "./presets-sidebar";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type PresetsScopeBarProps = {
|
||||
className?: string;
|
||||
@@ -17,9 +18,10 @@ type PresetsScopeBarProps = {
|
||||
issuePresets?: PresetOption[];
|
||||
};
|
||||
|
||||
/** `value` is the persisted kind; only the catalog key is copy. */
|
||||
const KINDS = [
|
||||
{ value: "pr", label: "Pull requests" },
|
||||
{ value: "issue", label: "Issues" },
|
||||
{ value: "pr", labelKey: "github:titlePullRequests" },
|
||||
{ value: "issue", labelKey: "github:titleIssues" },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
@@ -32,12 +34,13 @@ export function PresetsScopeBar({
|
||||
issuePresets = ISSUE_PRESETS,
|
||||
...props
|
||||
}: PresetsScopeBarProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<IntegrationScopeBar
|
||||
{...props}
|
||||
testId="github-presets-scope-bar"
|
||||
savedMenuTestId="github-saved-queries-menu"
|
||||
kinds={KINDS}
|
||||
kinds={KINDS.map(({ value, labelKey }) => ({ value, label: t(labelKey) }))}
|
||||
presetsByKind={(kind) => (kind === "pr" ? prPresets : issuePresets)}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Icon } from "@tabler/icons-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { PR_PRESETS, ISSUE_PRESETS, type PresetOption, type PresetGroup } from "./search-bar";
|
||||
import type { SavedPreset } from "./use-saved-presets";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type SidebarSelection = {
|
||||
kind: "pr" | "issue";
|
||||
@@ -30,6 +31,7 @@ function KindToggle({
|
||||
kind: "pr" | "issue";
|
||||
onChange: (k: "pr" | "issue") => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="mx-2 mb-3 grid grid-cols-2 rounded-md border p-0.5 text-xs">
|
||||
{(["pr", "issue"] as const).map((value) => (
|
||||
@@ -44,7 +46,7 @@ function KindToggle({
|
||||
: "text-muted-foreground hover:text-foreground",
|
||||
)}
|
||||
>
|
||||
{value === "pr" ? "Pull requests" : "Issues"}
|
||||
{value === "pr" ? t("github:pullRequests") : t("github:issues")}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -111,11 +113,12 @@ function PresetGroupList({
|
||||
onSelect: (s: SidebarSelection) => void;
|
||||
kind: "pr" | "issue";
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const items = presets.filter((p) => p.group === group);
|
||||
if (items.length === 0) return null;
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title={group === "inbox" ? "Inbox" : "Created"} />
|
||||
<SectionHeader title={group === "inbox" ? t("github:inbox") : t("github:created")} />
|
||||
{items.map((p) => (
|
||||
<PresetItem
|
||||
key={`${kind}-${p.value}`}
|
||||
@@ -146,12 +149,13 @@ function SavedSection({
|
||||
canSaveCurrent: boolean;
|
||||
onSaveCurrent: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<SectionHeader title="Saved" />
|
||||
<SectionHeader title={t("github:saved")} />
|
||||
{saved.length === 0 && (
|
||||
<div className="mx-2 px-2 py-1 text-xs text-muted-foreground/80 italic">
|
||||
No saved queries yet.
|
||||
{t("github:noSavedQueriesYet")}
|
||||
</div>
|
||||
)}
|
||||
{saved.map((s) => (
|
||||
@@ -169,7 +173,7 @@ function SavedSection({
|
||||
onDelete(s.id);
|
||||
}}
|
||||
className="opacity-0 group-hover/item:opacity-100 transition-opacity text-muted-foreground hover:text-foreground cursor-pointer"
|
||||
title="Delete saved query"
|
||||
title={t("github:deleteSavedQuery")}
|
||||
>
|
||||
<IconX className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
@@ -186,10 +190,10 @@ function SavedSection({
|
||||
? "text-muted-foreground hover:bg-muted/50 hover:text-foreground cursor-pointer"
|
||||
: "text-muted-foreground/50 cursor-not-allowed",
|
||||
)}
|
||||
title={canSaveCurrent ? "Save current query" : "Type a custom query first"}
|
||||
title={canSaveCurrent ? t("github:saveCurrentQuery") : t("github:typeACustomQueryFirst")}
|
||||
>
|
||||
<IconDeviceFloppy className="h-4 w-4 shrink-0" />
|
||||
<span>Save current query</span>
|
||||
<span>{t("github:saveCurrentQuery")}</span>
|
||||
</button>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { Combobox, type ComboboxOption } from "@/components/combobox";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { t } from "@/lib/i18n";
|
||||
|
||||
const ALL_REPOS = "__all__";
|
||||
|
||||
@@ -18,7 +20,7 @@ type RepoFilterComboboxProps = {
|
||||
|
||||
function buildRepoFilterOptions(repoOptions: string[]): ComboboxOption[] {
|
||||
return [
|
||||
{ value: ALL_REPOS, label: "All repos", keywords: ["all", "repositories", "repos"] },
|
||||
{ value: ALL_REPOS, label: t("github:allRepos"), keywords: ["all", "repositories", "repos"] },
|
||||
...repoOptions.map((repo) => ({ value: repo, label: repo, keywords: [repo] })),
|
||||
];
|
||||
}
|
||||
@@ -33,6 +35,7 @@ export function RepoFilterCombobox({
|
||||
triggerClassName,
|
||||
className,
|
||||
}: RepoFilterComboboxProps) {
|
||||
const { t } = useTranslation();
|
||||
const options = useMemo(() => buildRepoFilterOptions(repoOptions), [repoOptions]);
|
||||
|
||||
return (
|
||||
@@ -45,9 +48,9 @@ export function RepoFilterCombobox({
|
||||
}}
|
||||
options={options}
|
||||
ariaLabel={ariaLabel}
|
||||
placeholder="All repos"
|
||||
searchPlaceholder="Filter repositories..."
|
||||
emptyMessage="No repositories found."
|
||||
placeholder={t("github:allRepos")}
|
||||
searchPlaceholder={t("github:filterRepositories")}
|
||||
emptyMessage={t("github:noRepositoriesFound")}
|
||||
triggerClassName={triggerClassName}
|
||||
className={className}
|
||||
testId={testId}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Input } from "@kandev/ui/input";
|
||||
import { Button } from "@kandev/ui/button";
|
||||
import { Label } from "@kandev/ui/label";
|
||||
import { RepoFilterCombobox } from "./repo-filter-combobox";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
type SavePresetDialogProps = {
|
||||
open: boolean;
|
||||
@@ -42,6 +43,7 @@ function SavePresetForm({
|
||||
onSave: (label: string, repoFilter: string) => void;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [value, setValue] = useState(suggestedLabel);
|
||||
const [defaultRepoFilter, setDefaultRepoFilter] = useState(repoFilter);
|
||||
const trimmed = value.trim();
|
||||
@@ -56,16 +58,17 @@ function SavePresetForm({
|
||||
return (
|
||||
<>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Save query</DialogTitle>
|
||||
<DialogTitle>{t("github:saveQuery")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
Save this {kind === "pr" ? "pull request" : "issue"} query to the sidebar for quick access
|
||||
later.
|
||||
{t("github:savePresetDescription", {
|
||||
kind: kind === "pr" ? t("github:kindPullRequest") : t("github:kindIssue"),
|
||||
})}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label htmlFor="preset-label" className="text-xs">
|
||||
Name
|
||||
{t("github:name")}
|
||||
</Label>
|
||||
<Input
|
||||
id="preset-label"
|
||||
@@ -73,13 +76,13 @@ function SavePresetForm({
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
onFocus={(e) => e.target.select()}
|
||||
placeholder="e.g. Needs my review"
|
||||
placeholder={t("github:eGNeedsMyReview")}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5 text-xs">
|
||||
{customQuery && (
|
||||
<div className="flex gap-2">
|
||||
<span className="text-muted-foreground shrink-0 w-16">Query</span>
|
||||
<span className="text-muted-foreground shrink-0 w-16">{t("github:query")}</span>
|
||||
<code className="font-mono text-[11px] bg-muted rounded px-1.5 py-0.5 break-all">
|
||||
{customQuery}
|
||||
</code>
|
||||
@@ -87,27 +90,27 @@ function SavePresetForm({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label className="text-xs">Default repository</Label>
|
||||
<Label className="text-xs">{t("github:defaultRepository")}</Label>
|
||||
<RepoFilterCombobox
|
||||
repoFilter={defaultRepoFilter}
|
||||
onRepoFilterChange={setDefaultRepoFilter}
|
||||
repoOptions={repoOptions}
|
||||
ariaLabel="Default repository"
|
||||
ariaLabel={t("github:defaultRepository")}
|
||||
triggerClassName="h-11 border border-input bg-background px-3 py-2 text-sm hover:bg-secondary/50 md:h-9 md:py-1.5"
|
||||
testId="github-save-query-repo-trigger"
|
||||
dropdownTestId="github-save-query-repo-dropdown"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
This repository opens by default. You can change the filter after opening the query.
|
||||
{t("github:thisRepositoryOpensByDefaultYou")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" className="cursor-pointer" onClick={onClose}>
|
||||
Cancel
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
<Button className="cursor-pointer" disabled={!canSubmit} onClick={handleSubmit}>
|
||||
Save
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { cn } from "@/lib/utils";
|
||||
import { linkToTask } from "@/lib/links";
|
||||
import { useTaskById } from "@/hooks/domains/kanban/use-task-by-id";
|
||||
import type { KanbanState } from "@/lib/state/slices";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export type TaskRowLink = {
|
||||
id: string;
|
||||
@@ -72,6 +73,7 @@ function SingleTaskButton({
|
||||
const taskData = useTaskById(task.taskId);
|
||||
const stepTitle = useTaskStepTitle(taskData?.workflowStepId);
|
||||
const title = taskData?.title ?? task.fallbackTitle;
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -87,8 +89,10 @@ function SingleTaskButton({
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<div className="flex flex-col gap-0.5 text-xs">
|
||||
<span>Task: {title}</span>
|
||||
{stepTitle ? <span className="text-muted-foreground">Step: {stepTitle}</span> : null}
|
||||
<span>{t("github:taskLabelled", { title })}</span>
|
||||
{stepTitle ? (
|
||||
<span className="text-muted-foreground">{t("github:stepLabelled", { stepTitle })}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -96,6 +100,7 @@ function SingleTaskButton({
|
||||
}
|
||||
|
||||
export function TaskRowIndicator({ tasks, testIdPrefix, emptyLabel }: TaskRowIndicatorProps) {
|
||||
const { t } = useTranslation();
|
||||
const setActiveTask = useAppStore((state) => state.setActiveTask);
|
||||
const router = useRouter();
|
||||
const navigate = useCallback(
|
||||
@@ -124,7 +129,7 @@ export function TaskRowIndicator({ tasks, testIdPrefix, emptyLabel }: TaskRowInd
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button type="button" data-testid={`${testIdPrefix}-multi`} className={buttonClass}>
|
||||
<IconChecklist className={iconClass} />
|
||||
<span className="text-foreground/80">Tasks</span>
|
||||
<span className="text-foreground/80">{t("github:tasks")}</span>
|
||||
<Badge variant="outline" className="h-4 px-1 py-0 text-[10px] shrink-0">
|
||||
{tasks.length}
|
||||
</Badge>
|
||||
|
||||
Reference in New Issue
Block a user