From 27f5f2610c64826f6ac15f925e928cff6cca9ce1 Mon Sep 17 00:00:00 2001 From: dogukanoksuz Date: Tue, 28 Jan 2025 09:06:13 +0000 Subject: [PATCH] feat: Add last login timestamp in user settings --- public/locales/de/settings.json | 2 + public/locales/en/settings.json | 2 + public/locales/tr/settings.json | 2 + src/components/ui/data-table/data-table.tsx | 13 ++++ src/lib/utils.ts | 25 +++++++ src/pages/settings/users/index.tsx | 77 +++++++++++++-------- 6 files changed, 91 insertions(+), 30 deletions(-) diff --git a/public/locales/de/settings.json b/public/locales/de/settings.json index 6012e81..41a23bf 100644 --- a/public/locales/de/settings.json +++ b/public/locales/de/settings.json @@ -220,6 +220,8 @@ "otp": "Zwei-Faktor-Authentifizierung", "otp_enabled": "Aktiviert", "otp_disabled": "Deaktiviert", + "last_login_at": "Letzter Login", + "never": "Nie", "toasts": { "edit_success_msg": "Der Benutzer wurde erfolgreich bearbeitet.", "edit_error_msg": "Beim Bearbeiten des Benutzers ist ein Fehler aufgetreten.", diff --git a/public/locales/en/settings.json b/public/locales/en/settings.json index 6ed8400..7f9320e 100644 --- a/public/locales/en/settings.json +++ b/public/locales/en/settings.json @@ -221,6 +221,8 @@ "otp": "Two-Factor Authentication", "otp_enabled": "Enabled", "otp_disabled": "Disabled", + "last_login_at": "Last Login", + "never": "Never", "toasts": { "success_msg": "The user has been created successfully.", "edit_success_msg": "The user has been edited successfully.", diff --git a/public/locales/tr/settings.json b/public/locales/tr/settings.json index 835b04d..2194c9e 100644 --- a/public/locales/tr/settings.json +++ b/public/locales/tr/settings.json @@ -221,6 +221,8 @@ "otp": "İki Aşamalı Giriş", "otp_enabled": "Aktif", "otp_disabled": "Pasif", + "last_login_at": "Son Giriş", + "never": "Hiç", "toasts": { "success_msg": "Kullanıcı başarıyla oluşturuldu.", "edit_success_msg": "Kullanıcı başarıyla düzenlendi.", diff --git a/src/components/ui/data-table/data-table.tsx b/src/components/ui/data-table/data-table.tsx index c6b4eb1..eb0ed9b 100644 --- a/src/components/ui/data-table/data-table.tsx +++ b/src/components/ui/data-table/data-table.tsx @@ -97,6 +97,19 @@ const DataTable = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [rowSelection]) + React.useEffect(() => { + columns.forEach((column: any) => { + // Check if meta.hidden exists, if it's set column will be hidden + const meta = column.meta + if (meta && meta.hidden) { + setColumnVisibility((prev) => ({ + ...prev, + [column.accessorKey]: false, + })) + } + }) + }, []) + return (
diff --git a/src/lib/utils.ts b/src/lib/utils.ts index daba7de..3de73b8 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -55,3 +55,28 @@ export function setFormErrors(e: Error, form: UseFormReturn) { return false } + +export function getRelativeTimeString( + date: Date | number | string, + lang = "tr" +): string { + const now = new Date().getTime() + const time = new Date(date).getTime() + const diff = now - time + + const seconds = Math.floor(diff / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + const days = Math.floor(hours / 24) + const months = Math.floor(days / 30) + const years = Math.floor(days / 365) + + const rtf = new Intl.RelativeTimeFormat(lang, { numeric: "auto" }) + + if (seconds < 60) return rtf.format(-seconds, "second") + if (minutes < 60) return rtf.format(-minutes, "minute") + if (hours < 24) return rtf.format(-hours, "hour") + if (days < 30) return rtf.format(-days, "day") + if (months < 12) return rtf.format(-months, "month") + return rtf.format(-years, "year") +} diff --git a/src/pages/settings/users/index.tsx b/src/pages/settings/users/index.tsx index 34674d8..a8451e6 100644 --- a/src/pages/settings/users/index.tsx +++ b/src/pages/settings/users/index.tsx @@ -1,12 +1,13 @@ +import { useEffect, useState } from "react" +import { useRouter } from "next/router" import { http } from "@/services" import { Check, Footprints, User2, UserCog2, X } from "lucide-react" -import { useRouter } from "next/router" -import { useEffect, useState } from "react" import { useTranslation } from "react-i18next" -import CreateUser from "@/components/settings/create-user" -import EditUser from "@/components/settings/edit-user" -import { UserRowActions } from "@/components/settings/user-actions" +import { DivergentColumn } from "@/types/table" +import { IAuthLog, IUser } from "@/types/user" +import { getRelativeTimeString } from "@/lib/utils" +import { useEmitter } from "@/hooks/useEmitter" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import DataTable from "@/components/ui/data-table/data-table" @@ -19,9 +20,9 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip" -import { useEmitter } from "@/hooks/useEmitter" -import { DivergentColumn } from "@/types/table" -import { IAuthLog, IUser } from "@/types/user" +import CreateUser from "@/components/settings/create-user" +import EditUser from "@/components/settings/edit-user" +import { UserRowActions } from "@/components/settings/user-actions" const getType = (type: string) => { switch (type) { @@ -41,7 +42,7 @@ export default function UserSettingsPage() { const [data, setData] = useState([]) const router = useRouter() const emitter = useEmitter() - const { t } = useTranslation("settings") + const { t, i18n } = useTranslation("settings") const columns: DivergentColumn[] = [ { @@ -107,6 +108,23 @@ export default function UserSettingsPage() { ), }, + { + accessorKey: "last_login_at", + header: ({ column }) => ( + + ), + title: t("users.last_login_at"), + cell: ({ row }) => ( + <> + {row.original.last_login_at + ? getRelativeTimeString(row.original.last_login_at, i18n.language) + : t("users.never")} + + ), + }, { accessorKey: "auth_type", header: ({ column }) => ( @@ -175,6 +193,9 @@ export default function UserSettingsPage() { )} ), + meta: { + hidden: true, + }, }, { id: "actions", @@ -187,12 +208,10 @@ export default function UserSettingsPage() { ] const fetchData = () => { - http - .get(`/settings/users`) - .then((res) => { - setData(res.data) - setLoading(false) - }) + http.get(`/settings/users`).then((res) => { + setData(res.data) + setLoading(false) + }) } useEffect(() => { @@ -301,15 +320,15 @@ function AuthLogDialog() { <> {row.original.created_at ? new Date(row.original.created_at).toLocaleDateString( - i18n.language, - { - day: "2-digit", - month: "long", - year: "numeric", - hour: "2-digit", - minute: "2-digit", - } - ) + i18n.language, + { + day: "2-digit", + month: "long", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + } + ) : t("users.auth_log.unknown")} ), @@ -320,12 +339,10 @@ function AuthLogDialog() { setLoading(true) setOpen(true) - http - .get(`/settings/users/auth_logs/${user_id}`) - .then((res) => { - setData(res.data) - setLoading(false) - }) + http.get(`/settings/users/auth_logs/${user_id}`).then((res) => { + setData(res.data) + setLoading(false) + }) }) return (