fix: use fresh project status and rights after reload
After F5 without projectId in the address the hub and sidebar showed the status and edit right saved in sessionStorage, so a project switched to Архивный or a revoked edit0 right was ignored until the project was picked again. The fresh list from SharePoint now always wins; the saved copy is used only while the list is loading. Found in T-HUB-07/09. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { useRouter } from '../routing/RouterContext';
|
|||||||
import { IAccessibleProject } from './projectTypes';
|
import { IAccessibleProject } from './projectTypes';
|
||||||
import { fetchAccessibleProjects } from './projectRepository';
|
import { fetchAccessibleProjects } from './projectRepository';
|
||||||
import { readStoredProject, storeProject } from './projectSession';
|
import { readStoredProject, storeProject } from './projectSession';
|
||||||
|
import { resolveCurrentProject } from './projectRules';
|
||||||
|
|
||||||
export type ProjectsLoadState = 'loading' | 'ready' | 'error';
|
export type ProjectsLoadState = 'loading' | 'ready' | 'error';
|
||||||
|
|
||||||
@@ -51,13 +52,12 @@ export const ProjectProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
|||||||
if (isCancelled) return;
|
if (isCancelled) return;
|
||||||
setProjects(loaded);
|
setProjects(loaded);
|
||||||
setLoadState('ready');
|
setLoadState('ready');
|
||||||
// В сессии могли остаться устаревшие права — обновляем по свежим данным (как в main).
|
// В сессии могли остаться устаревшие статус и права — сохраняем свежие.
|
||||||
setStoredProject((stored) => {
|
setStoredProject((stored) => {
|
||||||
const fresh = stored ? findProject(loaded, stored.id) : undefined;
|
const fresh = stored ? findProject(loaded, stored.id) : undefined;
|
||||||
if (!stored || !fresh) return stored;
|
if (!fresh) return stored;
|
||||||
const updated = { ...stored, canEdit: fresh.canEdit };
|
storeProject(fresh);
|
||||||
storeProject(updated);
|
return fresh;
|
||||||
return updated;
|
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((error: unknown) => {
|
.catch((error: unknown) => {
|
||||||
@@ -71,13 +71,7 @@ export const ProjectProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
|||||||
}, [services, reloadKey]);
|
}, [services, reloadKey]);
|
||||||
|
|
||||||
const routeProjectId = route.projectId ? Number(route.projectId) : undefined;
|
const routeProjectId = route.projectId ? Number(route.projectId) : undefined;
|
||||||
let currentProject: IAccessibleProject | undefined;
|
const currentProject = resolveCurrentProject(projects, routeProjectId, storedProject);
|
||||||
if (routeProjectId === undefined) {
|
|
||||||
currentProject = storedProject;
|
|
||||||
} else {
|
|
||||||
currentProject =
|
|
||||||
findProject(projects, routeProjectId) ?? (storedProject?.id === routeProjectId ? storedProject : undefined);
|
|
||||||
}
|
|
||||||
const currentProjectId = route.projectId ?? (storedProject ? String(storedProject.id) : undefined);
|
const currentProjectId = route.projectId ?? (storedProject ? String(storedProject.id) : undefined);
|
||||||
|
|
||||||
const selectProject = React.useCallback(
|
const selectProject = React.useCallback(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
isSectionAvailable,
|
isSectionAvailable,
|
||||||
mergeAccessRows,
|
mergeAccessRows,
|
||||||
pickRecentProjectIds,
|
pickRecentProjectIds,
|
||||||
|
resolveCurrentProject,
|
||||||
sortProjects
|
sortProjects
|
||||||
} from './projectRules';
|
} from './projectRules';
|
||||||
import { IAccessibleProject } from './projectTypes';
|
import { IAccessibleProject } from './projectTypes';
|
||||||
@@ -71,6 +72,25 @@ describe('getStatusKind', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('resolveCurrentProject', () => {
|
||||||
|
const stored = project({ id: 51, status: 'Текущий', canEdit: true });
|
||||||
|
const fresh = project({ id: 51, status: 'Архив', canEdit: false });
|
||||||
|
|
||||||
|
it('свежие статус и права из списка главнее сохранённых в сессии (баг T-HUB-07/09)', () => {
|
||||||
|
expect(resolveCurrentProject([fresh], undefined, stored)).toBe(fresh);
|
||||||
|
expect(resolveCurrentProject([fresh], 51, stored)).toBe(fresh);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('пока список не загрузился — сохранённый выбор', () => {
|
||||||
|
expect(resolveCurrentProject([], undefined, stored)).toBe(stored);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('проект из адреса, которого нет ни в списке, ни в сессии, — не выбран', () => {
|
||||||
|
expect(resolveCurrentProject([fresh], 7, stored)).toBeUndefined();
|
||||||
|
expect(resolveCurrentProject([], undefined, undefined)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('isSectionAvailable', () => {
|
describe('isSectionAvailable', () => {
|
||||||
it('архивный проект — закрыты все разделы, кроме главной', () => {
|
it('архивный проект — закрыты все разделы, кроме главной', () => {
|
||||||
const archived = project({ status: 'архивный' });
|
const archived = project({ status: 'архивный' });
|
||||||
|
|||||||
@@ -85,6 +85,23 @@ export function isSectionAvailable(page: PageId, project: IAccessibleProject): b
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Текущий проект: id из адреса, а без него — сохранённый в сессии выбор.
|
||||||
|
* Данные (статус, права) берутся из свежего списка с сервера; сохранённая копия — только пока список
|
||||||
|
* не загрузился или если проекта в нём нет. Иначе после смены статуса / прав F5 показывал бы старые.
|
||||||
|
*/
|
||||||
|
export function resolveCurrentProject(
|
||||||
|
projects: IAccessibleProject[],
|
||||||
|
routeProjectId: number | undefined,
|
||||||
|
storedProject: IAccessibleProject | undefined
|
||||||
|
): IAccessibleProject | undefined {
|
||||||
|
const targetId = routeProjectId ?? storedProject?.id;
|
||||||
|
if (targetId === undefined) return undefined;
|
||||||
|
const fresh = projects.filter((project) => project.id === targetId)[0];
|
||||||
|
if (fresh) return fresh;
|
||||||
|
return storedProject?.id === targetId ? storedProject : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/** Выгрузка за период недоступна только для архивного проекта. */
|
/** Выгрузка за период недоступна только для архивного проекта. */
|
||||||
export function isExportAvailable(project: IAccessibleProject): boolean {
|
export function isExportAvailable(project: IAccessibleProject): boolean {
|
||||||
return getStatusKind(project.status) !== 'archive';
|
return getStatusKind(project.status) !== 'archive';
|
||||||
|
|||||||
Reference in New Issue
Block a user