Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/storybook-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ operator-facing control you can click before changing product CSS.
| `Workspace/WorkspaceCalendar` | Read observed Naruon events, or open a commitment to land on that post. Fail-closed copy stays `이 범위의 일정을 아직 받을 수 없습니다`. | `--color-chip-border`, `WorkspaceCalendar`, `EvidenceStatusMark` |
| `Evidence/OntologyExplorer` | Distinguish Post, Person, Organization, and Team by shape and text, use the token-backed surface as a secondary cue, then open the exact-value table or cited evidence. Compare desktop, narrow, drawer, empty, truncated, denied, stale, and rejected states. | `--ontology-node-*-fill`, `OntologyExplorer` |
| `Post/ProductEvidenceList` | Open the cited product span. If the identity is unresolved, review the product catalog before using the relationship. Compare catalog-linked and catalog-review-required states. | `--surface`, `--border`, `ProductEvidenceList` |
| `Dashboard/VoiceTaxonomySummary` | Compare source and semantic classifications, note overlapping memberships, then review disagreements and records waiting for evidence. | `--surface`, `--border`, `VoiceTaxonomySummary` |
| `Dashboard/VoiceTaxonomySummary` | Compare source and semantic classifications, note overlapping memberships, then review disagreements and records waiting for evidence; `KoreanMobile` verifies locale-complete customer copy in the narrow viewport. | `--surface`, `--border`, `VoiceTaxonomySummary` |
| `Navigation/WorkspaceNav` | Reach every workspace destination and the language action; `MobileAllDestinations` keeps all actions visible without horizontal clipping. | `--gnb-height`, `--size-control-min`, `WorkspaceNav` |

Repeated web objects must use `frontend/src/styles/tokens.css` and a module
under `frontend/src/components/`. Do not add a second Node package manager;
Expand Down
21 changes: 15 additions & 6 deletions frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -1344,16 +1344,25 @@
/* Phone Breakpoint (<768px) */

.workspace-gnb {
overflow-x: auto;
overscroll-behavior-inline: contain;
gap: 0.75rem;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
height: auto;
gap: 0;
padding: 0 1rem;
scrollbar-width: thin;
}

.workspace-gnb-item,
.workspace-gnb-item {
min-height: var(--size-control-min);
justify-content: center;
padding: 0 0.25rem;
text-align: center;
}

.workspace-gnb-tools {
flex: 0 0 auto;
grid-column: 1 / -1;
min-height: var(--size-control-min);
margin-left: 0;
justify-content: flex-end;
}
Comment thread
seonghobae marked this conversation as resolved.

.mobile-drawer-trigger {
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/components/VoiceTaxonomySummary.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Meta, StoryObj } from "@storybook/react";
import { setLocale } from "../i18n";
import { VoiceTaxonomySummary } from "./VoiceTaxonomySummary";

const meta = { title: "Dashboard/VoiceTaxonomySummary", component: VoiceTaxonomySummary } satisfies Meta<typeof VoiceTaxonomySummary>;
Expand All @@ -14,3 +15,12 @@ export const OverlappingEvidence: Story = { args: { data: {
{ voice_concept_code: "vom", post_count: 4, eligible_percentage: 33.3 },
],
} } };

export const KoreanMobile: Story = {
...OverlappingEvidence,
beforeEach: () => {
setLocale("ko");
return () => setLocale("en");
},
globals: { viewport: { value: "mobile1", isRotated: false } },
};
25 changes: 23 additions & 2 deletions frontend/src/components/VoiceTaxonomySummary.test.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { act, render, screen } from "@testing-library/react";
import { afterEach, describe, expect, it } from "vitest";
import { setLocale } from "../i18n";
import { VoiceTaxonomySummary } from "./VoiceTaxonomySummary";

describe("VoiceTaxonomySummary", () => {
afterEach(() => setLocale("en"));

it("discloses overlapping counts and the next review action", () => {
render(<VoiceTaxonomySummary data={{
total_eligible: 2, classified_unique: 0, multi_membership: 1,
Expand All @@ -16,4 +19,22 @@ describe("VoiceTaxonomySummary", () => {
expect(screen.getByText(/voice categories, so category counts can overlap/)).toBeInTheDocument();
expect(screen.getByText(/Review disagreements and records without voice evidence/)).toBeInTheDocument();
});

it("updates every visible label when the product locale changes", () => {
render(<VoiceTaxonomySummary data={{
total_eligible: 2, classified_unique: 2, multi_membership: 0,
source_count: 2, derived_count: 0, unavailable: 0, disagreement: 0,
counts_overlap: false,
category_memberships: [{ voice_concept_code: "voc", post_count: 2, eligible_percentage: 100 }],
}} />);

act(() => setLocale("ko"));

expect(screen.getByRole("heading", { name: "글 유형 근거 현황" })).toBeInTheDocument();
expect(screen.getByText("기록된 근거")).toBeInTheDocument();
expect(screen.getByText("글 유형 근거가 없는 기록")).toBeInTheDocument();
expect(screen.getByText("고객의 소리")).toBeInTheDocument();
expect(screen.getByText(/불일치와 글 유형 근거가 없는 기록을 확인한 뒤/)).toBeInTheDocument();
expect(screen.queryByText("Voice evidence overview")).not.toBeInTheDocument();
});
});
3 changes: 2 additions & 1 deletion frontend/src/components/VoiceTaxonomySummary.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { VoiceTaxonomySummary as Summary } from "../api";
import { t, tf } from "../i18n";
import { t, tf, useLocale } from "../i18n";

const voiceLabels = {
voc: "Voice of Customer",
Expand All @@ -10,6 +10,7 @@ const voiceLabels = {
} as const;

export function VoiceTaxonomySummary({ data }: { data: Summary }) {
useLocale();
return (
<section className="operations-dashboard" aria-labelledby="voice-summary-heading">
<h2 id="voice-summary-heading">{t("Voice evidence overview")}</h2>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/WorkspaceNav.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,11 @@ export const WithTools: Story = {
tools: <button type="button">Sign out</button>,
},
};

export const MobileAllDestinations: Story = {
args: {
destination: "dashboard",
tools: <button type="button">언어</button>,
},
globals: { viewport: { value: "mobile1", isRotated: false } },
};
6 changes: 5 additions & 1 deletion frontend/src/mobileNavigationCss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ const css = readFileSync(join(dirname(fileURLToPath(import.meta.url)), "App.css"
it("keeps the workspace GNB reachable on mobile", () => {
const mobile = css.match(/@media \(max-width: 768px\) \{([\s\S]*?)\n\}/)?.[1] ?? "";
expect(mobile).toContain(".workspace-gnb");
expect(mobile).toContain("overflow-x: auto");
expect(mobile).toContain("grid-template-columns: repeat(3, minmax(0, 1fr))");
expect(mobile).toContain("height: auto");
expect(mobile).toContain("grid-column: 1 / -1");
expect(mobile).toContain("min-height: var(--size-control-min)");
expect(mobile).not.toContain("overflow-x: auto");
Comment thread
seonghobae marked this conversation as resolved.
expect(mobile).not.toContain(".workspace-gnb {\n display: none");
});