различные правки
This commit is contained in:
@@ -4,6 +4,7 @@ import { getMyRequests } from './myRequests.service';
|
|||||||
import { IMyRequest } from './models/IMyRequest';
|
import { IMyRequest } from './models/IMyRequest';
|
||||||
import { REQUEST_STATUS } from '../../shared/approval';
|
import { REQUEST_STATUS } from '../../shared/approval';
|
||||||
import { navigate, routeUrl } from '../../shared/routing';
|
import { navigate, routeUrl } from '../../shared/routing';
|
||||||
|
import { onRequestsChanged } from '../../shared/requestEvents';
|
||||||
import styles from './MyRequestsPanel.module.scss';
|
import styles from './MyRequestsPanel.module.scss';
|
||||||
|
|
||||||
export interface IMyRequestsPanelProps {
|
export interface IMyRequestsPanelProps {
|
||||||
@@ -53,7 +54,10 @@ function statusClass(status: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const RequestItem: React.FC<{ r: IMyRequest }> = ({ r }) => (
|
// «Ждёt вашего решения»: автор здесь — не вы, поэтому важны email + имя,
|
||||||
|
// а статус не нужен (в этой секции все заявки по определению «на согласовании»).
|
||||||
|
// «Созданы вами»: автор — всегда вы, строку с ним не показываем, зато нужен статус.
|
||||||
|
const RequestItem: React.FC<{ r: IMyRequest; variant: 'own' | 'approval' }> = ({ r, variant }) => (
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
className={styles.item}
|
className={styles.item}
|
||||||
@@ -71,24 +75,31 @@ const RequestItem: React.FC<{ r: IMyRequest }> = ({ r }) => (
|
|||||||
>
|
>
|
||||||
<div className={styles.itemMain}>
|
<div className={styles.itemMain}>
|
||||||
<span className={styles.itemTitle}>{r.title || `#${r.id}`}</span>
|
<span className={styles.itemTitle}>{r.title || `#${r.id}`}</span>
|
||||||
<span className={styles.itemAuthor}>
|
{variant === 'approval' && (
|
||||||
{r.authorEmail || r.authorName || '— автор не определён —'}
|
<span className={styles.itemAuthor}>
|
||||||
</span>
|
{[r.authorName, r.authorEmail].filter(Boolean).join(' · ') || '— автор не определён —'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{r.created && <span className={styles.itemDate}>{formatDate(r.created)}</span>}
|
{r.created && <span className={styles.itemDate}>{formatDate(r.created)}</span>}
|
||||||
</div>
|
</div>
|
||||||
<span className={`${styles.status} ${statusClass(r.status)}`}>{r.status || '—'}</span>
|
{variant === 'own' && (
|
||||||
|
<span className={`${styles.status} ${statusClass(r.status)}`}>{r.status || '—'}</span>
|
||||||
|
)}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|
||||||
const RequestGroups: React.FC<{ rows: IMyRequest[] }> = ({ rows }) => (
|
const RequestGroups: React.FC<{ rows: IMyRequest[]; variant: 'own' | 'approval' }> = ({
|
||||||
|
rows,
|
||||||
|
variant,
|
||||||
|
}) => (
|
||||||
<>
|
<>
|
||||||
{groupByType(rows).map((group) => (
|
{groupByType(rows).map((group) => (
|
||||||
<div className={styles.group} key={group.requestTypeName}>
|
<div className={styles.group} key={group.requestTypeName}>
|
||||||
<div className={styles.groupTitle}>{group.requestTypeName}</div>
|
<div className={styles.groupTitle}>{group.requestTypeName}</div>
|
||||||
<ul className={styles.list}>
|
<ul className={styles.list}>
|
||||||
{group.rows.map((r) => (
|
{group.rows.map((r) => (
|
||||||
<RequestItem r={r} key={`${r.requestTypeId}-${r.id}`} />
|
<RequestItem r={r} variant={variant} key={`${r.requestTypeId}-${r.id}`} />
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -102,6 +113,11 @@ const RequestGroups: React.FC<{ rows: IMyRequest[] }> = ({ rows }) => (
|
|||||||
// Владелец: второй разработчик. Вся логика/данные — внутри этой папки.
|
// Владелец: второй разработчик. Вся логика/данные — внутри этой папки.
|
||||||
// Общий UI-кит (shared/ui), pnpjs и config можно импортировать, но НЕ менять.
|
// Общий UI-кит (shared/ui), pnpjs и config можно импортировать, но НЕ менять.
|
||||||
//
|
//
|
||||||
|
// Перечитывает данные по событию rp-requests-changed (shared/requestEvents) —
|
||||||
|
// оно шлётся после сохранения формы (SubmitFeature) и после решения
|
||||||
|
// согласующего (ApproverService.decide), так что панель обновляется сама,
|
||||||
|
// без перезагрузки страницы.
|
||||||
|
//
|
||||||
// Диагностика по типам заявок (listId/siteUrl/rowCount/error) в UI не выводится —
|
// Диагностика по типам заявок (listId/siteUrl/rowCount/error) в UI не выводится —
|
||||||
// см. console.table в devtools при загрузке панели.
|
// см. console.table в devtools при загрузке панели.
|
||||||
const MyRequestsPanel: React.FC<IMyRequestsPanelProps> = ({ context }) => {
|
const MyRequestsPanel: React.FC<IMyRequestsPanelProps> = ({ context }) => {
|
||||||
@@ -110,17 +126,25 @@ const MyRequestsPanel: React.FC<IMyRequestsPanelProps> = ({ context }) => {
|
|||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
getMyRequests(context)
|
|
||||||
.then((result) => {
|
const load = (): void => {
|
||||||
if (cancelled) return;
|
getMyRequests(context)
|
||||||
setRows(result.rows);
|
.then((result) => {
|
||||||
console.table(result.debug);
|
if (cancelled) return;
|
||||||
})
|
setRows(result.rows);
|
||||||
.catch((e) => {
|
setError('');
|
||||||
if (!cancelled) setError((e as Error).message);
|
console.table(result.debug);
|
||||||
});
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (!cancelled) setError((e as Error).message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
load();
|
||||||
|
const unsubscribe = onRequestsChanged(load);
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true;
|
cancelled = true;
|
||||||
|
unsubscribe();
|
||||||
};
|
};
|
||||||
}, [context]);
|
}, [context]);
|
||||||
|
|
||||||
@@ -142,14 +166,14 @@ const MyRequestsPanel: React.FC<IMyRequestsPanelProps> = ({ context }) => {
|
|||||||
{pendingApproval.length > 0 && (
|
{pendingApproval.length > 0 && (
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<div className={styles.sectionTitle}>Ждёт вашего решения</div>
|
<div className={styles.sectionTitle}>Ждёт вашего решения</div>
|
||||||
<RequestGroups rows={pendingApproval} />
|
<RequestGroups rows={pendingApproval} variant="approval" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{own.length > 0 && (
|
{own.length > 0 && (
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<div className={styles.sectionTitle}>Созданы вами</div>
|
<div className={styles.sectionTitle}>Созданы вами</div>
|
||||||
<RequestGroups rows={own} />
|
<RequestGroups rows={own} variant="own" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { RequestGrid } from '../../components/RequestGrid/RequestGrid';
|
|||||||
import { getRequestsByCategory, REQUEST_TYPES } from '../../data/requests';
|
import { getRequestsByCategory, REQUEST_TYPES } from '../../data/requests';
|
||||||
import { getFormComponent } from '../../forms/registry';
|
import { getFormComponent } from '../../forms/registry';
|
||||||
import { IRequestType } from '../../models/types';
|
import { IRequestType } from '../../models/types';
|
||||||
|
import { emitRequestsChanged } from '../../shared/requestEvents';
|
||||||
|
|
||||||
export interface ISubmitFeatureProps {
|
export interface ISubmitFeatureProps {
|
||||||
context: WebPartContext;
|
context: WebPartContext;
|
||||||
@@ -60,7 +61,13 @@ const SubmitFeature: React.FC<ISubmitFeatureProps> = ({ context, activeCategoryI
|
|||||||
// Ленивый рендер формы: её код подтягивается только сейчас, при открытии.
|
// Ленивый рендер формы: её код подтягивается только сейчас, при открытии.
|
||||||
return (
|
return (
|
||||||
<React.Suspense fallback={<div className={styles.loading}>Загрузка формы…</div>}>
|
<React.Suspense fallback={<div className={styles.loading}>Загрузка формы…</div>}>
|
||||||
<FormComponent requestType={request} context={context} onCancel={handleBack} sourceId={sourceId} />
|
<FormComponent
|
||||||
|
requestType={request}
|
||||||
|
context={context}
|
||||||
|
onCancel={handleBack}
|
||||||
|
onSaved={emitRequestsChanged}
|
||||||
|
sourceId={sourceId}
|
||||||
|
/>
|
||||||
</React.Suspense>
|
</React.Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ApprovalListService } from './approvalList.service';
|
|||||||
import { REQUEST_STATUS, APPROVAL_PHASE } from './models/statuses';
|
import { REQUEST_STATUS, APPROVAL_PHASE } from './models/statuses';
|
||||||
import { IApprovalRow } from './models/IApprovalRow';
|
import { IApprovalRow } from './models/IApprovalRow';
|
||||||
import { ISiteUser } from './models/ISiteUser';
|
import { ISiteUser } from './models/ISiteUser';
|
||||||
|
import { emitRequestsChanged } from '../requestEvents';
|
||||||
|
|
||||||
export type ApproverDecision = 'approve' | 'return' | 'reject';
|
export type ApproverDecision = 'approve' | 'return' | 'reject';
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ export class ApproverService {
|
|||||||
const rows = await this.approval.getForRequest(requestId);
|
const rows = await this.approval.getForRequest(requestId);
|
||||||
const requestStatus = this.computeRequestStatus(rows);
|
const requestStatus = this.computeRequestStatus(rows);
|
||||||
await this.updateRequestStatus(requestId, requestStatus);
|
await this.updateRequestStatus(requestId, requestStatus);
|
||||||
|
emitRequestsChanged();
|
||||||
return requestStatus;
|
return requestStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// Общее событие «список заявок изменился»: создана новая заявка или принято
|
||||||
|
// решение согласующего. Панель «Текущие заявки» (features/myRequests) подписана
|
||||||
|
// и перечитывает данные. Через window.dispatchEvent — проще шины состояния,
|
||||||
|
// достаточно для нечастых событий (сохранение формы, решение согласующего).
|
||||||
|
const EVENT_NAME = 'rp-requests-changed';
|
||||||
|
|
||||||
|
export function emitRequestsChanged(): void {
|
||||||
|
window.dispatchEvent(new Event(EVENT_NAME));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Подписка на изменения. Возвращает функцию отписки (вызвать в cleanup эффекта). */
|
||||||
|
export function onRequestsChanged(handler: () => void): () => void {
|
||||||
|
window.addEventListener(EVENT_NAME, handler);
|
||||||
|
return () => window.removeEventListener(EVENT_NAME, handler);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user