@@ -249,6 +251,19 @@ export function AnalyticsWorkItemsPage() {
const baseUrl = `/${workspace.slug}/analytics`;
+ const byState = analytics?.by_state ?? {};
+ const backlogCount = sumStateGroup(byState, STATE_GROUP_SYNONYMS.backlog);
+ const startedCount = sumStateGroup(byState, STATE_GROUP_SYNONYMS.started);
+ const unstartedCount = sumStateGroup(byState, STATE_GROUP_SYNONYMS.unstarted);
+ const completedCount = sumStateGroup(byState, STATE_GROUP_SYNONYMS.completed);
+ const totalIssues = Object.values(byState).reduce((sum, count) => sum + count, 0);
+
+ const priorityRows = Object.entries(analytics?.by_priority ?? {}).map(([priority, count]) => ({
+ priority: priority ? priority.charAt(0).toUpperCase() + priority.slice(1) : 'None',
+ count,
+ }));
+
+ const createdResolvedData: TrendPoint[] = analytics?.trend ?? [];
return (
{/* Tabs */}
@@ -257,13 +272,13 @@ export function AnalyticsWorkItemsPage() {
to={`${baseUrl}/overview`}
className="border-b-2 border-transparent px-4 py-2.5 text-sm font-medium text-(--txt-secondary) no-underline hover:text-(--txt-primary)"
>
- {t('analytics.overview', 'Overview')}
+ {t('common.overview', 'Overview')}
- {t('analytics.workItems', 'Work items')}
+ {t('common.workItems', 'Work items')}
@@ -271,13 +286,17 @@ export function AnalyticsWorkItemsPage() {
{t('analytics.workItems', 'Work items')}
+ {analyticsError && (
+
{analyticsError}
+ )}
+
{/* KPIs */}
{t('analytics.totalWorkItems', 'Total Work items')}
-
{issues.length}
+
{totalIssues}
@@ -340,7 +359,7 @@ export function AnalyticsWorkItemsPage() {
tickLine={{ stroke: 'var(--border-subtle)' }}
axisLine={{ stroke: 'var(--border-subtle)' }}
label={{
- value: t('analytics.axisNoOfWorkItems', 'NO. OF WORK ITEMS'),
+ value: t('analytics.axisWorkItemsCount', 'NO. OF WORK ITEMS'),
angle: -90,
position: 'insideLeft',
fill: 'var(--txt-tertiary)',
@@ -440,7 +459,7 @@ export function AnalyticsWorkItemsPage() {
tickLine={{ stroke: 'var(--border-subtle)' }}
axisLine={{ stroke: 'var(--border-subtle)' }}
label={{
- value: t('analytics.axisNoOfWorkItem', 'NO. OF WORK ITEM'),
+ value: t('analytics.axisWorkItemCount', 'NO. OF WORK ITEM'),
angle: -90,
position: 'insideLeft',
fill: 'var(--txt-tertiary)',
@@ -456,33 +475,40 @@ export function AnalyticsWorkItemsPage() {
+ {exportError && (
+
{exportError}
+ )}
+
{/* Priority table */}
- {priorityRows.length} {t('analytics.priority', 'Priority')}
- {priorityRows.length !== 1 ? t('analytics.prioritySuffixPlural', 'ies') : ''}
+ {t('analytics.prioritiesCount', '{{count}} Priorities', {
+ count: priorityRows.length,
+ })}
-
-
-
- |
+ |
{t('analytics.priority', 'Priority')}
|
-
+ |
{t('analytics.count', 'Count')}
|
@@ -490,8 +516,8 @@ export function AnalyticsWorkItemsPage() {
{priorityRows.map(({ priority, count }) => (
- | {priority} |
- {count} |
+ {priority} |
+ {count} |
))}
@@ -506,45 +532,24 @@ export function AnalyticsWorkItemsPage() {
{t('analytics.projectsCount', '{{count}} Projects', { count: projects.length })}
-
-
-
-
- |
- {t('analytics.colProject', 'Project')}
- |
-
- {t('analytics.colBacklog', 'Backlog')}
- |
-
- {t('analytics.colStarted', 'Started')}
+ |
+ {t('common.project', 'Project')}
|
-
- {t('analytics.colUnstarted', 'Unstarted')}
- |
-
- {t('analytics.colCompleted', 'Completed')}
- |
-
- {t('analytics.colCancelled', 'Cancelled')}
+ |
+ {t('common.actions', 'Actions')}
|
- {projectRows.map(({ project, backlog, started, unstarted, completed, cancelled }) => (
+ {projects.map((project) => (
- |
+ |
@@ -552,11 +557,19 @@ export function AnalyticsWorkItemsPage() {
{project.name}
|
- {backlog} |
- {started} |
- {unstarted} |
- {completed} |
- {cancelled} |
+
+
+ |
))}
diff --git a/apps/web/src/pages/IssueListPage.tsx b/apps/web/src/pages/IssueListPage.tsx
index e9502f9..3312242 100644
--- a/apps/web/src/pages/IssueListPage.tsx
+++ b/apps/web/src/pages/IssueListPage.tsx
@@ -181,39 +181,50 @@ export function IssueListPage() {
}
let cancelled = false;
setLoading(true);
+
+ const safeFetch = (promise: Promise, fallback: T): Promise => {
+ return Promise.resolve(promise)
+ .then((val) => val ?? fallback)
+ .catch((err: unknown) => {
+ const errorMessage = err instanceof Error ? err.message : String(err);
+ console.warn('Secondary request ignored (Prevents crashes)', errorMessage);
+ return fallback;
+ });
+ };
+
Promise.all([
workspaceService.getBySlug(workspaceSlug),
projectService.get(workspaceSlug, projectId),
- projectService.list(workspaceSlug),
- issueService.list(workspaceSlug, projectId, { limit: 100 }),
- stateService.list(workspaceSlug, projectId),
- labelService.list(workspaceSlug, projectId),
- cycleService.list(workspaceSlug, projectId),
- moduleService.list(workspaceSlug, projectId),
- workspaceService.listMembers(workspaceSlug),
+
+ safeFetch(projectService.list(workspaceSlug), [] as ProjectApiResponse[]),
+ safeFetch(
+ issueService.list(workspaceSlug, projectId, { limit: 100 }),
+ [] as IssueApiResponse[],
+ ),
+ safeFetch(stateService.list(workspaceSlug, projectId), [] as StateApiResponse[]),
+ safeFetch(labelService.list(workspaceSlug, projectId), [] as LabelApiResponse[]),
+ safeFetch(cycleService.list(workspaceSlug, projectId), [] as CycleApiResponse[]),
+ safeFetch(moduleService.list(workspaceSlug, projectId), [] as ModuleApiResponse[]),
+ safeFetch(workspaceService.listMembers(workspaceSlug), [] as WorkspaceMemberApiResponse[]),
])
.then(([w, p, list, iss, st, lab, cyc, mod, mem]) => {
if (cancelled) return;
setWorkspace(w);
setProject(p);
- setProjects(list ?? []);
- setIssues(iss ?? []);
- setStates(st ?? []);
- setLabels(lab ?? []);
- setCycles(cyc ?? []);
- setModules(mod ?? []);
- setMembers(mem ?? []);
+ setProjects(list);
+ setIssues(iss);
+ setStates(st);
+ setLabels(lab);
+ setCycles(cyc);
+ setModules(mod);
+ setMembers(mem);
})
- .catch(() => {
- if (!cancelled) setWorkspace(null);
- setProject(null);
- setProjects([]);
- setIssues([]);
- setStates([]);
- setLabels([]);
- setCycles([]);
- setModules([]);
- setMembers([]);
+ .catch((err) => {
+ console.error('Critical error when loading Project or Workspace : ', err);
+ if (!cancelled) {
+ setWorkspace(null);
+ setProject(null);
+ }
})
.finally(() => {
if (!cancelled) setLoading(false);