feat: port stroy-bot-projects-binder as binder page
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>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user