fix client chat v2

This commit is contained in:
TronoSfera 2026-02-27 21:10:01 +03:00
parent 7b509f6af3
commit df80b5cb5f
7 changed files with 214 additions and 149 deletions

View file

@ -157,6 +157,7 @@ def _serialize_public_service_request(row: RequestServiceRequest) -> PublicServi
id=row.id, id=row.id,
request_id=row.request_id, request_id=row.request_id,
client_id=row.client_id, client_id=row.client_id,
assigned_lawyer_id=row.assigned_lawyer_id,
type=str(row.type or ""), type=str(row.type or ""),
status=str(row.status or "NEW"), status=str(row.status or "NEW"),
body=str(row.body or ""), body=str(row.body or ""),

View file

@ -76,6 +76,7 @@ class PublicServiceRequestRead(BaseModel):
id: UUID id: UUID
request_id: UUID request_id: UUID
client_id: Optional[UUID] = None client_id: Optional[UUID] = None
assigned_lawyer_id: Optional[UUID] = None
type: str type: str
status: str status: str
body: str body: str

View file

@ -173,6 +173,7 @@ export function RequestWorkspace({
const canRequestData = viewerRoleCode === "LAWYER" || viewerRoleCode === "ADMIN"; const canRequestData = viewerRoleCode === "LAWYER" || viewerRoleCode === "ADMIN";
const canFillRequestData = viewerRoleCode === "CLIENT"; const canFillRequestData = viewerRoleCode === "CLIENT";
const canSeeRate = viewerRoleCode !== "CLIENT"; const canSeeRate = viewerRoleCode !== "CLIENT";
const canSeeCreatedUpdatedInCard = viewerRoleCode !== "CLIENT";
const safeMessages = Array.isArray(messages) ? messages : []; const safeMessages = Array.isArray(messages) ? messages : [];
const safeAttachments = Array.isArray(attachments) ? attachments : []; const safeAttachments = Array.isArray(attachments) ? attachments : [];
const safeStatusHistory = Array.isArray(statusHistory) ? statusHistory : []; const safeStatusHistory = Array.isArray(statusHistory) ? statusHistory : [];
@ -424,6 +425,20 @@ export function RequestWorkspace({
openPreview(item); openPreview(item);
}; };
const downloadAttachment = (item) => {
const url = String(item?.download_url || "").trim();
if (!url) return;
const link = document.createElement("a");
link.href = url;
link.target = "_blank";
link.rel = "noreferrer";
const fileName = String(item?.file_name || "").trim();
if (fileName) link.download = fileName;
document.body.appendChild(link);
link.click();
link.remove();
};
useEffect(() => { useEffect(() => {
liveCursorRef.current = localActivityCursor || ""; liveCursorRef.current = localActivityCursor || "";
}, [localActivityCursor, row?.id]); }, [localActivityCursor, row?.id]);
@ -1287,14 +1302,18 @@ export function RequestWorkspace({
{lawyerLabel} {lawyerLabel}
</span> </span>
</div> </div>
<div className="request-field"> {canSeeCreatedUpdatedInCard ? (
<span className="request-field-label">Создана</span> <>
<span className="request-field-value">{fmtShortDateTime(row.created_at)}</span> <div className="request-field">
</div> <span className="request-field-label">Создана</span>
<div className="request-field"> <span className="request-field-value">{fmtShortDateTime(row.created_at)}</span>
<span className="request-field-label">Изменена</span> </div>
<span className="request-field-value">{fmtShortDateTime(row.updated_at)}</span> <div className="request-field">
</div> <span className="request-field-label">Изменена</span>
<span className="request-field-value">{fmtShortDateTime(row.updated_at)}</span>
</div>
</>
) : null}
</div> </div>
<div className="request-status-route"> <div className="request-status-route">
<h4>Маршрут статусов</h4> <h4>Маршрут статусов</h4>
@ -2372,7 +2391,7 @@ export function RequestWorkspace({
{isFile ? ( {isFile ? (
value && typeof value === "object" ? ( value && typeof value === "object" ? (
<div className="request-data-summary-file"> <div className="request-data-summary-file">
<button type="button" className="chat-message-file-chip" onClick={() => openAttachmentFromMessage(value)}> <button type="button" className="chat-message-file-chip" onClick={() => downloadAttachment(value)}>
<span className="chat-message-file-icon" aria-hidden="true">📎</span> <span className="chat-message-file-icon" aria-hidden="true">📎</span>
<span className="chat-message-file-name">{String(value.file_name || "Файл")}</span> <span className="chat-message-file-name">{String(value.file_name || "Файл")}</span>
</button> </button>

View file

@ -37,27 +37,28 @@
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
.client-summary-actions { .client-help-modal {
margin-top: 0.75rem; width: min(760px, 100%);
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
} }
.client-service-requests { .client-help-stack {
margin-top: 0.85rem; display: grid;
gap: 0.75rem;
} }
.client-service-requests h3 { .client-help-block {
margin: 0 0 0.65rem; border: 1px solid var(--line);
border-radius: 12px;
background: rgba(255, 255, 255, 0.02);
padding: 0.7rem;
display: grid;
gap: 0.6rem;
} }
.service-request-modal { .client-help-description {
width: min(720px, 100%); margin: 0;
} color: #dbe6f4;
line-height: 1.45;
.service-request-form textarea {
min-height: 150px;
} }
#client-page-status { #client-page-status {

View file

@ -5,17 +5,6 @@ import { detectAttachmentPreviewKind, fmtShortDateTime } from "./admin/shared/ut
(function () { (function () {
const { useCallback, useEffect, useMemo, useRef, useState } = React; const { useCallback, useEffect, useMemo, useRef, useState } = React;
const SERVICE_REQUEST_TYPE_LABELS = {
CURATOR_CONTACT: "Запрос к куратору",
LAWYER_CHANGE_REQUEST: "Смена юриста",
};
const SERVICE_REQUEST_STATUS_LABELS = {
NEW: "Новый",
IN_PROGRESS: "В работе",
RESOLVED: "Решен",
REJECTED: "Отклонен",
};
function StatusLine({ status }) { function StatusLine({ status }) {
return <p className={"status" + (status?.kind ? " " + status.kind : "")}>{status?.message || ""}</p>; return <p className={"status" + (status?.kind ? " " + status.kind : "")}>{status?.message || ""}</p>;
} }
@ -270,82 +259,93 @@ import { detectAttachmentPreviewKind, fmtShortDateTime } from "./admin/shared/ut
); );
} }
function ServiceRequestModal({ open, type, body, status, loading, onBodyChange, onClose, onSubmit }) { function ClientHelpModal({
const title = type === "LAWYER_CHANGE_REQUEST" ? "Запрос на смену юриста" : "Обращение к куратору"; open,
status,
loadingType,
lawyerChangeReason,
curatorBlocked,
lawyerChangeBlocked,
onClose,
onReasonChange,
onSubmitCurator,
onSubmitLawyerChange,
}) {
return ( return (
<div className={"overlay" + (open ? " open" : "")} id="service-request-overlay" onClick={(event) => event.target.id === "service-request-overlay" && onClose()}> <div className={"overlay" + (open ? " open" : "")} id="client-help-overlay" onClick={(event) => event.target.id === "client-help-overlay" && onClose()}>
<div className="modal service-request-modal" onClick={(event) => event.stopPropagation()}> <div className="modal client-help-modal" onClick={(event) => event.stopPropagation()}>
<div className="modal-head"> <div className="modal-head">
<div> <div>
<h3 id="service-request-title">{title}</h3> <h3 id="client-help-title">Помощь по заявке</h3>
</div> </div>
<button className="close" type="button" id="service-request-close" onClick={onClose} aria-label="Закрыть"> <button className="close" type="button" id="client-help-close" onClick={onClose} aria-label="Закрыть">
× ×
</button> </button>
</div> </div>
<form id="service-request-form" className="stack service-request-form" onSubmit={onSubmit}> <div className="client-help-stack">
<div className="field"> <section className="client-help-block">
<label htmlFor="service-request-body">Сообщение</label> <p className="client-help-description">
<textarea Если нужна дополнительная поддержка по делу, можно обратиться к куратору. Запрос отправляется администратору платформы.
id="service-request-body" </p>
value={body} <button
onChange={onBodyChange} className="btn secondary btn-sm"
maxLength={4000} id="cabinet-curator-request-open"
placeholder="Опишите обращение" type="button"
disabled={loading} disabled={curatorBlocked || loadingType === "CURATOR_CONTACT"}
/> onClick={onSubmitCurator}
</div> >
<div className="modal-actions modal-actions-right"> {loadingType === "CURATOR_CONTACT" ? "Отправка..." : "Обратиться к куратору"}
<button className="btn btn-sm" id="service-request-send" type="submit" disabled={loading}>
{loading ? "Отправка..." : "Отправить"}
</button> </button>
</div> </section>
<section className="client-help-block">
<p className="client-help-description">
Если текущий юрист не устраивает, можно запросить его смену. Укажите причину для администратора.
</p>
<div className="field">
<label htmlFor="service-request-body">Причина смены юриста</label>
<textarea
id="service-request-body"
value={lawyerChangeReason}
onChange={onReasonChange}
maxLength={4000}
placeholder="Опишите причину"
disabled={lawyerChangeBlocked || loadingType === "LAWYER_CHANGE_REQUEST"}
/>
</div>
<button
className="btn secondary btn-sm"
id="cabinet-lawyer-change-open"
type="button"
disabled={lawyerChangeBlocked || loadingType === "LAWYER_CHANGE_REQUEST"}
onClick={onSubmitLawyerChange}
>
{loadingType === "LAWYER_CHANGE_REQUEST" ? "Отправка..." : "Запросить смену"}
</button>
</section>
<StatusLine status={status} /> <StatusLine status={status} />
</form> </div>
</div> </div>
</div> </div>
); );
} }
function ServiceRequestList({ rows }) {
const safeRows = Array.isArray(rows) ? rows : [];
return (
<ul className="simple-list request-modal-list" id="cabinet-service-requests">
{safeRows.length ? (
safeRows.map((item) => {
const typeCode = String(item?.type || "").toUpperCase();
const statusCode = String(item?.status || "").toUpperCase();
return (
<li key={String(item.id)} className="simple-item">
<div>{(SERVICE_REQUEST_TYPE_LABELS[typeCode] || typeCode || "Запрос") + " • " + (SERVICE_REQUEST_STATUS_LABELS[statusCode] || statusCode || "NEW")}</div>
<div className="muted request-modal-item-meta">{fmtShortDateTime(item?.created_at)}</div>
{item?.body ? <p>{String(item.body)}</p> : null}
</li>
);
})
) : (
<li className="muted">Обращений пока нет</li>
)}
</ul>
);
}
function App() { function App() {
const [requestModal, setRequestModal] = useState(createRequestModalState()); const [requestModal, setRequestModal] = useState(createRequestModalState());
const [requestsList, setRequestsList] = useState([]); const [requestsList, setRequestsList] = useState([]);
const [activeTrack, setActiveTrack] = useState(""); const [activeTrack, setActiveTrack] = useState("");
const [status, setStatus] = useState({ message: "", kind: "" }); const [status, setStatus] = useState({ message: "", kind: "" });
const [serviceRequests, setServiceRequests] = useState([]); const [serviceRequests, setServiceRequests] = useState([]);
const [serviceRequestModal, setServiceRequestModal] = useState({ open: false, type: "", body: "", loading: false, status: { message: "", kind: "" } }); const [clientHelpModal, setClientHelpModal] = useState({
open: false,
loadingType: "",
lawyerChangeReason: "",
status: { message: "", kind: "" },
});
const setPageStatus = useCallback((message, kind) => { const setPageStatus = useCallback((message, kind) => {
setStatus({ message: String(message || ""), kind: kind || "" }); setStatus({ message: String(message || ""), kind: kind || "" });
}, []); }, []);
const setServiceStatus = useCallback((message, kind) => {
setServiceRequestModal((prev) => ({ ...prev, status: { message: String(message || ""), kind: kind || "" } }));
}, []);
const apiError = (data, fallback) => { const apiError = (data, fallback) => {
if (data && typeof data.detail === "string" && data.detail.trim()) return data.detail; if (data && typeof data.detail === "string" && data.detail.trim()) return data.detail;
return fallback; return fallback;
@ -656,60 +656,103 @@ import { detectAttachmentPreviewKind, fmtShortDateTime } from "./admin/shared/ut
[activeTrack, apiJson] [activeTrack, apiJson]
); );
const openServiceRequestModal = useCallback((type) => { const openClientHelpModal = useCallback(() => {
const normalized = String(type || "").trim().toUpperCase(); setClientHelpModal((prev) => ({
if (!normalized) return; ...prev,
setServiceRequestModal({
open: true, open: true,
type: normalized,
body: "",
loading: false,
status: { message: "", kind: "" }, status: { message: "", kind: "" },
}); }));
}, []); }, []);
const closeServiceRequestModal = useCallback(() => { const closeClientHelpModal = useCallback(() => {
setServiceRequestModal({ open: false, type: "", body: "", loading: false, status: { message: "", kind: "" } }); setClientHelpModal((prev) => ({
...prev,
open: false,
loadingType: "",
status: { message: "", kind: "" },
}));
}, []); }, []);
const normalizedCurrentLawyerId = String(requestModal.requestData?.assigned_lawyer_id || "")
.trim()
.toLowerCase();
const serviceRequestState = useMemo(() => {
const rows = Array.isArray(serviceRequests) ? serviceRequests : [];
const curatorRows = rows.filter((item) => String(item?.type || "").toUpperCase() === "CURATOR_CONTACT");
const lawyerRows = rows.filter((item) => String(item?.type || "").toUpperCase() === "LAWYER_CHANGE_REQUEST");
const latestLawyerChange = lawyerRows[0] || null;
const hasCuratorRequest = curatorRows.length > 0;
let lawyerChangeDisabledByState = false;
if (latestLawyerChange) {
const hasLawyerSnapshot = Object.prototype.hasOwnProperty.call(latestLawyerChange, "assigned_lawyer_id");
if (hasLawyerSnapshot) {
const requestedForLawyer = String(latestLawyerChange?.assigned_lawyer_id || "")
.trim()
.toLowerCase();
lawyerChangeDisabledByState = requestedForLawyer === normalizedCurrentLawyerId;
} else {
const statusCode = String(latestLawyerChange?.status || "").toUpperCase();
lawyerChangeDisabledByState = statusCode === "NEW" || statusCode === "IN_PROGRESS";
}
}
return {
hasCuratorRequest,
lawyerChangeDisabledByState,
};
}, [normalizedCurrentLawyerId, serviceRequests]);
const canInteract = Boolean(requestModal.requestData && !requestModal.loading);
const curatorBlocked = !canInteract || serviceRequestState.hasCuratorRequest;
const lawyerChangeBlocked = !canInteract || serviceRequestState.lawyerChangeDisabledByState;
const submitServiceRequest = useCallback( const submitServiceRequest = useCallback(
async (event) => { async ({ type, body }) => {
if (event && typeof event.preventDefault === "function") event.preventDefault();
const track = String(activeTrack || "").trim(); const track = String(activeTrack || "").trim();
const requestType = String(type || "").trim().toUpperCase();
const message = String(body || "").trim();
if (!track) { if (!track) {
setServiceStatus("Сначала выберите заявку.", "error"); setClientHelpModal((prev) => ({ ...prev, status: { message: "Сначала выберите заявку.", kind: "error" } }));
return; return;
} }
const requestType = String(serviceRequestModal.type || "").trim().toUpperCase();
const body = String(serviceRequestModal.body || "").trim();
if (!requestType) { if (!requestType) {
setServiceStatus("Выберите тип обращения.", "error"); setClientHelpModal((prev) => ({ ...prev, status: { message: "Не указан тип запроса.", kind: "error" } }));
return; return;
} }
if (body.length < 3) { if (message.length < 3) {
setServiceStatus("Сообщение должно содержать минимум 3 символа.", "error"); setClientHelpModal((prev) => ({ ...prev, status: { message: "Сообщение должно содержать минимум 3 символа.", kind: "error" } }));
return; return;
} }
try { try {
setServiceRequestModal((prev) => ({ ...prev, loading: true, status: { message: "", kind: "" } })); setClientHelpModal((prev) => ({ ...prev, loadingType: requestType, status: { message: "", kind: "" } }));
await apiJson( await apiJson(
"/api/public/requests/" + encodeURIComponent(track) + "/service-requests", "/api/public/requests/" + encodeURIComponent(track) + "/service-requests",
{ {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ type: requestType, body }), body: JSON.stringify({ type: requestType, body: message }),
}, },
"Не удалось отправить обращение" "Не удалось отправить обращение"
); );
await loadRequestWorkspace(track, false); await loadRequestWorkspace(track, false);
setPageStatus("Обращение отправлено.", "ok"); setPageStatus("Обращение отправлено.", "ok");
closeServiceRequestModal(); setClientHelpModal((prev) => ({
...prev,
loadingType: "",
status: { message: "Обращение отправлено.", kind: "ok" },
lawyerChangeReason: requestType === "LAWYER_CHANGE_REQUEST" ? "" : prev.lawyerChangeReason,
}));
} catch (error) { } catch (error) {
setServiceRequestModal((prev) => ({ ...prev, loading: false })); setClientHelpModal((prev) => ({
setServiceStatus(error?.message || "Не удалось отправить обращение", "error"); ...prev,
loadingType: "",
status: { message: error?.message || "Не удалось отправить обращение", kind: "error" },
}));
} }
}, },
[activeTrack, apiJson, closeServiceRequestModal, loadRequestWorkspace, serviceRequestModal.body, serviceRequestModal.type, setPageStatus, setServiceStatus] [activeTrack, apiJson, loadRequestWorkspace, setPageStatus]
); );
useEffect(() => { useEffect(() => {
@ -721,7 +764,6 @@ import { detectAttachmentPreviewKind, fmtShortDateTime } from "./admin/shared/ut
}, [loadMyRequests, setPageStatus]); }, [loadMyRequests, setPageStatus]);
const summary = requestModal.requestData || null; const summary = requestModal.requestData || null;
const canInteract = Boolean(summary && !requestModal.loading);
return ( return (
<div className="client-page-shell"> <div className="client-page-shell">
@ -731,9 +773,17 @@ import { detectAttachmentPreviewKind, fmtShortDateTime } from "./admin/shared/ut
<h1>Кабинет клиента</h1> <h1>Кабинет клиента</h1>
<p className="muted">Работа с заявками: статусы, чат, файлы и обращения.</p> <p className="muted">Работа с заявками: статусы, чат, файлы и обращения.</p>
</div> </div>
<div style={{ display: "flex", gap: "0.45rem", flexWrap: "wrap" }}> <button
<a className="btn secondary btn-sm" href="/">На лендинг</a> className="icon-btn workspace-head-icon"
</div> id="cabinet-help-open"
type="button"
data-tooltip="Помощь и обращения"
aria-label="Помощь и обращения"
disabled={!canInteract}
onClick={openClientHelpModal}
>
?
</button>
</div> </div>
<section className="section active client-section"> <section className="section active client-section">
@ -795,14 +845,6 @@ import { detectAttachmentPreviewKind, fmtShortDateTime } from "./admin/shared/ut
<span className="request-field-value" id="cabinet-request-updated">{summary ? fmtShortDateTime(summary.updated_at) : "-"}</span> <span className="request-field-value" id="cabinet-request-updated">{summary ? fmtShortDateTime(summary.updated_at) : "-"}</span>
</div> </div>
</div> </div>
<div className="client-summary-actions">
<button className="btn secondary btn-sm" id="cabinet-curator-request-open" type="button" disabled={!canInteract} onClick={() => openServiceRequestModal("CURATOR_CONTACT")}>
Обратиться к куратору
</button>
<button className="btn secondary btn-sm" id="cabinet-lawyer-change-open" type="button" disabled={!canInteract} onClick={() => openServiceRequestModal("LAWYER_CHANGE_REQUEST")}>
Запросить смену юриста
</button>
</div>
</div> </div>
<RequestWorkspace <RequestWorkspace
@ -848,23 +890,26 @@ import { detectAttachmentPreviewKind, fmtShortDateTime } from "./admin/shared/ut
dataRequestSave: "data-request-save", dataRequestSave: "data-request-save",
}} }}
/> />
<div className="block client-service-requests">
<h3>Мои обращения</h3>
<ServiceRequestList rows={serviceRequests} />
</div>
</section> </section>
<p className="status" id="client-page-status">{status.message}</p> <p className="status" id="client-page-status">{status.message}</p>
</main> </main>
<ServiceRequestModal <ClientHelpModal
open={serviceRequestModal.open} open={clientHelpModal.open}
type={serviceRequestModal.type} status={clientHelpModal.status}
body={serviceRequestModal.body} loadingType={clientHelpModal.loadingType}
status={serviceRequestModal.status} lawyerChangeReason={clientHelpModal.lawyerChangeReason}
loading={serviceRequestModal.loading} curatorBlocked={curatorBlocked}
onBodyChange={(event) => setServiceRequestModal((prev) => ({ ...prev, body: event.target.value }))} lawyerChangeBlocked={lawyerChangeBlocked}
onClose={closeServiceRequestModal} onClose={closeClientHelpModal}
onSubmit={submitServiceRequest} onReasonChange={(event) =>
setClientHelpModal((prev) => ({
...prev,
lawyerChangeReason: event.target.value,
status: { message: "", kind: "" },
}))
}
onSubmitCurator={() => submitServiceRequest({ type: "CURATOR_CONTACT", body: "Прошу подключить куратора к текущей заявке." })}
onSubmitLawyerChange={() => submitServiceRequest({ type: "LAWYER_CHANGE_REQUEST", body: clientHelpModal.lawyerChangeReason })}
/> />
<GlobalTooltipLayer /> <GlobalTooltipLayer />
</div> </div>

View file

@ -88,7 +88,7 @@ test("request data file field flow via UI: lawyer requests file -> client upload
}); });
await page.locator("#data-request-save").click(); await page.locator("#data-request-save").click();
await expect(page.locator("#data-request-status")).toContainText("Данные сохранены."); await expect(page.locator("#data-request-overlay")).not.toHaveClass(/open/);
await expect(page.locator("#cabinet-messages .request-data-item.done").last()).toBeVisible(); await expect(page.locator("#cabinet-messages .request-data-item.done").last()).toBeVisible();
await page.goto("/admin"); await page.goto("/admin");

View file

@ -35,23 +35,21 @@ test("service requests UI flow: client creates requests -> admin sees them in Re
trackCleanupTrack(testInfo, trackNumber); trackCleanupTrack(testInfo, trackNumber);
await openPublicCabinet(page, trackNumber); await openPublicCabinet(page, trackNumber);
await page.locator("#cabinet-curator-request-open").click(); await page.locator("#cabinet-help-open").click();
await expect(page.locator("#service-request-overlay")).toHaveClass(/open/); await expect(page.locator("#client-help-overlay")).toHaveClass(/open/);
await page.locator("#service-request-body").fill("Нужна консультация куратора по делу.");
await page.locator("#service-request-send").click();
await expect(page.locator("#client-page-status")).toContainText("Обращение отправлено.");
await expect(page.locator("#cabinet-service-requests")).toContainText("Запрос к куратору");
await page.locator("#cabinet-lawyer-change-open").click(); await page.locator("#cabinet-curator-request-open").click();
await expect(page.locator("#service-request-overlay")).toHaveClass(/open/);
await page.locator("#service-request-body").fill("Прошу рассмотреть смену юриста.");
await page.locator("#service-request-send").click();
await expect(page.locator("#client-page-status")).toContainText("Обращение отправлено."); await expect(page.locator("#client-page-status")).toContainText("Обращение отправлено.");
await expect(page.locator("#cabinet-service-requests")).toContainText("Смена юриста"); await expect(page.locator("#cabinet-curator-request-open")).toBeDisabled();
await page.locator("#service-request-body").fill("Прошу рассмотреть смену юриста.");
await page.locator("#cabinet-lawyer-change-open").click();
await expect(page.locator("#client-page-status")).toContainText("Обращение отправлено.");
await expect(page.locator("#cabinet-lawyer-change-open")).toBeDisabled();
await loginAdminPanel(page, { email: "admin@example.com", password: "admin123" }); await loginAdminPanel(page, { email: "admin@example.com", password: "admin123" });
await page.locator("aside .menu button[data-section='serviceRequests']").click(); await page.locator("aside .menu button[data-section='serviceRequests']").click();
await expect(page.locator("#section-service-requests h2")).toHaveText("Запросы"); await expect(page.locator("#section-service-requests h2")).toHaveText("Запросы");
await expect(page.locator("#section-service-requests table")).toContainText("Нужна консультация куратора"); await expect(page.locator("#section-service-requests table")).toContainText("Прошу подключить куратора к текущей заявке.");
await expect(page.locator("#section-service-requests table")).toContainText("Прошу рассмотреть смену юриста"); await expect(page.locator("#section-service-requests table")).toContainText("Прошу рассмотреть смену юриста");
}); });