chore: improve admin logs user cards
This commit is contained in:
@@ -389,6 +389,21 @@
|
||||
usersStore.openUser(uid, { pathContext: "payments" });
|
||||
}
|
||||
|
||||
function openLogsUserCard(userId) {
|
||||
const uid = Number(userId);
|
||||
if (!Number.isFinite(uid) || uid === 0) return;
|
||||
const next = normalizeSection("logs");
|
||||
sidebarOpen = false;
|
||||
if (active !== next) {
|
||||
active = next;
|
||||
paymentsStore.closePayment({ skipPush: true });
|
||||
supportStore.closeTicketView({ skipPush: true });
|
||||
onSectionChange(next);
|
||||
}
|
||||
usersStore.setActive(next);
|
||||
usersStore.openUser(uid, { skipPush: true, pathContext: "logs" });
|
||||
}
|
||||
|
||||
function openUserCard(userId) {
|
||||
const uid = Number(userId);
|
||||
if (!Number.isFinite(uid) || uid === 0) return;
|
||||
@@ -801,7 +816,7 @@
|
||||
{/if}
|
||||
|
||||
{#if active === "logs"}
|
||||
<LogsSection {at} {fmtDate} />
|
||||
<LogsSection {at} {fmtDate} onOpenUserCard={openLogsUserCard} />
|
||||
{/if}
|
||||
|
||||
{#if active === "support"}
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
AdminTable,
|
||||
AdminTableSkeleton,
|
||||
} from "$components/patterns/admin/index.js";
|
||||
import { User } from "$components/ui/icons.js";
|
||||
|
||||
export let at;
|
||||
export let fmtDate;
|
||||
export let onOpenUserCard = () => {};
|
||||
|
||||
const logsStore = getContext("logsStore");
|
||||
|
||||
@@ -25,6 +27,25 @@
|
||||
at("content", {}, "Контент"),
|
||||
];
|
||||
|
||||
function userDisplay(entry, kind) {
|
||||
const id = kind === "target" ? entry.target_user_id : entry.user_id;
|
||||
const label = kind === "target" ? entry.target_user_label : entry.user_label;
|
||||
if (label) return label;
|
||||
if (kind !== "target") {
|
||||
if (entry.telegram_first_name) return entry.telegram_first_name;
|
||||
if (entry.telegram_username) {
|
||||
const username = String(entry.telegram_username);
|
||||
return username.startsWith("@") ? username : `@${username}`;
|
||||
}
|
||||
if (entry.email) return entry.email;
|
||||
}
|
||||
return id || "—";
|
||||
}
|
||||
|
||||
function userId(entry, kind) {
|
||||
return kind === "target" ? entry.target_user_id : entry.user_id;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
logsStore.loadLogs();
|
||||
});
|
||||
@@ -65,7 +86,7 @@
|
||||
<AdminTableSkeleton
|
||||
headers={logHeaders}
|
||||
rows={10}
|
||||
widths={["120px", "120px", "58px", "58px", "220px"]}
|
||||
widths={["120px", "120px", "160px", "160px", "220px"]}
|
||||
/>
|
||||
{:else if !logs.length}
|
||||
<AdminEmptyState tone="card"
|
||||
@@ -89,12 +110,50 @@
|
||||
<td class="admin-cell-mono" data-label={at("event", {}, "Событие")}
|
||||
>{entry.event_type}</td
|
||||
>
|
||||
<td class="admin-cell-mono" data-label={at("user_short", {}, "User")}
|
||||
>{entry.user_id || "—"}</td
|
||||
>
|
||||
<td class="admin-cell-mono" data-label={at("target_short", {}, "Target")}
|
||||
>{entry.target_user_id || "—"}</td
|
||||
>
|
||||
<td class="admin-logs-user-cell" data-label={at("user_short", {}, "User")}>
|
||||
{#if userId(entry, "user")}
|
||||
<span class="admin-logs-user">
|
||||
<AdminButton
|
||||
class="admin-logs-user-btn"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
title={at("payments_open_user", {}, "Open user card")}
|
||||
aria-label={at("payments_open_user", {}, "Open user card")}
|
||||
onclick={() => onOpenUserCard(userId(entry, "user"))}
|
||||
>
|
||||
<User size={14} />
|
||||
</AdminButton>
|
||||
<span class="admin-logs-user-meta">
|
||||
<span class="admin-logs-user-name">{userDisplay(entry, "user")}</span>
|
||||
<span class="admin-logs-user-id">ID {userId(entry, "user")}</span>
|
||||
</span>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="admin-muted">—</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="admin-logs-user-cell" data-label={at("target_short", {}, "Target")}>
|
||||
{#if userId(entry, "target")}
|
||||
<span class="admin-logs-user">
|
||||
<AdminButton
|
||||
class="admin-logs-user-btn"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
title={at("payments_open_user", {}, "Open user card")}
|
||||
aria-label={at("payments_open_user", {}, "Open user card")}
|
||||
onclick={() => onOpenUserCard(userId(entry, "target"))}
|
||||
>
|
||||
<User size={14} />
|
||||
</AdminButton>
|
||||
<span class="admin-logs-user-meta">
|
||||
<span class="admin-logs-user-name">{userDisplay(entry, "target")}</span>
|
||||
<span class="admin-logs-user-id">ID {userId(entry, "target")}</span>
|
||||
</span>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="admin-muted">—</span>
|
||||
{/if}
|
||||
</td>
|
||||
<td class="admin-cell-wrap" data-label={at("content", {}, "Контент")}
|
||||
>{entry.content || ""}</td
|
||||
>
|
||||
@@ -118,3 +177,53 @@
|
||||
logsStore.setPage(logsPage + 1);
|
||||
}}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.admin-logs-user-cell {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.admin-logs-user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-logs-user-meta {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.admin-logs-user-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: var(--admin-text);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-logs-user-id {
|
||||
color: var(--admin-dim);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.admin-logs-user-cell :global(.admin-logs-user-btn.admin-btn) {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
min-width: 30px;
|
||||
min-height: 30px;
|
||||
flex-shrink: 0;
|
||||
padding: 0;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.admin-logs-user-cell :global(.admin-logs-user-btn svg) {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -81,11 +81,7 @@
|
||||
$: openedUserTelegramProfileLinkKind = openedUser ? userTelegramProfileLinkKind(openedUser) : "";
|
||||
$: openedUserTelegramProfileHint =
|
||||
openedUserTelegramProfileLinkKind === "id"
|
||||
? at(
|
||||
"user_open_tg_profile_id_hint",
|
||||
{},
|
||||
"Профиль будет открыт по Telegram ID. Telegram может заблокировать переход из-за настроек приватности пользователя или ограничений клиента."
|
||||
)
|
||||
? at("user_open_tg_profile_id_hint", {}, "Бот отправит кнопку профиля в Telegram")
|
||||
: at("user_open_tg_profile_hint", {}, "Открыть профиль Telegram");
|
||||
|
||||
$: if (openedUser && userDetailTab === "logs" && !userLogsLoading && !userLogsLoaded) {
|
||||
@@ -189,11 +185,6 @@
|
||||
{at("user_open_tg_profile", {}, "Открыть Telegram")}
|
||||
</AdminButton>
|
||||
</div>
|
||||
{#if openedUserTelegramProfileLinkKind === "id"}
|
||||
<small class="admin-user-telegram-profile-note"
|
||||
>{openedUserTelegramProfileHint}</small
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1168,12 +1159,6 @@
|
||||
gap: 8px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.admin-user-telegram-profile-note {
|
||||
display: block;
|
||||
margin-top: 2px;
|
||||
color: var(--admin-dim);
|
||||
line-height: 1.35;
|
||||
}
|
||||
:global(.admin-avatar-dialog) {
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(0, 1fr);
|
||||
|
||||
Reference in New Issue
Block a user