mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 14:33:17 +00:00
320 lines
7.4 KiB
Vue
320 lines
7.4 KiB
Vue
<template>
|
|
<div
|
|
v-if="messages.length"
|
|
ref="rootEl"
|
|
class="desk-status-messages"
|
|
:class="{ 'is-open': open }"
|
|
>
|
|
<div class="desk-status-messages-panel" role="dialog" :aria-hidden="!open">
|
|
<div class="desk-status-messages-inner">
|
|
<div class="desk-status-messages-head">
|
|
<strong>Avisos de {{ screenLabel }}</strong>
|
|
<span class="muted">{{ messages.length }}</span>
|
|
</div>
|
|
<ul class="desk-status-messages-list">
|
|
<li v-for="item in messages" :key="item.key">
|
|
<button type="button" class="desk-status-message" :class="`is-${item.severity}`" @click="activate(item)">
|
|
<i :class="iconFor(item.severity)" />
|
|
<span>
|
|
<strong>{{ item.title }}</strong>
|
|
<small v-if="item.detail">{{ item.detail }}</small>
|
|
</span>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
class="statusbar-item desk-status-messages-trigger"
|
|
:aria-expanded="open"
|
|
:title="summaryTitle"
|
|
@click="toggle"
|
|
>
|
|
<i :class="summaryIcon" />
|
|
<span>{{ summaryLabel }}</span>
|
|
<i class="pi pi-angle-up desk-status-messages-caret" />
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { DeskStatusMessage, PadronImssFilter } from "~/composables/useDesktop";
|
|
|
|
const { statusMessages, tabLabel, padronImssFilter, treeKey } = useDesktop();
|
|
|
|
const rootEl = ref<HTMLElement | null>(null);
|
|
const open = ref(false);
|
|
const pinned = ref(false);
|
|
let autoTimer: ReturnType<typeof setTimeout> | null = null;
|
|
|
|
const messages = computed(() => statusMessages.value);
|
|
const screenLabel = computed(() => tabLabel.value || "esta pantalla");
|
|
|
|
const severityRank: Record<DeskStatusMessage["severity"], number> = {
|
|
danger: 3,
|
|
warn: 2,
|
|
info: 1,
|
|
};
|
|
|
|
const topSeverity = computed(() => {
|
|
let best: DeskStatusMessage["severity"] = "info";
|
|
for (const item of messages.value) {
|
|
if (severityRank[item.severity] > severityRank[best]) best = item.severity;
|
|
}
|
|
return best;
|
|
});
|
|
|
|
const summaryIcon = computed(() => iconFor(topSeverity.value));
|
|
const summaryLabel = computed(() => {
|
|
if (messages.value.length === 1) return messages.value[0].title;
|
|
return `${messages.value.length} avisos`;
|
|
});
|
|
const summaryTitle = computed(() => messages.value.map((item) => item.title).join(" · "));
|
|
|
|
const signature = computed(() =>
|
|
messages.value.map((item) => `${item.key}:${item.title}:${item.severity}`).join("|"),
|
|
);
|
|
|
|
function iconFor(severity: DeskStatusMessage["severity"]) {
|
|
if (severity === "warn") return "pi pi-exclamation-triangle";
|
|
if (severity === "info") return "pi pi-info-circle";
|
|
return "pi pi-exclamation-circle";
|
|
}
|
|
|
|
function clearAuto() {
|
|
if (autoTimer) {
|
|
clearTimeout(autoTimer);
|
|
autoTimer = null;
|
|
}
|
|
}
|
|
|
|
function collapse() {
|
|
open.value = false;
|
|
pinned.value = false;
|
|
clearAuto();
|
|
}
|
|
|
|
function expandTemporary() {
|
|
pinned.value = false;
|
|
open.value = true;
|
|
clearAuto();
|
|
autoTimer = setTimeout(() => {
|
|
if (!pinned.value) open.value = false;
|
|
autoTimer = null;
|
|
}, 4500);
|
|
}
|
|
|
|
function toggle() {
|
|
if (open.value && pinned.value) {
|
|
collapse();
|
|
return;
|
|
}
|
|
if (open.value && !pinned.value) {
|
|
pinned.value = true;
|
|
clearAuto();
|
|
return;
|
|
}
|
|
open.value = true;
|
|
pinned.value = true;
|
|
clearAuto();
|
|
}
|
|
|
|
async function activate(item: DeskStatusMessage) {
|
|
if (item.treeKey) {
|
|
treeKey.value = item.treeKey;
|
|
} else if (item.filter && item.route === "/padron") {
|
|
treeKey.value = "all";
|
|
}
|
|
if (item.filter) {
|
|
padronImssFilter.value = item.filter as PadronImssFilter;
|
|
}
|
|
if (item.route && useRoute().path !== item.route) {
|
|
await navigateTo(item.route);
|
|
}
|
|
collapse();
|
|
}
|
|
|
|
function onDocPointer(event: PointerEvent) {
|
|
if (!open.value || !rootEl.value) return;
|
|
if (rootEl.value.contains(event.target as Node)) return;
|
|
collapse();
|
|
}
|
|
|
|
watch(signature, (value, previous) => {
|
|
if (!value) {
|
|
collapse();
|
|
return;
|
|
}
|
|
if (value !== previous) expandTemporary();
|
|
}, { immediate: true });
|
|
|
|
onMounted(() => {
|
|
document.addEventListener("pointerdown", onDocPointer);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
document.removeEventListener("pointerdown", onDocPointer);
|
|
clearAuto();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.desk-status-messages {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-end;
|
|
overflow: visible;
|
|
}
|
|
.desk-status-messages-trigger {
|
|
border: 0;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
max-width: min(360px, 42vw);
|
|
padding-inline: 10px;
|
|
}
|
|
.desk-status-messages-trigger:hover {
|
|
color: #344156;
|
|
}
|
|
.desk-status-messages-caret {
|
|
font-size: 11px !important;
|
|
color: #7a8798 !important;
|
|
transition: transform .35s ease;
|
|
}
|
|
.desk-status-messages.is-open .desk-status-messages-caret {
|
|
transform: rotate(180deg);
|
|
}
|
|
.desk-status-messages-panel {
|
|
position: absolute;
|
|
right: 0;
|
|
bottom: calc(100% + 8px);
|
|
width: min(360px, 72vw);
|
|
display: grid;
|
|
grid-template-rows: 0fr;
|
|
opacity: 0;
|
|
transform: translateY(12px);
|
|
pointer-events: none;
|
|
transition:
|
|
grid-template-rows .45s cubic-bezier(.22, 1, .36, 1),
|
|
opacity .28s ease,
|
|
transform .45s cubic-bezier(.22, 1, .36, 1);
|
|
z-index: 40;
|
|
}
|
|
.desk-status-messages.is-open .desk-status-messages-panel {
|
|
grid-template-rows: 1fr;
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
.desk-status-messages-inner {
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
border: 1px solid #e1e6ed;
|
|
border-radius: 12px;
|
|
background: #fff;
|
|
box-shadow: 0 12px 28px rgba(23, 32, 51, .14);
|
|
}
|
|
.desk-status-messages-head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
padding: 10px 12px 8px;
|
|
border-bottom: 1px solid #edf0f4;
|
|
color: #344156;
|
|
font-size: 12px;
|
|
}
|
|
.desk-status-messages-list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 6px;
|
|
max-height: 240px;
|
|
overflow: auto;
|
|
}
|
|
.desk-status-message {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
padding: 10px;
|
|
border: 0;
|
|
border-radius: 8px;
|
|
background: transparent;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
color: #405069;
|
|
}
|
|
.desk-status-message:hover {
|
|
background: #f5f7fa;
|
|
}
|
|
.desk-status-message > i {
|
|
margin-top: 2px;
|
|
font-size: 14px;
|
|
}
|
|
.desk-status-message span {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
.desk-status-message strong {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: #2d3a4d;
|
|
}
|
|
.desk-status-message small {
|
|
font-size: 11px;
|
|
line-height: 1.35;
|
|
color: #6b7785;
|
|
}
|
|
.desk-status-message.is-danger > i { color: #c2414b; }
|
|
.desk-status-message.is-warn > i { color: #b7791f; }
|
|
.desk-status-message.is-info > i { color: #2f6fe4; }
|
|
.muted {
|
|
color: #6b7785;
|
|
font-size: 11px;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-messages-trigger:hover {
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-messages-caret {
|
|
color: #94a3b8 !important;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-messages-inner {
|
|
border-color: #243044;
|
|
background: #111b2e;
|
|
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
|
|
}
|
|
|
|
:global(html.dark) .desk-status-messages-head {
|
|
border-bottom-color: #243044;
|
|
color: #e2e8f0;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-message {
|
|
color: #cbd5e1;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-message:hover {
|
|
background: #1a2744;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-message strong {
|
|
color: #f1f5f9;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-message small,
|
|
:global(html.dark) .muted {
|
|
color: #94a3b8;
|
|
}
|
|
|
|
:global(html.dark) .desk-status-message.is-info > i {
|
|
color: #60a5fa;
|
|
}
|
|
</style>
|