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 { fetchAccessibleProjects } from './projectRepository';
|
||||
import { readStoredProject, storeProject } from './projectSession';
|
||||
import { resolveCurrentProject } from './projectRules';
|
||||
|
||||
export type ProjectsLoadState = 'loading' | 'ready' | 'error';
|
||||
|
||||
@@ -51,13 +52,12 @@ export const ProjectProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
||||
if (isCancelled) return;
|
||||
setProjects(loaded);
|
||||
setLoadState('ready');
|
||||
// В сессии могли остаться устаревшие права — обновляем по свежим данным (как в main).
|
||||
// В сессии могли остаться устаревшие статус и права — сохраняем свежие.
|
||||
setStoredProject((stored) => {
|
||||
const fresh = stored ? findProject(loaded, stored.id) : undefined;
|
||||
if (!stored || !fresh) return stored;
|
||||
const updated = { ...stored, canEdit: fresh.canEdit };
|
||||
storeProject(updated);
|
||||
return updated;
|
||||
if (!fresh) return stored;
|
||||
storeProject(fresh);
|
||||
return fresh;
|
||||
});
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
@@ -71,13 +71,7 @@ export const ProjectProvider: React.FC<{ children: React.ReactNode }> = ({ child
|
||||
}, [services, reloadKey]);
|
||||
|
||||
const routeProjectId = route.projectId ? Number(route.projectId) : undefined;
|
||||
let currentProject: IAccessibleProject | undefined;
|
||||
if (routeProjectId === undefined) {
|
||||
currentProject = storedProject;
|
||||
} else {
|
||||
currentProject =
|
||||
findProject(projects, routeProjectId) ?? (storedProject?.id === routeProjectId ? storedProject : undefined);
|
||||
}
|
||||
const currentProject = resolveCurrentProject(projects, routeProjectId, storedProject);
|
||||
const currentProjectId = route.projectId ?? (storedProject ? String(storedProject.id) : undefined);
|
||||
|
||||
const selectProject = React.useCallback(
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isSectionAvailable,
|
||||
mergeAccessRows,
|
||||
pickRecentProjectIds,
|
||||
resolveCurrentProject,
|
||||
sortProjects
|
||||
} from './projectRules';
|
||||
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', () => {
|
||||
it('архивный проект — закрыты все разделы, кроме главной', () => {
|
||||
const archived = project({ status: 'архивный' });
|
||||
|
||||
@@ -85,6 +85,23 @@ export function isSectionAvailable(page: PageId, project: IAccessibleProject): b
|
||||
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 {
|
||||
return getStatusKind(project.status) !== 'archive';
|
||||
|
||||
Reference in New Issue
Block a user