diff --git a/apps/csm-portal/webapp/src/App.tsx b/apps/csm-portal/webapp/src/App.tsx
index e491de4e80..523cf37b22 100644
--- a/apps/csm-portal/webapp/src/App.tsx
+++ b/apps/csm-portal/webapp/src/App.tsx
@@ -95,6 +95,9 @@ const ProductVulnerabilityDetailPage = lazy(
const CsmEngagementsPage = lazy(
() => import("@features/csm-engagements/pages/CsmEngagementsPage"),
);
+const CsmTimeCardsPage = lazy(
+ () => import("@features/csm-timecards/pages/CsmTimeCardsPage"),
+);
/**
* Landing for `/`. Defers to AuthGuard's post-login deep-link restore when a
@@ -258,16 +261,7 @@ export default function App(): JSX.Element {
path="security-center/vulnerabilities/:id"
element={}
/>
-
- }
- />
+ } />
, disabled: true },
{ key: "create_task", label: "Create task…", icon: , divider: true, disabled: true },
{ key: "request_call", label: "Request a call…", icon: , disabled: true },
- { key: "log_time", label: "Log time…", icon: , divider: true, disabled: true },
+ { key: "log_time", label: "Log time…", icon: , divider: true },
{ key: "copy_link", label: "Copy case link", icon: },
);
diff --git a/apps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsx b/apps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsx
index 70c2f763ec..f6b6fc5931 100644
--- a/apps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsx
+++ b/apps/csm-portal/webapp/src/features/csm-cases/pages/CsmCaseDetailPage.tsx
@@ -77,10 +77,12 @@ import {
LinkedItemsWidget,
ProductContextWidget,
SlaTimelineWidget,
- TimeLogsWidget,
WatchersWidget,
} from "@features/csm-cases/components/CaseDetailWidgets";
import { CallRequestsWidget } from "@features/csm-cases/components/CallRequestsWidget";
+import CaseTimeCardsPanel from "@features/csm-timecards/components/CaseTimeCardsPanel";
+import LogTimeCardDialog from "@features/csm-timecards/components/LogTimeCardDialog";
+import { usePostTimeCard } from "@features/csm-timecards/api/useTimeCards";
import { caseIdLabel } from "@features/csm-cases/utils/caseIdentity";
import {
publicCommentGateReason,
@@ -237,8 +239,7 @@ const TAB_DEFS: Array<{
// SLA tab is parked until its backend clock data is wired; disabled for now.
{ id: "sla", label: "SLA", icon: , disabled: true },
{ id: "attachments", label: "Attachments", icon: },
- // Time tracking is parked until its backend flow lands; disabled for now.
- { id: "time", label: "Time tracking", icon: , disabled: true },
+ { id: "time", label: "Time tracking", icon: },
{ id: "call-requests", label: "Call requests", icon: },
];
@@ -285,6 +286,8 @@ export default function CsmCaseDetailPage(): JSX.Element {
const [metaCollapsed, setMetaCollapsed] = useState(false);
const [composerOpen, setComposerOpen] = useState(false);
const [assignOpen, setAssignOpen] = useState(false);
+ const [logTimeOpen, setLogTimeOpen] = useState(false);
+ const postTimeCard = usePostTimeCard();
// Attachment pending delete confirmation (drives the confirm dialog).
const [pendingDelete, setPendingDelete] = useState(
null,
@@ -577,6 +580,16 @@ export default function CsmCaseDetailPage(): JSX.Element {
return;
}
+ if (action.secondary === "log_time") {
+ // CAMG-006: closed cases are read-only — no new time entries.
+ if (data?.state === "closed") {
+ showError("This case is closed — time tracking is read-only.");
+ return;
+ }
+ setLogTimeOpen(true);
+ return;
+ }
+
setFeedback({
message: SECONDARY_TOAST[action.secondary] ?? `Action: ${action.secondary}`,
severity: "info",
@@ -1238,9 +1251,10 @@ export default function CsmCaseDetailPage(): JSX.Element {
{activeTab === "time" && (
- onAction({ secondary: "log_time" })}
+ setLogTimeOpen(true)}
+ readOnly={isClosed}
/>
)}
@@ -1261,6 +1275,31 @@ export default function CsmCaseDetailPage(): JSX.Element {
/>
)}
+ {logTimeOpen && (
+ setLogTimeOpen(false)}
+ onSubmit={(input) =>
+ postTimeCard.mutate(input, {
+ onSuccess: () => {
+ setLogTimeOpen(false);
+ setActiveTab("time");
+ setFeedback({
+ message: "Time card submitted for review.",
+ severity: "success",
+ sticky: false,
+ });
+ },
+ onError: () => showError("Could not log time."),
+ })
+ }
+ />
+ )}
+