e7527081b4
Add the "binder" section (?page=binder&projectId=): links a SharePoint project to the TNSU API project catalogue by writing UniqueId0/UniqueName to the "Проекты" list. The class component is rewritten as function components with hooks while keeping the legacy behaviour and texts: the same project GET with odata=nometadata and empty odata-version, MERGE with IF-MATCH * for bind/unbind (no confirmation), catalogue search with 400 ms debounce and 20-per-page paging, card select/deselect, status colours, ru-RU dates and HTML-entity decoding of the address. Fluent UI is replaced with ui-kit. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
import * as React from 'react';
|
|
import { Alert, Spinner } from '@tnsu/ui-kit';
|
|
import { useRouter } from '../../app/routing/RouterContext';
|
|
import { isProjectBound, parseProjectId } from './binderRules';
|
|
import { ICatalogProject } from './binderTypes';
|
|
import { useBinderProject } from './useBinderProject';
|
|
import { useProjectBinding } from './useProjectBinding';
|
|
import { useCatalogSearch } from './useCatalogSearch';
|
|
import { ProjectCard } from './ProjectCard';
|
|
import { CatalogSection } from './CatalogSection';
|
|
import { DismissibleAlert } from './DismissibleAlert';
|
|
import { BINDER_TEXTS } from './texts';
|
|
import styles from './BinderPage.module.scss';
|
|
|
|
/** Привязка к справочнику (бывший stroy-bot-projects-binder): связь проекта SharePoint с проектом API. */
|
|
export const BinderPage: React.FC = () => {
|
|
const { route } = useRouter();
|
|
const projectId = parseProjectId(route.projectId);
|
|
const { project, setProject, isLoading, projectError } = useBinderProject(projectId);
|
|
const binding = useProjectBinding(project, setProject);
|
|
const catalog = useCatalogSearch(!isLoading);
|
|
|
|
if (isLoading) {
|
|
return <Spinner size="large" label={BINDER_TEXTS.loadingProject} block />;
|
|
}
|
|
|
|
const onBind = (selected: ICatalogProject): void => {
|
|
binding
|
|
.bind(selected)
|
|
.then((isSaved) => {
|
|
if (isSaved) catalog.clearSelection();
|
|
})
|
|
.catch(() => undefined);
|
|
};
|
|
|
|
return (
|
|
<div className={styles.page}>
|
|
{projectError && <Alert variant="warn">{projectError}</Alert>}
|
|
{project && (
|
|
<ProjectCard
|
|
project={project}
|
|
isSaving={binding.isSaving}
|
|
onUnbind={() => {
|
|
binding.unbind().catch(() => undefined);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{binding.successMessage && (
|
|
<DismissibleAlert variant="ok" dismissLabel={BINDER_TEXTS.dismiss} onDismiss={binding.clearSuccess}>
|
|
{binding.successMessage}
|
|
</DismissibleAlert>
|
|
)}
|
|
{binding.saveError && (
|
|
<DismissibleAlert variant="err" dismissLabel={BINDER_TEXTS.dismiss} onDismiss={binding.clearSaveError}>
|
|
{binding.saveError}
|
|
</DismissibleAlert>
|
|
)}
|
|
|
|
<hr className={styles.hairline} />
|
|
|
|
<CatalogSection catalog={catalog} isBound={isProjectBound(project)} isSaving={binding.isSaving} onBind={onBind} />
|
|
</div>
|
|
);
|
|
};
|