Procházet zdrojové kódy

feat(clients): live online dot + last-online tooltip on offline

Two small UX cues on the clients table online column:

- a pulsing green dot next to the Online tag so an active client reads as
  live at a glance (honors prefers-reduced-motion).
- hovering the Offline tag shows the client's last-online timestamp from
  record.traffic.lastOnline, formatted with the panel's calendar setting
  (or "-" when the client has never connected).
MHSanaei před 11 hodinami
rodič
revize
c8df1b19ff

+ 20 - 0
frontend/src/pages/clients/ClientsPage.css

@@ -62,6 +62,26 @@
 .dot-orange { background: var(--ant-color-warning); }
 .dot-gray { background: var(--ant-color-text-quaternary); }
 
+.online-dot {
+  display: inline-block;
+  width: 7px;
+  height: 7px;
+  border-radius: 50%;
+  margin-inline-end: 5px;
+  vertical-align: middle;
+  background: var(--ant-color-success);
+  animation: online-blink 1.1s ease-in-out infinite;
+}
+
+@keyframes online-blink {
+  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(82, 196, 26, 0.55); }
+  50% { opacity: 0.35; box-shadow: 0 0 0 4px rgba(82, 196, 26, 0); }
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .online-dot { animation: none; }
+}
+
 .status-tag {
   margin: 0 0 0 4px;
   font-size: 11px;

+ 10 - 3
frontend/src/pages/clients/ClientsPage.tsx

@@ -626,10 +626,17 @@ export default function ClientsPage() {
       render: (_v, record) => {
         const bucket = clientBucket(record);
         if (bucket === 'depleted') return <Tag color="red">{t('depleted')}</Tag>;
-        if (record.enable && isOnline(record.email)) return <Tag color="green">{t('pages.clients.online')}</Tag>;
+        if (record.enable && isOnline(record.email)) return (
+          <Tag color="green"><span className="online-dot" />{t('pages.clients.online')}</Tag>
+        );
         if (!record.enable) return <Tag>{t('disabled')}</Tag>;
         if (bucket === 'expiring') return <Tag color="orange">{t('depletingSoon')}</Tag>;
-        return <Tag>{t('pages.clients.offline')}</Tag>;
+        const lastOnline = record.traffic?.lastOnline ?? 0;
+        return (
+          <Tooltip title={`${t('lastOnline')}: ${lastOnline > 0 ? IntlUtil.formatDate(lastOnline, datepicker) : '-'}`}>
+            <Tag>{t('pages.clients.offline')}</Tag>
+          </Tooltip>
+        );
       },
     },
     {
@@ -732,7 +739,7 @@ export default function ClientsPage() {
       ),
     },
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  ], [t, togglingEmail, clientBucket, isOnline, inboundsById, filters, allGroups]);
+  ], [t, togglingEmail, clientBucket, isOnline, inboundsById, filters, allGroups, datepicker]);
 
   const tablePagination = {
     current: currentPage,