ux(audit): typography pass

- Action chip now uses monoweight glyphs (+, ×, ↻, ⚙, ∑, ⚿) instead
  of mixed emoji, so all rows share a baseline; chip shrunk to 32px
  with font-semibold and leading-none.
- Removed the actor avatar circle (visual noise); actor name carries
  the primary weight on its own (semibold, gray-900).
- Sentence layout reworked as a single <p> with inline margins
  instead of a flex row: "rop удалил Лида ceb8…". Actor — semibold
  900, verb — 700, entity word — 500, ID — font-mono 11px sky-600.
- Entity labels capitalised ("Лида" / "Пользователя" / "Систему")
  so the sentence reads as natural Russian, not technical jargon.
- Day header demoted from sticky+gradient block to a plain h2 with
  tracking-[0.18em] — keeps rhythm without the muted-bar look.
- Timestamp uses <time> + text-[11px] leading-none; less prominent
  than the message itself.
- Dropped the unused initials() helper after removing the avatar.
This commit is contained in:
Telecom 2026-05-22 13:52:43 +05:00
parent a92298daf2
commit b79e99246e

View File

@ -17,9 +17,9 @@ const entries = ref<AuditEntry[]>([]);
const loading = ref(false); const loading = ref(false);
const ENTITY_LABELS: Record<string, string> = { const ENTITY_LABELS: Record<string, string> = {
lead: 'лида', lead: 'Лида',
user: 'пользователя', user: 'Пользователя',
system: 'систему', system: 'Систему',
}; };
interface ActionMeta { interface ActionMeta {
@ -29,12 +29,12 @@ interface ActionMeta {
} }
const ACTIONS: Record<string, ActionMeta> = { const ACTIONS: Record<string, ActionMeta> = {
create: { label: 'создал', tone: 'success', icon: '+' }, create: { label: 'создал', tone: 'success', icon: '+' },
delete: { label: 'удалил', tone: 'danger', icon: '🗑' }, delete: { label: 'удалил', tone: 'danger', icon: '×' },
reassign: { label: 'переназначил', tone: 'warning', icon: '↻' }, reassign: { label: 'переназначил', tone: 'warning', icon: '↻' },
reset_password: { label: 'сменил пароль', tone: 'warning', icon: '🔑' }, reset_password: { label: 'сменил пароль', tone: 'warning', icon: '⚿' },
change_role: { label: 'изменил роль', tone: 'warning', icon: '👤' }, change_role: { label: 'изменил роль', tone: 'warning', icon: '⚙' },
recalc: { label: 'пересчитал score', tone: 'neutral', icon: '⟳' }, recalc: { label: 'пересчитал score', tone: 'neutral', icon: '∑' },
}; };
function actionMeta(a: string): ActionMeta { function actionMeta(a: string): ActionMeta {
@ -132,11 +132,6 @@ const grouped = computed(() => {
return groups; return groups;
}); });
function initials(name: string): string {
const parts = name.split(/[\s._-]+/).filter(Boolean).slice(0, 2);
return parts.map((p) => p[0]?.toUpperCase() ?? '').join('') || name[0]?.toUpperCase() || '?';
}
function shortId(id: string): string { function shortId(id: string): string {
return id.length > 10 ? id.slice(0, 8) + '…' : id; return id.length > 10 ? id.slice(0, 8) + '…' : id;
} }
@ -194,26 +189,26 @@ onMounted(load);
> >
Событий не найдено. Событий не найдено.
</div> </div>
<div v-else class="space-y-8"> <div v-else class="space-y-6">
<div <section
v-for="group in grouped" v-for="group in grouped"
:key="group.day" :key="group.day"
class="space-y-3" class="space-y-2"
>
<h2
class="text-[10px] font-semibold uppercase tracking-[0.18em] text-gray-400 px-1"
> >
<div class="sticky top-16 z-10 bg-gradient-to-b from-white via-white to-white/0 pb-1">
<div class="text-[11px] font-semibold uppercase tracking-widest text-gray-400">
{{ group.day }} {{ group.day }}
</div> </h2>
</div>
<div class="card divide-y divide-gray-100"> <div class="card divide-y divide-gray-100">
<div <div
v-for="e in group.items" v-for="e in group.items"
:key="e.id" :key="e.id"
class="flex items-center gap-4 px-4 py-3 hover:bg-gray-50/70 transition" class="flex items-center gap-3 px-4 py-3 hover:bg-gray-50/70 transition"
> >
<div <div
class="w-9 h-9 rounded-full flex items-center justify-center text-base ring-1 shrink-0" class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold ring-1 shrink-0 leading-none"
:class="[ :class="[
TONE_CLASSES[actionMeta(e.action).tone].bg, TONE_CLASSES[actionMeta(e.action).tone].bg,
TONE_CLASSES[actionMeta(e.action).tone].ring, TONE_CLASSES[actionMeta(e.action).tone].ring,
@ -224,39 +219,37 @@ onMounted(load);
{{ actionMeta(e.action).icon }} {{ actionMeta(e.action).icon }}
</div> </div>
<div class="flex items-center gap-2 shrink-0"> <p
<div class="flex-1 min-w-0 text-sm text-gray-700 leading-snug truncate"
class="w-7 h-7 rounded-full bg-gray-100 text-gray-700 flex items-center justify-center text-[11px] font-semibold"
:title="e.actor"
> >
{{ initials(e.actor) }} <span class="font-semibold text-gray-900">{{ e.actor }}</span>
</div> <span class="mx-1.5">{{ actionMeta(e.action).label }}</span>
<span class="text-sm font-medium text-gray-900">{{ e.actor }}</span>
</div>
<div class="flex-1 min-w-0 text-sm text-gray-700 flex items-center gap-1.5 truncate">
<span>{{ actionMeta(e.action).label }}</span>
<span class="text-gray-500">{{ ENTITY_LABELS[e.entity] ?? e.entity }}</span> <span class="text-gray-500">{{ ENTITY_LABELS[e.entity] ?? e.entity }}</span>
<button <button
v-if="e.entity === 'lead'" v-if="e.entity === 'lead'"
type="button" type="button"
class="font-mono text-sky-600 hover:underline truncate" class="ml-1.5 font-mono text-xs text-sky-600 hover:underline align-middle"
@click="gotoEntity(e)" @click="gotoEntity(e)"
> >
{{ shortId(e.entity_id) }} {{ shortId(e.entity_id) }}
</button> </button>
<span v-else class="font-mono text-gray-600 truncate">{{ e.entity_id }}</span> <span
</div> v-else
class="ml-1.5 font-mono text-xs text-gray-500 align-middle"
>
{{ e.entity_id }}
</span>
</p>
<div <time
class="text-xs text-gray-400 tabular-nums shrink-0" class="text-[11px] text-gray-400 tabular-nums shrink-0 leading-none"
:title="fmtFullTime(e.created_at)" :title="fmtFullTime(e.created_at)"
> >
{{ relativeTime(e.created_at) }} {{ relativeTime(e.created_at) }}
</time>
</div> </div>
</div> </div>
</div> </section>
</div>
</div> </div>
</div> </div>
</template> </template>