style(analytics): мобильная раскладка списков воронки — без горизонтальной прокрутки
На 390px таблицы «Не поднимают трубку» и «Открыли ссылку, но не подтвердили» уезжали вправо (статус и телефон не видны без прокрутки). На мобильном они раскладываются списком: телефон и число попыток в строку, город/тип/статус и дата — ниже мелким. В таблице попыток процент ответов уходит во вторую строку, чтобы колонка «Не взяли» влезла. Тест на мобильную раскладку (8 проверок). SW v125.
This commit is contained in:
parent
188db318e3
commit
41ac30c090
|
|
@ -1,7 +1,7 @@
|
|||
const isLocalhost = self.location.hostname === "localhost" || self.location.hostname === "127.0.0.1";
|
||||
|
||||
if (!isLocalhost) {
|
||||
const STATIC_CACHE = "construction-delivery-static-v124";
|
||||
const STATIC_CACHE = "construction-delivery-static-v125";
|
||||
const RUNTIME_CACHE = "construction-delivery-runtime-v119";
|
||||
const APP_SHELL_URLS = ["/", "/client", "/index.html", "/manifest.webmanifest", "/icons/icon-192.png", "/icons/icon-512.png"];
|
||||
|
||||
|
|
|
|||
|
|
@ -207,7 +207,10 @@ export const CommsFunnelBlock = ({ funnel, isLoading, mobile = false, periodLabe
|
|||
<Td mobile={mobile} fontSize={fontSize}>{a.attempt}-я</Td>
|
||||
<Td mobile={mobile} fontSize={fontSize} align="right">{a.total}</Td>
|
||||
<Td mobile={mobile} fontSize={fontSize} align="right">
|
||||
{a.answered} <span style={{ color: 'var(--color-text-muted)' }}>({a.answered_pct}%)</span>
|
||||
{a.answered}
|
||||
{mobile
|
||||
? <div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)' }}>{a.answered_pct}%</div>
|
||||
: <span style={{ color: 'var(--color-text-muted)' }}> ({a.answered_pct}%)</span>}
|
||||
</Td>
|
||||
<Td mobile={mobile} fontSize={fontSize} align="right">{a.confirmed}</Td>
|
||||
<Td mobile={mobile} fontSize={fontSize} align="right" muted>{a.no_answer}</Td>
|
||||
|
|
@ -237,6 +240,34 @@ export const CommsFunnelBlock = ({ funnel, isLoading, mobile = false, periodLabe
|
|||
<div style={{ fontSize: fontSize.m, fontWeight: 600, color: 'var(--color-text)', marginBottom: '0.35rem' }}>
|
||||
Не поднимают трубку
|
||||
</div>
|
||||
{mobile ? (
|
||||
noAnswer.length === 0 ? (
|
||||
<div style={{ fontSize: fontSize.s, color: 'var(--color-text-muted)', padding: '0.6rem', textAlign: 'center' }}>
|
||||
Нет таких номеров за период
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{noAnswer.map((n) => (
|
||||
<div key={n.phone} style={{
|
||||
padding: '0.45rem 0', borderBottom: '1px solid var(--color-border, rgba(51,65,85,0.35))',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '0.5rem', alignItems: 'baseline' }}>
|
||||
<span style={{ fontSize: fontSize.m, color: 'var(--color-text)' }}>{n.phone}</span>
|
||||
<span style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)' }}>
|
||||
попыток: {n.attempts}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)', marginTop: '0.15rem' }}>
|
||||
{n.town} · {KIND_LABELS[n.kind] || n.kind} · {STATUS_LABELS[n.status] || n.status || '—'}
|
||||
</div>
|
||||
<div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)' }}>
|
||||
последний звонок {fmtDateTime(n.last_call)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -263,6 +294,7 @@ export const CommsFunnelBlock = ({ funnel, isLoading, mobile = false, periodLabe
|
|||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ── Открыли ссылку, но не подтвердили ───────────────────────────── */}
|
||||
|
|
@ -270,6 +302,32 @@ export const CommsFunnelBlock = ({ funnel, isLoading, mobile = false, periodLabe
|
|||
<div style={{ fontSize: fontSize.m, fontWeight: 600, color: 'var(--color-text)', marginBottom: '0.35rem' }}>
|
||||
Открыли ссылку, но не подтвердили
|
||||
</div>
|
||||
{mobile ? (
|
||||
openedNoConfirm.length === 0 ? (
|
||||
<div style={{ fontSize: fontSize.s, color: 'var(--color-text-muted)', padding: '0.6rem', textAlign: 'center' }}>
|
||||
Все, кто открыл ссылку, подтвердили
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{openedNoConfirm.map((o) => (
|
||||
<div key={o.num + o.phone} style={{
|
||||
padding: '0.45rem 0', borderBottom: '1px solid var(--color-border, rgba(51,65,85,0.35))',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '0.5rem', alignItems: 'baseline' }}>
|
||||
<span style={{ fontSize: fontSize.m, color: 'var(--color-text)' }}>{o.num}</span>
|
||||
<span style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)' }}>открытий: {o.opens}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)', marginTop: '0.15rem' }}>
|
||||
{o.name} · {o.phone}
|
||||
</div>
|
||||
<div style={{ fontSize: fontSize.xs, color: 'var(--color-text-muted)' }}>
|
||||
последнее открытие {fmtDateTime(o.opened)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
|
|
@ -294,6 +352,7 @@ export const CommsFunnelBlock = ({ funnel, isLoading, mobile = false, periodLabe
|
|||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
</Panel>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -97,6 +97,18 @@ describe("CommsFunnelBlock", () => {
|
|||
expect(empty).toContain("Подтверждений за период нет");
|
||||
});
|
||||
|
||||
it("на мобильном списки «не берут трубку» и «открыли без подтверждения» раскладываются без прокрутки", () => {
|
||||
const m = renderToStaticMarkup(<CommsFunnelBlock funnel={funnel} mobile periodLabel="30 дней" />);
|
||||
expect(m).toContain("попыток: 2");
|
||||
expect(m).toContain("Севастополь · самовывоз · В ручной работе");
|
||||
expect(m).toContain("последний звонок");
|
||||
expect(m).toContain("открытий: 14");
|
||||
expect(m).toContain("ЗАКАЗЧИК · 9788324113");
|
||||
// на мобильном процент по попыткам уходит во вторую строку, а не в скобках
|
||||
expect(m).not.toContain("(65.6%)");
|
||||
expect(m).toContain("65.6%");
|
||||
});
|
||||
|
||||
it("показывает заглушку загрузки, пока данных нет", () => {
|
||||
const loading = renderToStaticMarkup(<CommsFunnelBlock funnel={null} isLoading mobile={false} periodLabel="7 дней" />);
|
||||
expect(loading).toContain("Воронка коммуникаций");
|
||||
|
|
|
|||
Loading…
Reference in New Issue