From c919a87826d22893acda0dcc91a11f909c9ee7fa Mon Sep 17 00:00:00 2001 From: JWittmeyer Date: Fri, 23 Jan 2026 14:04:06 +0100 Subject: [PATCH 1/7] Hotfix pagination --- components/pagination/Pagination.tsx | 104 +++++++++++++++++++++------ types/pagination.ts | 1 + 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/components/pagination/Pagination.tsx b/components/pagination/Pagination.tsx index 3b9af42..b57b2ae 100644 --- a/components/pagination/Pagination.tsx +++ b/components/pagination/Pagination.tsx @@ -1,49 +1,113 @@ import { PaginationProps } from '../../types/pagination' import { MemoIconArrowLeft, MemoIconArrowRight } from '../kern-icons/icons'; -import { useEffect, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +const PAGE_BTN_BASE = 'inline-flex items-center border-t-2 px-4 pt-4 text-sm font-medium'; +const PAGE_BTN_ACTIVE = `${PAGE_BTN_BASE} border-indigo-500 text-indigo-600`; +const PAGE_BTN_INACTIVE = `${PAGE_BTN_BASE} border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700`; +const ELLIPSIS_CLASS = `${PAGE_BTN_BASE} border-transparent text-gray-500`; +const NAV_BTN_BASE = 'inline-flex items-center border-t-2 border-transparent pt-4 text-sm font-medium text-gray-500 hover:border-gray-300 hover:text-gray-700 disabled:opacity-50 disabled:cursor-not-allowed'; +const NAV_BTN_PREV = `${NAV_BTN_BASE} pr-1`; +const NAV_BTN_NEXT = `${NAV_BTN_BASE} pl-1`; export default function Pagination(props: PaginationProps) { + const [currentPage, setCurrentPage] = useState(1); - const [currentPage, setCurrentPage] = useState(0); - const [totalPages, setTotalPages] = useState(0); + const totalPages = useMemo(() => + Math.ceil(props.fullCount / props.limit), + [props.fullCount, props.limit] + ); - useMemo(() => { + // Sync currentPage when props change + useEffect(() => { setCurrentPage(props.offset / props.limit + 1); }, [props.offset, props.limit]); - useMemo(() => { - setTotalPages(Math.ceil(props.fullCount / props.limit)); - }, [props.fullCount, props.limit]); - + // Sync offset back to parent when currentPage changes useEffect(() => { props.setOffset((currentPage - 1) * props.limit); - }, [currentPage, props.limit]); + }, [currentPage, props.limit, props.setOffset]); + + const handlePrevious = useCallback(() => { + setCurrentPage(prev => prev - 1); + }, []); + + const handleNext = useCallback(() => { + setCurrentPage(prev => prev + 1); + }, []); + + const pageButtons = useMemo(() => { + const ellipsis = (key: string) => ( + ... + ); + + const pageButton = (page: number) => ( + + ); + + // Show all pages when reducePageNumbers is off or total is small + if (!props.reducePageNumbers || totalPages <= 10) { + return Array.from({ length: totalPages }, (_, i) => pageButton(i + 1)); + } + + // Middle position: 1 ... (current-1, current, current+1) ... last + if (currentPage > 3 && currentPage < totalPages - 2) { + return [ + pageButton(1), + ellipsis('start'), + pageButton(currentPage - 1), + pageButton(currentPage), + pageButton(currentPage + 1), + ellipsis('end'), + pageButton(totalPages) + ]; + } + + // Near start or end: first 4 ... last 4 + return [ + pageButton(1), + pageButton(2), + pageButton(3), + pageButton(4), + ellipsis('middle'), + pageButton(totalPages - 3), + pageButton(totalPages - 2), + pageButton(totalPages - 1), + pageButton(totalPages) + ]; + }, [totalPages, currentPage, props.reducePageNumbers]); return ( - ) + ); } diff --git a/types/pagination.ts b/types/pagination.ts index 3f4689c..21247b5 100644 --- a/types/pagination.ts +++ b/types/pagination.ts @@ -5,4 +5,5 @@ export type PaginationProps = { limit: number; previousLabel?: string; nextLabel?: string; + reducePageNumbers?: boolean; } \ No newline at end of file From 05d8d00186902c48d1a2bfcf6861abd5ee6ab7ed Mon Sep 17 00:00:00 2001 From: JWittmeyer Date: Fri, 23 Jan 2026 14:19:12 +0100 Subject: [PATCH 2/7] Defaults to 3 --- components/inbox-mail/InboxMailView.tsx | 2 +- components/pagination/Pagination.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/components/inbox-mail/InboxMailView.tsx b/components/inbox-mail/InboxMailView.tsx index ec5e534..2949b8e 100644 --- a/components/inbox-mail/InboxMailView.tsx +++ b/components/inbox-mail/InboxMailView.tsx @@ -219,7 +219,7 @@ export default function InboxMailView(props: InboxMailViewProps) { /> ))} - +
{selectedThread && threadMails && threadMails.length > 0 ? ( diff --git a/components/pagination/Pagination.tsx b/components/pagination/Pagination.tsx index b57b2ae..c71ac79 100644 --- a/components/pagination/Pagination.tsx +++ b/components/pagination/Pagination.tsx @@ -69,14 +69,12 @@ export default function Pagination(props: PaginationProps) { ]; } - // Near start or end: first 4 ... last 4 + // Near start or end: first 3 ... last 3 return [ pageButton(1), pageButton(2), pageButton(3), - pageButton(4), ellipsis('middle'), - pageButton(totalPages - 3), pageButton(totalPages - 2), pageButton(totalPages - 1), pageButton(totalPages) From 66bbca3308e03ff82e38b162b688c83f88e47416 Mon Sep 17 00:00:00 2001 From: JWittmeyer Date: Mon, 26 Jan 2026 10:31:26 +0100 Subject: [PATCH 3/7] Adds navigation to home --- components/inbox-mail/InboxMailThreadOverview.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/inbox-mail/InboxMailThreadOverview.tsx b/components/inbox-mail/InboxMailThreadOverview.tsx index d47284e..8cfcb17 100644 --- a/components/inbox-mail/InboxMailThreadOverview.tsx +++ b/components/inbox-mail/InboxMailThreadOverview.tsx @@ -1,8 +1,9 @@ -import { useMemo } from "react"; +import { useCallback, useMemo } from "react"; import { InboxMailThread, InboxMailThreadSupportProgressState } from "./types-mail"; import { Tooltip } from "@nextui-org/react"; import { MemoIconAlertTriangle, MemoIconCircleCheck, MemoIconHelpCircle, MemoIconProgressCheck, MemoIconUser } from "../kern-icons/icons"; import { formatDisplayTimestamp } from "@/submodules/javascript-functions/date-parser"; +import { useRouter } from "next/router"; interface InboxMailThreadOverviewProps { thread: InboxMailThread; @@ -14,6 +15,7 @@ interface InboxMailThreadOverviewProps { export default function InboxMailThreadOverview(props: InboxMailThreadOverviewProps) { const t = useMemo(() => props.translator, [props.translator]); + const router = useRouter(); const { displayName, displayInitials, @@ -32,6 +34,8 @@ export default function InboxMailThreadOverview(props: InboxMailThreadOverviewPr } }, [props.thread, props.isAdmin]); + const clickHome = useCallback(() => router.push("/"), [router]); + return (
) : (
- Kern AI + Kern AI
)} From 1c7b2c7f38231de96cb62e800c05e9be2d275660 Mon Sep 17 00:00:00 2001 From: JWittmeyer Date: Mon, 26 Jan 2026 13:10:59 +0100 Subject: [PATCH 4/7] Adds org jump option and badge --- components/inbox-mail/InboxMailAdminPanel.tsx | 102 +++++++++++------- .../inbox-mail/InboxMailThreadOverview.tsx | 6 +- components/inbox-mail/InboxMailView.tsx | 2 +- components/inbox-mail/types-mail.tsx | 6 ++ 4 files changed, 72 insertions(+), 44 deletions(-) diff --git a/components/inbox-mail/InboxMailAdminPanel.tsx b/components/inbox-mail/InboxMailAdminPanel.tsx index 134670c..4ce6f60 100644 --- a/components/inbox-mail/InboxMailAdminPanel.tsx +++ b/components/inbox-mail/InboxMailAdminPanel.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from "react"; -import { InboxMailThread, InboxMailThreadSupportProgressState, User } from "./types-mail"; +import { InboxMailThread, InboxMailThreadSupportProgressState, JumpDestination, User } from "./types-mail"; import { IconExternalLink, IconProgressCheck } from "@tabler/icons-react"; import KernDropdown from "../KernDropdown"; import { addUserToOrganization, removeUserFromOrganization, updateInboxMailThreadsUnreadByContent, updateInboxMailThreadsUnreadByProject } from "./service-mail"; @@ -21,32 +21,35 @@ interface InboxMailAdminPanelProps { function InboxMailAdminPanel(props: InboxMailAdminPanelProps) { // No translations needed, admin only - const assignAndJump = useCallback((toConversation: boolean) => { + const assignAndJump = useCallback((destination: JumpDestination) => { if (!props.currentUser) return; const currentOrganizationId = props.currentUser?.organizationId; if (!currentOrganizationId) { addUserToOrganization(props.currentUser.mail, props.selectedThread.organizationName, (res) => { - jumptoConversationOrProject(toConversation); - + jumpTo(destination); }); } else if (currentOrganizationId === props.selectedThread.organizationId) { - jumptoConversationOrProject(toConversation); - + jumpTo(destination); } else { removeUserFromOrganization(props.currentUser.mail, (res) => { addUserToOrganization(props.currentUser.mail, props.selectedThread.organizationName, (res) => { - jumptoConversationOrProject(toConversation); + jumpTo(destination); }); }); } }, [props.currentUser, props.selectedThread]); - const jumptoConversationOrProject = useCallback((toConversation: boolean) => { - if (toConversation) { - window.open(`/cognition/projects/${props.selectedThread.metaData?.projectId}/ui/${props.selectedThread.metaData?.conversationId}`, '_blank'); - } - else { - window.open(`/cognition/projects/${props.selectedThread.metaData.projectId}/pipeline`, '_blank'); + const jumpTo = useCallback((destination: JumpDestination) => { + switch (destination) { + case JumpDestination.CONVERSATION: + window.open(`/cognition/projects/${props.selectedThread.metaData?.projectId}/ui/${props.selectedThread.metaData?.conversationId}`, '_blank'); + break; + case JumpDestination.PROJECT: + window.open(`/cognition/projects/${props.selectedThread.metaData?.projectId}/pipeline`, '_blank'); + break; + case JumpDestination.ORGANIZATION: + window.open('/cognition', '_blank'); + break; } }, [props.selectedThread.metaData]); @@ -103,6 +106,28 @@ function InboxMailAdminPanel(props: InboxMailAdminPanelProps) {
}
+ {props.selectedThread.organizationId && ( +
+
+ Organization +
+ +
+ ID: + {props.selectedThread.organizationId} +
+
+ {props.selectedThread.organizationName} +
+ +
+ )} + {props.selectedThread.metaData?.projectId && (
@@ -118,39 +143,34 @@ function InboxMailAdminPanel(props: InboxMailAdminPanelProps) {
- ) - } - - { - props.selectedThread.metaData?.conversationId && ( -
-
- Conversation -
- -
- ID: - {props.selectedThread.metaData.conversationId} -
-
- {props.selectedThread.metaData.conversationHeader || "N/A"} -
- + )} + + {props.selectedThread.metaData?.conversationId && ( +
+
+ Conversation +
+ +
+ ID: + {props.selectedThread.metaData.conversationId}
- ) - } +
+ {props.selectedThread.metaData.conversationHeader || "N/A"} +
+ +
+ )}
); }; diff --git a/components/inbox-mail/InboxMailThreadOverview.tsx b/components/inbox-mail/InboxMailThreadOverview.tsx index 8cfcb17..b7a5f36 100644 --- a/components/inbox-mail/InboxMailThreadOverview.tsx +++ b/components/inbox-mail/InboxMailThreadOverview.tsx @@ -35,7 +35,6 @@ export default function InboxMailThreadOverview(props: InboxMailThreadOverviewPr }, [props.thread, props.isAdmin]); const clickHome = useCallback(() => router.push("/"), [router]); - return (
-
+
{displayName ?? `<${t("inboxMail.unknownUser")}>`}
+ {props.isAdmin ?
+ Org: {props.thread.organizationName} +
: null} {unreadMailCount > 0 && ( {unreadMailCount} {t("inboxMail.newBadge")} diff --git a/components/inbox-mail/InboxMailView.tsx b/components/inbox-mail/InboxMailView.tsx index 2949b8e..9f89a39 100644 --- a/components/inbox-mail/InboxMailView.tsx +++ b/components/inbox-mail/InboxMailView.tsx @@ -224,7 +224,7 @@ export default function InboxMailView(props: InboxMailViewProps) {
{selectedThread && threadMails && threadMails.length > 0 ? ( <> - {isAdmin && selectedThread.isAdminSupportThread && + {isAdmin && Date: Mon, 26 Jan 2026 16:49:45 +0100 Subject: [PATCH 5/7] Adds filter options --- components/inbox-mail/InboxMailAdminPanel.tsx | 76 +++++-- .../inbox-mail/InboxMailThreadOverview.tsx | 12 +- components/inbox-mail/InboxMailView.tsx | 206 +++++++++++++++--- .../inboxMailLocalTranslations.json | 10 +- components/inbox-mail/service-mail.tsx | 21 +- components/inbox-mail/types-mail.tsx | 16 +- components/kern-icons/icons.ts | 5 +- 7 files changed, 284 insertions(+), 62 deletions(-) diff --git a/components/inbox-mail/InboxMailAdminPanel.tsx b/components/inbox-mail/InboxMailAdminPanel.tsx index 4ce6f60..53f52f3 100644 --- a/components/inbox-mail/InboxMailAdminPanel.tsx +++ b/components/inbox-mail/InboxMailAdminPanel.tsx @@ -1,8 +1,9 @@ -import React, { useCallback } from "react"; +import React, { useCallback, useEffect, useState } from "react"; import { InboxMailThread, InboxMailThreadSupportProgressState, JumpDestination, User } from "./types-mail"; -import { IconExternalLink, IconProgressCheck } from "@tabler/icons-react"; +import { IconExternalLink, IconProgressCheck, IconAlertTriangle } from "@tabler/icons-react"; import KernDropdown from "../KernDropdown"; -import { addUserToOrganization, removeUserFromOrganization, updateInboxMailThreadsUnreadByContent, updateInboxMailThreadsUnreadByProject } from "./service-mail"; +import { addUserToOrganization, removeUserFromOrganization, updateInboxMailThreadsUnreadByContent, updateInboxMailThreadsUnreadByProject, updateInboxMailThreadUnreadLast, deleteInboxMailThreadsSimilar } from "./service-mail"; +import { Tooltip } from "@nextui-org/react"; import KernButton from "../kern-button/KernButton"; @@ -17,10 +18,21 @@ interface InboxMailAdminPanelProps { handleInboxMailProgressChange: (value: InboxMailThreadSupportProgressState) => void; currentUser: User; refetchInboxMailOverview: () => void; + translator: (key: string) => string; } function InboxMailAdminPanel(props: InboxMailAdminPanelProps) { - // No translations needed, admin only + const t = props.translator; + const [canMarkLastUnread, setCanMarkLastUnread] = useState(true); + + useEffect(() => { + if (props.selectedThread.metaData?.unreadMailCountAdmin > 0) { + setCanMarkLastUnread(false); + } else { + setCanMarkLastUnread(true); + } + }, [props.selectedThread.id, props.selectedThread.metaData?.unreadMailCountAdmin]); + const assignAndJump = useCallback((destination: JumpDestination) => { if (!props.currentUser) return; const currentOrganizationId = props.currentUser?.organizationId; @@ -64,7 +76,18 @@ function InboxMailAdminPanel(props: InboxMailAdminPanelProps) { updateInboxMailThreadsUnreadByProject(props.selectedThread.id, (res) => { props.refetchInboxMailOverview(); }); + }, [props.selectedThread?.id, props.refetchInboxMailOverview]); + + const handleMarkLastUnread = useCallback(() => { + setCanMarkLastUnread(false); + updateInboxMailThreadUnreadLast(props.selectedThread.id, (res) => { + props.refetchInboxMailOverview(); + }); + }, [props.selectedThread?.id, props.refetchInboxMailOverview]); + const handleDeleteSimilar = useCallback(() => { + if (!confirm("Are you sure you want to delete all similar inbox mails? This action cannot be undone.")) return; + deleteInboxMailThreadsSimilar(props.selectedThread.id, (res) => props.refetchInboxMailOverview()); }, [props.selectedThread?.id, props.refetchInboxMailOverview]); return ( @@ -92,19 +115,38 @@ function InboxMailAdminPanel(props: InboxMailAdminPanelProps) { {props.selectedThread.metaData.supportOwnerName?.last}
)} - {props.selectedThread?.metaData?.autoGenerated && -
- - -
- } +
+ {props.selectedThread?.metaData?.autoGenerated && ( + <> + + + + )} + + {props.selectedThread?.metaData?.autoGenerated && ( + {t("inboxMail.deleteAllByContentTooltip")}
} + placement="top" + color="invert" + > + } + /> + + )} +
{props.selectedThread.organizationId && (
diff --git a/components/inbox-mail/InboxMailThreadOverview.tsx b/components/inbox-mail/InboxMailThreadOverview.tsx index b7a5f36..2d9632e 100644 --- a/components/inbox-mail/InboxMailThreadOverview.tsx +++ b/components/inbox-mail/InboxMailThreadOverview.tsx @@ -1,7 +1,7 @@ import { useCallback, useMemo } from "react"; import { InboxMailThread, InboxMailThreadSupportProgressState } from "./types-mail"; import { Tooltip } from "@nextui-org/react"; -import { MemoIconAlertTriangle, MemoIconCircleCheck, MemoIconHelpCircle, MemoIconProgressCheck, MemoIconUser } from "../kern-icons/icons"; +import { MemoIconAlertTriangle, MemoIconCircleCheck, MemoIconHelpCircle, MemoIconProgressCheck, MemoIconUser, MemoIconUsers } from "../kern-icons/icons"; import { formatDisplayTimestamp } from "@/submodules/javascript-functions/date-parser"; import { useRouter } from "next/router"; @@ -68,9 +68,13 @@ export default function InboxMailThreadOverview(props: InboxMailThreadOverviewPr
{displayName ?? `<${t("inboxMail.unknownUser")}>`}
- {props.isAdmin ?
- Org: {props.thread.organizationName} -
: null} + {props.isAdmin && ( + +
+ +
+
+ )} {unreadMailCount > 0 && ( {unreadMailCount} {t("inboxMail.newBadge")} diff --git a/components/inbox-mail/InboxMailView.tsx b/components/inbox-mail/InboxMailView.tsx index 9f89a39..9765d9a 100644 --- a/components/inbox-mail/InboxMailView.tsx +++ b/components/inbox-mail/InboxMailView.tsx @@ -1,10 +1,10 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import CreateNewMailModal from "./CreateNewMailModal"; -import { InboxMail, InboxMailThread, User, InboxMailThreadSupportProgressState } from "./types-mail"; +import { InboxMail, InboxMailThread, User, InboxMailThreadSupportProgressState, InboxMailFilter, SUPPORT_FILTERS, THREAD_TYPE_FILTERS } from "./types-mail"; import { getInboxMailOverviewByThreadsPaginated, getInboxMailsByThread, createInboxMailByThread, updateInboxMailThreadProgress, deleteInboxMailById } from "./service-mail"; import KernButton from "../kern-button/KernButton"; -import { MemoIconPlus, MemoIconRefresh, MemoIconHelpCircle } from "../kern-icons/icons"; +import { MemoIconPlus, MemoIconRefresh, MemoIconHelpCircle, MemoIconX } from "../kern-icons/icons"; import useRefState from "../../hooks/useRefState"; import { MAIL_LIMIT_PER_PAGE, prepareThreadDisplayData } from "./helper"; import useEnumOptionsTranslated, { getEnumOptionsForLanguage } from "../../hooks/enums/useEnumOptionsTranslated"; @@ -14,6 +14,9 @@ import InboxMailAdminPanel from "./InboxMailAdminPanel"; import { UserRole } from "@/submodules/javascript-functions/enums/enums"; import InboxMailThreadOverview from "./InboxMailThreadOverview"; import ThreadMailItem from "./InboxMailItem"; +import KernDropdown from "../KernDropdown"; +import { enumToArray } from "@/submodules/javascript-functions/general"; +import { caseType } from "@/submodules/javascript-functions/case-types-parser"; interface InboxMailViewProps { @@ -38,11 +41,76 @@ export default function InboxMailView(props: InboxMailViewProps) { const [organizations, setOrganizations] = useState([]); const [selectedOrganization, setSelectedOrganization] = useState(null); const [isAdmin, setIsAdmin] = useState(undefined); + const { state: currentFilter, setState: setCurrentFilter, ref: currentFilterRef } = useRefState([]); + const { state: filterOrgId, setState: setFilterOrgId, ref: filterOrgIdRef } = useRefState(null); const progressStateOptions = props.translatorScope?.type === "local" ? getEnumOptionsForLanguage(InboxMailThreadSupportProgressState, "InboxMailThreadSupportProgressState", t, "en") : props.translatorScope?.type === "i18n" ? useEnumOptionsTranslated(InboxMailThreadSupportProgressState, "InboxMailThreadSupportProgressState", "enums") : []; + const allFilterOptions = useMemo(() => enumToArray(InboxMailFilter, { caseType: caseType.CAPITALIZE_FIRST_PER_WORD }), []); + + const availableFilterOptions = useMemo(() => + allFilterOptions.filter(option => !currentFilter.includes(option.value)), + [allFilterOptions, currentFilter] + ); + + const filterTooltips = useMemo(() => + availableFilterOptions.map(option => + option.value === InboxMailFilter.ALL + ? t("inboxMail.filterAllTooltip") + : null + ), + [availableFilterOptions, t] + ); + + const addFilter = useCallback((option: { name: string, value: InboxMailFilter }) => { + const newValue = option.value; + let newFilters: InboxMailFilter[]; + + // ALL clears everything else + if (newValue === InboxMailFilter.ALL) { + newFilters = [InboxMailFilter.ALL]; + } else { + let filtered = currentFilterRef.current.filter(f => f !== InboxMailFilter.ALL); + // Support filters are mutually exclusive + if (SUPPORT_FILTERS.includes(newValue)) filtered = filtered.filter(f => !SUPPORT_FILTERS.includes(f)); + + // Thread type filters are mutually exclusive + if (THREAD_TYPE_FILTERS.includes(newValue)) filtered = filtered.filter(f => !THREAD_TYPE_FILTERS.includes(f)); + + newFilters = [...filtered, newValue]; + } + setCurrentFilter(newFilters); + }, []); + + const removeFilter = useCallback((filter: InboxMailFilter) => { + const newFilters = currentFilterRef.current.filter(f => f !== filter); + if (newFilters.length === 0) newFilters.push(InboxMailFilter.ALL); + setCurrentFilter(newFilters); + }, []); + + const getFilterDisplayName = useCallback((filter: InboxMailFilter) => { + return allFilterOptions.find(o => o.value === filter)?.name || filter; + }, [allFilterOptions]); + + const orgFilterOptions = useMemo(() => + organizations.map((org: any) => ({ name: org.name, value: org.id })), + [organizations] + ); + + const selectedOrgName = useMemo(() => + organizations.find((org: any) => org.id === filterOrgId)?.name || null, + [organizations, filterOrgId] + ); + + + const selectOrgFilter = useCallback((option: { name: string, value: string }) => { + if (option.value) setFilterOrgId(option.value); + }, []); + + const clearOrgFilter = useCallback(() => setFilterOrgId(null), []); + useEffect(() => { getUserInfoExtended(res => { setCurrentUser(res); @@ -50,6 +118,11 @@ export default function InboxMailView(props: InboxMailViewProps) { getIsAdmin((isAdmin) => setIsAdmin(isAdmin)); }, []); + useEffect(() => { + if (isAdmin === undefined) return; + setCurrentFilter(isAdmin ? [InboxMailFilter.SUPPORT_PENDING] : [InboxMailFilter.ALL]); + }, [isAdmin]); + useEffect(() => { if (!isAdmin) return; getAllOrganizations((res) => { @@ -64,8 +137,21 @@ export default function InboxMailView(props: InboxMailViewProps) { }, [isAdmin, selectedOrganization, currentUser]); useEffect(() => { + if (isAdmin === undefined || currentFilter.length === 0) return; + refetchInboxMailOverview(); + }, [currentPage, isAdmin]); + + useEffect(() => { + if (currentFilter.length === 0) return; + setCurrentPage(1); refetchInboxMailOverview(); - }, [currentPage]); + }, [currentFilter]); + + useEffect(() => { + if (isAdmin === undefined || currentFilter.length === 0) return; + setCurrentPage(1); + refetchInboxMailOverview(); + }, [filterOrgId]); useEffect(() => { if (!selectedThread?.id) return @@ -74,7 +160,9 @@ export default function InboxMailView(props: InboxMailViewProps) { }, [selectedThread?.id, props.handleInboxMailRefreshToken, isAdmin]); const refetchInboxMailOverview = useCallback(() => { - getInboxMailOverviewByThreadsPaginated(currentPage, MAIL_LIMIT_PER_PAGE, (res) => { + const filters: string[] = [...currentFilterRef.current]; + if (filterOrgIdRef.current) filters.push(filterOrgIdRef.current); + getInboxMailOverviewByThreadsPaginated(currentPage, MAIL_LIMIT_PER_PAGE, filters, (res) => { setInboxMailThreads(res.threads); setFullCount(res?.totalThreads); props.handleInboxMailRefreshToken?.(Date.now()); @@ -87,16 +175,16 @@ export default function InboxMailView(props: InboxMailViewProps) { setThreadMails(res); if (selectedThread.unreadMailCount > 0) { setInboxMailThreads(prevThreads => prevThreads.map(t => t.id === selectedThread.id ? { ...t, unreadMailCount: 0 } : t)); + setSelectedThread(prev => prev ? { ...prev, unreadMailCount: 0 } : prev); if (resetRefreshToken) props.handleInboxMailRefreshToken?.(Date.now()); } if (selectedThread.isAdminSupportThread && isAdmin && selectedThread.metaData?.unreadMailCountAdmin > 0) { + const updatedMetaData = { ...selectedThread.metaData, unreadMailCountAdmin: 0 }; setInboxMailThreads(prevThreads => prevThreads.map(t => t.id === selectedThread.id ? { ...t, - metaData: { - ...t.metaData, - unreadMailCountAdmin: 0 - }, + metaData: updatedMetaData, } : t)); + setSelectedThread(prev => prev ? { ...prev, metaData: updatedMetaData } : prev); if (resetRefreshToken) props.handleInboxMailRefreshToken?.(Date.now()); } }); @@ -105,7 +193,9 @@ export default function InboxMailView(props: InboxMailViewProps) { const handleInboxMailCreation = useCallback((content: string, recipientIds?: string[], subject?: string, isImportant?: boolean, metaData?: any) => { createInboxMailByThread(content, (result) => { setOpenCreateMail(false); - getInboxMailOverviewByThreadsPaginated(currentPage, MAIL_LIMIT_PER_PAGE, (res) => { + const filters: string[] = [...currentFilterRef.current]; + if (filterOrgIdRef.current) filters.push(filterOrgIdRef.current); + getInboxMailOverviewByThreadsPaginated(currentPage, MAIL_LIMIT_PER_PAGE, filters, (res) => { if (isNewThread && res.threads.length > 0) { setSelectedThread(res.threads.find((thread) => thread.id === result.threadId)); } @@ -173,31 +263,78 @@ export default function InboxMailView(props: InboxMailViewProps) { return (
-
- - { - setIsNewThread(true); - setOpenCreateMail(true); - setIsAdminSupportThread(true); - }} /> - { - setIsNewThread(true); - setOpenCreateMail(true); - setIsAdminSupportThread(false); - }} /> +
+ {isAdmin && ( + <> +
+ {currentFilter.map(filter => ( +
+ {getFilterDisplayName(filter)} + {filter != InboxMailFilter.ALL && ( + + )} +
+ ))} + {selectedOrgName && ( +
+ {selectedOrgName} + +
+ )} +
+
+ {availableFilterOptions.length > 0 && ( + + )} + {orgFilterOptions.length > 0 && ( + + )} +
+ + )} +
+ + { + setIsNewThread(true); + setOpenCreateMail(true); + setIsAdminSupportThread(true); + }} /> + { + setIsNewThread(true); + setOpenCreateMail(true); + setIsAdminSupportThread(false); + }} /> +
{inboxMailThreads?.length === 0 ? ( @@ -231,6 +368,7 @@ export default function InboxMailView(props: InboxMailViewProps) { handleInboxMailProgressChange={handleInboxMailProgressChange} currentUser={currentUser} refetchInboxMailOverview={refetchInboxMailOverview} + translator={t} /> } {threadMails.map((mail: InboxMail) => ( diff --git a/components/inbox-mail/inboxMailLocalTranslations.json b/components/inbox-mail/inboxMailLocalTranslations.json index 0374ec2..cf08907 100644 --- a/components/inbox-mail/inboxMailLocalTranslations.json +++ b/components/inbox-mail/inboxMailLocalTranslations.json @@ -36,7 +36,15 @@ "cancelButton": "Cancel", "previous": "Previous", "next": "Next", - "to": "To" + "to": "To", + "addFilter": "Add Filter", + "filterAllTooltip": "Selecting 'All' clears other filters", + "markLastUnread": "Mark last unread", + "filterByOrg": "Filter by Org", + "readAllByError": "Read all by error", + "readAllByProject": "Read all by project", + "deleteAllByContent": "Delete all by content", + "deleteAllByContentTooltip": "Deletes all auto-generated threads with the same content in the same organization" }, "InboxMailThreadSupportProgressState": { "PENDING": "Pending", diff --git a/components/inbox-mail/service-mail.tsx b/components/inbox-mail/service-mail.tsx index d730e54..0bb94b8 100644 --- a/components/inbox-mail/service-mail.tsx +++ b/components/inbox-mail/service-mail.tsx @@ -1,5 +1,5 @@ import { FetchType, jsonFetchWrapper } from "@/submodules/javascript-functions/basic-fetch"; -import { InboxMailThreadSupportProgressState } from "./types-mail"; +import { InboxMailFilter, InboxMailThreadSupportProgressState } from "./types-mail"; import { convertCamelToSnakeCase } from "@/submodules/javascript-functions/case-types-parser"; const url = `/refinery-gateway/api/v1`; @@ -11,8 +11,13 @@ export function createInboxMailByThread(content: string, onResult: (result: any) } -export function getInboxMailOverviewByThreadsPaginated(page: number, limit: number, onResult: (result: any) => void) { - const fetchUrl = `${url}/inbox-mail/overview?page=${page}&limit=${limit}`; +export function getInboxMailOverviewByThreadsPaginated(page?: number, limit?: number, filters?: string[], onResult?: (result: any) => void) { + const searchParams = new URLSearchParams(); + if (page !== undefined) searchParams.append("page", page.toString()); + if (limit !== undefined) searchParams.append("limit", limit.toString()); + if (filters?.length) filters.forEach(filter => searchParams.append("filters", filter)); + const queryString = searchParams.toString(); + const fetchUrl = `${url}/inbox-mail/overview${queryString ? `?${queryString}` : ""}`; jsonFetchWrapper(fetchUrl, FetchType.GET, onResult); } @@ -96,4 +101,14 @@ export function updateInboxMailThreadsUnreadByProject(threadId: string, onResult export function updateInboxMailThreadsUnreadByContent(threadId: string, onResult: (result: any) => void) { const finalUrl = `${url}/inbox-mail/thread/${threadId}/unread/content`; jsonFetchWrapper(finalUrl, FetchType.PUT, onResult); +} + +export function updateInboxMailThreadUnreadLast(threadId: string, onResult: (result: any) => void) { + const finalUrl = `${url}/inbox-mail/thread/${threadId}/unread-last`; + jsonFetchWrapper(finalUrl, FetchType.PUT, onResult); +} + +export function deleteInboxMailThreadsSimilar(threadId: string, onResult: (result: any) => void) { + const finalUrl = `${url}/inbox-mail/thread/${threadId}/similar`; + jsonFetchWrapper(finalUrl, FetchType.DELETE, onResult); } \ No newline at end of file diff --git a/components/inbox-mail/types-mail.tsx b/components/inbox-mail/types-mail.tsx index 17bbc5c..9fb15c2 100644 --- a/components/inbox-mail/types-mail.tsx +++ b/components/inbox-mail/types-mail.tsx @@ -65,4 +65,18 @@ export enum JumpDestination { CONVERSATION = "CONVERSATION", PROJECT = "PROJECT", ORGANIZATION = "ORGANIZATION" -} \ No newline at end of file +} + +export enum InboxMailFilter { + ALL = "ALL", + UNREAD = "UNREAD", + IMPORTANT = "IMPORTANT", + SUPPORT_PENDING = "SUPPORT_PENDING", + SUPPORT_IN_PROGRESS = "SUPPORT_IN_PROGRESS", + SUPPORT_RESOLVED = "SUPPORT_RESOLVED", + ONLY_USER_THREADS = "ONLY_USER_THREADS", + ONLY_SYSTEM_THREADS = "ONLY_SYSTEM_THREADS" +} + +export const SUPPORT_FILTERS = [InboxMailFilter.SUPPORT_PENDING, InboxMailFilter.SUPPORT_IN_PROGRESS, InboxMailFilter.SUPPORT_RESOLVED]; +export const THREAD_TYPE_FILTERS = [InboxMailFilter.ONLY_USER_THREADS, InboxMailFilter.ONLY_SYSTEM_THREADS]; \ No newline at end of file diff --git a/components/kern-icons/icons.ts b/components/kern-icons/icons.ts index 07c9b0b..8e3ead6 100644 --- a/components/kern-icons/icons.ts +++ b/components/kern-icons/icons.ts @@ -1,5 +1,5 @@ import { memo } from 'react'; -import { IconActivity, IconAdjustments, IconAdjustmentsAlt, IconAdjustmentsOff, IconAlertCircle, IconAlertTriangle, IconAlertTriangleFilled, IconAngle, IconApi, IconArchive, IconArrowAutofitDown, IconArrowCurveRight, IconArrowDown, IconArrowLeft, IconArrowNarrowLeft, IconArrowNarrowRight, IconArrowRight, IconArrowsRandom, IconArrowsSort, IconArrowUp, IconArrowUpRight, IconAssembly, IconBallpen, IconBallpenOff, IconBell, IconBolt, IconBottle, IconBox, IconBoxOff, IconBrandGithub, IconBrandOpenai, IconBrandPython, IconBulb, IconBulldozer, IconCamera, IconCategoryPlus, IconCell, IconChartBubble, IconChartCircles, IconChartDots3, IconChartLine, IconChartPie, IconCheck, IconChecks, IconChevronCompactLeft, IconChevronCompactRight, IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronUp, IconCircle, IconCircleCheck, IconCircleCheckFilled, IconCircleMinus, IconCirclePlus, IconClick, IconClipboard, IconClipboardCheck, IconClipboardOff, IconClock, IconCode, IconCodePlus, IconColorPicker, IconColumns, IconColumns1, IconColumns2, IconColumns3, IconCopy, IconCrown, IconCrownOff, IconDatabase, IconDatabasePlus, IconDeviceFloppy, IconDots, IconDotsVertical, IconDownload, IconEdit, IconEngine, IconExclamationCircle, IconExclamationMark, IconExternalLink, IconEye, IconEyeCancel, IconEyeCheck, IconEyeOff, IconFile, IconFileDownload, IconFileImport, IconFileInfo, IconFilePencil, IconFiles, IconFileText, IconFileUpload, IconFileWord, IconFilter, IconFilterOff, IconFishHook, IconFolderBolt, IconGitCommit, IconGripVertical, IconHandClick, IconHeading, IconHelp, IconHexagons, IconHierarchy, IconHierarchy3, IconHistory, IconHome, IconHourglass, IconHourglassEmpty, IconInfoCircle, IconInfoCircleFilled, IconInfoSquare, IconLayoutList, IconLayoutNavbarCollapse, IconLayoutSidebar, IconLetterGSmall, IconLink, IconList, IconLoader, IconLoader2, IconLockAccess, IconMap, IconMaximize, IconMessageCircle, IconMinus, IconMessageCircleSearch, IconMessages, IconMinimize, IconMoustache, IconNews, IconNotes, IconPencil, IconPlayCardStar, IconPlayerPlay, IconPlayerPlayFilled, IconPlus, IconPoint, IconPointerOff, IconPointerSearch, IconPointFilled, IconQuestionMark, IconRecycle, IconRefresh, IconRefreshAlert, IconResize, IconRobot, IconRotate, IconScissors, IconScreenshot, IconSearch, IconSend, IconSettings, IconShare, IconShieldCheckFilled, IconShieldFilled, IconSquare, IconSquareCheck, IconStar, IconTag, IconTemplate, IconTerminal, IconThumbDown, IconThumbDownFilled, IconThumbUp, IconThumbUpFilled, IconTrash, IconTrashXFilled, IconTriangleInverted, IconTriangleSquareCircle, IconUpload, IconUser, IconUsersGroup, IconUserX, IconVariable, IconVariablePlus, IconVersions, IconWand, IconWaveSine, IconWebhook, IconWreckingBall, IconX, IconZoomCode, IconDragDrop2, IconCircleDotted, IconWorld, IconMail, IconHelpCircle, IconProgressCheck } from '@tabler/icons-react'; +import { IconActivity, IconAdjustments, IconAdjustmentsAlt, IconAdjustmentsOff, IconAlertCircle, IconAlertTriangle, IconAlertTriangleFilled, IconAngle, IconApi, IconArchive, IconArrowAutofitDown, IconArrowCurveRight, IconArrowDown, IconArrowLeft, IconArrowNarrowLeft, IconArrowNarrowRight, IconArrowRight, IconArrowsRandom, IconArrowsSort, IconArrowUp, IconArrowUpRight, IconAssembly, IconBallpen, IconBallpenOff, IconBell, IconBolt, IconBottle, IconBox, IconBoxOff, IconBrandGithub, IconBrandOpenai, IconBrandPython, IconBulb, IconBulldozer, IconCamera, IconCategoryPlus, IconCell, IconChartBubble, IconChartCircles, IconChartDots3, IconChartLine, IconChartPie, IconCheck, IconChecks, IconChevronCompactLeft, IconChevronCompactRight, IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronUp, IconCircle, IconCircleCheck, IconCircleCheckFilled, IconCircleMinus, IconCirclePlus, IconClick, IconClipboard, IconClipboardCheck, IconClipboardOff, IconClock, IconCode, IconCodePlus, IconColorPicker, IconColumns, IconColumns1, IconColumns2, IconColumns3, IconCopy, IconCrown, IconCrownOff, IconDatabase, IconDatabasePlus, IconDeviceFloppy, IconDots, IconDotsVertical, IconDownload, IconEdit, IconEngine, IconExclamationCircle, IconExclamationMark, IconExternalLink, IconEye, IconEyeCancel, IconEyeCheck, IconEyeOff, IconFile, IconFileDownload, IconFileImport, IconFileInfo, IconFilePencil, IconFiles, IconFileText, IconFileUpload, IconFileWord, IconFilter, IconFilterOff, IconFishHook, IconFolderBolt, IconGitCommit, IconGripVertical, IconHandClick, IconHeading, IconHelp, IconHexagons, IconHierarchy, IconHierarchy3, IconHistory, IconHome, IconHourglass, IconHourglassEmpty, IconInfoCircle, IconInfoCircleFilled, IconInfoSquare, IconLayoutList, IconLayoutNavbarCollapse, IconLayoutSidebar, IconLetterGSmall, IconLink, IconList, IconLoader, IconLoader2, IconLockAccess, IconMap, IconMaximize, IconMessageCircle, IconMinus, IconMessageCircleSearch, IconMessages, IconMinimize, IconMoustache, IconNews, IconNotes, IconPencil, IconPlayCardStar, IconPlayerPlay, IconPlayerPlayFilled, IconPlus, IconPoint, IconPointerOff, IconPointerSearch, IconPointFilled, IconQuestionMark, IconRecycle, IconRefresh, IconRefreshAlert, IconResize, IconRobot, IconRotate, IconScissors, IconScreenshot, IconSearch, IconSend, IconSettings, IconShare, IconShieldCheckFilled, IconShieldFilled, IconSquare, IconSquareCheck, IconStar, IconTag, IconTemplate, IconTerminal, IconThumbDown, IconThumbDownFilled, IconThumbUp, IconThumbUpFilled, IconTrash, IconTrashXFilled, IconTriangleInverted, IconTriangleSquareCircle, IconUpload, IconUser, IconUsers, IconUsersGroup, IconUserX, IconVariable, IconVariablePlus, IconVersions, IconWand, IconWaveSine, IconWebhook, IconWreckingBall, IconX, IconZoomCode, IconDragDrop2, IconCircleDotted, IconWorld, IconMail, IconHelpCircle, IconProgressCheck } from '@tabler/icons-react'; export const MemoIconHome = memo(IconHome); export const MemoIconInfoCircle = memo(IconInfoCircle); @@ -188,4 +188,5 @@ export const MemoIconWorld = memo(IconWorld); export const MemoIconMail = memo(IconMail); export const MemoIconHelpCircle = memo(IconHelpCircle); export const MemoIconProgressCheck = memo(IconProgressCheck); -export const MemoIconFileWord = memo(IconFileWord); \ No newline at end of file +export const MemoIconFileWord = memo(IconFileWord); +export const MemoIconUsers = memo(IconUsers); \ No newline at end of file From 845e4d14416acc80b4a8f001ee9494c593f65bdf Mon Sep 17 00:00:00 2001 From: JWittmeyer Date: Tue, 27 Jan 2026 10:47:39 +0100 Subject: [PATCH 6/7] Light user config cells --- components/kern-table/CellComponents.tsx | 37 +++++++++++++++++------- components/kern-table/KernTable.tsx | 6 +++- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/components/kern-table/CellComponents.tsx b/components/kern-table/CellComponents.tsx index 795e495..dc1f167 100644 --- a/components/kern-table/CellComponents.tsx +++ b/components/kern-table/CellComponents.tsx @@ -14,17 +14,19 @@ import { MemoIconAlertCircle, MemoIconAlertTriangleFilled, MemoIconArrowRight, M function OrganizationAndUsersCell({ organization }) { return ( -
+
{organization.name}
-
- - - - {organization.userCount} -
+ +
+ + + + {!organization.userCount ? : organization.userCount.total + " / " + organization.userCount.normal + " / " + organization.userCount.light} +
+
) } @@ -73,6 +75,12 @@ function BadgeCell({ value }) { return {value ? () : ()} } +function NulledBadgeCell({ value, tooltip, tooltipPlacement }: { value: boolean | null | undefined, tooltip?: string, tooltipPlacement?: "top" | "bottom" | "left" | "right" }) { + const cell = {value == true ? () : value == false ? () : ()} + if (tooltip) return {cell} + else return cell; +} + function OrganizationUserCell({ userToOrganization, organizations, user, onClickRemove, onClickAdd }) { return <>{(userToOrganization && user.id in userToOrganization) ? (
{userToOrganization[user.id]['name']} @@ -298,6 +306,15 @@ function EtlApiTokenCell({ organization }) { ) } +function LightUserConfigCell({ organization }) { + return ( +
+ {!organization.lightUserConfig ? : + {organization.lightUserConfig?.amount} / {organization.lightUserConfig.timeFrame}} +
+ ) +} + function EmailCell({ user }) { const tooltipContent = useMemo(() => { let content = user.email; @@ -406,4 +423,4 @@ function TaskStateCell({ value, color, tooltipValue }) { ); } -export { OrganizationAndUsersCell, MaxRowsColsCharsCell, CommentsCell, ExportConsumptionAndDeleteCell, BadgeCell, OrganizationUserCell, DeleteCell, LevelCell, ArchiveReasonCell, ProjectNameTaskCell, CancelTaskCell, IconCell, ConfigCell, EditDeleteOrgButtonCell, ViewStackCell, AbortSessionButtonCell, FeedbackMessageCell, FeedbackMessageTextCell, JumpToConversationCell, RemoteVersionCell, ExternalLinkCell, ModelDateCell, FileSizeCell, StatusModelCell, DeleteModelCell, LabelCell, ViewCell, EvaluationRunStateCell, EvaluationRunDetailsCell, EtlApiTokenCell, EmailCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell, TruncateAndTooltipCell, JumpToConversationAndAssignCell, TaskStateCell } \ No newline at end of file +export { OrganizationAndUsersCell, MaxRowsColsCharsCell, CommentsCell, LightUserConfigCell, ExportConsumptionAndDeleteCell, BadgeCell, NulledBadgeCell, OrganizationUserCell, DeleteCell, LevelCell, ArchiveReasonCell, ProjectNameTaskCell, CancelTaskCell, IconCell, ConfigCell, EditDeleteOrgButtonCell, ViewStackCell, AbortSessionButtonCell, FeedbackMessageCell, FeedbackMessageTextCell, JumpToConversationCell, RemoteVersionCell, ExternalLinkCell, ModelDateCell, FileSizeCell, StatusModelCell, DeleteModelCell, LabelCell, ViewCell, EvaluationRunStateCell, EvaluationRunDetailsCell, EtlApiTokenCell, EmailCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell, TruncateAndTooltipCell, JumpToConversationAndAssignCell, TaskStateCell } \ No newline at end of file diff --git a/components/kern-table/KernTable.tsx b/components/kern-table/KernTable.tsx index 3b8657f..e138b7d 100644 --- a/components/kern-table/KernTable.tsx +++ b/components/kern-table/KernTable.tsx @@ -1,6 +1,6 @@ import SortArrows from "@/submodules/react-components/components/kern-table/SortArrows"; import { KernTableProps } from "../../types/kern-table"; -import { AbortSessionButtonCell, ArchiveReasonCell, BadgeCell, CancelTaskCell, CommentsCell, ConfigCell, DeleteModelCell, DeleteCell, EditDeleteOrgButtonCell, EmailCell, EtlApiTokenCell, EvaluationRunDetailsCell, EvaluationRunStateCell, ExportConsumptionAndDeleteCell, ExternalLinkCell, FeedbackMessageCell, FeedbackMessageTextCell, FileSizeCell, IconCell, JumpToConversationCell, LabelCell, LevelCell, MaxRowsColsCharsCell, ModelDateCell, OrganizationAndUsersCell, OrganizationUserCell, ProjectNameTaskCell, RemoteVersionCell, StatusModelCell, ViewCell, ViewStackCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell, TruncateAndTooltipCell, JumpToConversationAndAssignCell, TaskStateCell } from "./CellComponents"; +import { AbortSessionButtonCell, ArchiveReasonCell, BadgeCell, CancelTaskCell, CommentsCell, ConfigCell, DeleteModelCell, DeleteCell, EditDeleteOrgButtonCell, EmailCell, EtlApiTokenCell, LightUserConfigCell, EvaluationRunDetailsCell, EvaluationRunStateCell, ExportConsumptionAndDeleteCell, ExternalLinkCell, FeedbackMessageCell, FeedbackMessageTextCell, FileSizeCell, IconCell, JumpToConversationCell, LabelCell, LevelCell, MaxRowsColsCharsCell, ModelDateCell, OrganizationAndUsersCell, OrganizationUserCell, ProjectNameTaskCell, RemoteVersionCell, StatusModelCell, ViewCell, ViewStackCell, EditIntegrationCell, ExpiredTokenCell, LinkCell, ConfigReleaseNotificationCell, TruncateAndTooltipCell, JumpToConversationAndAssignCell, TaskStateCell, NulledBadgeCell } from "./CellComponents"; import { Fragment, useMemo } from "react"; import KernDropdown from "../KernDropdown"; import { NotApplicableBadge } from "@/submodules/react-components/components/Badges"; @@ -102,6 +102,8 @@ function ComponentMapper(cell: any) { return ; case 'BadgeCell': return ; + case 'NulledBadgeCell': + return ; case 'OrganizationUserCell': return ; case 'DeleteCell': @@ -152,6 +154,8 @@ function ComponentMapper(cell: any) { return ; case 'EtlApiTokenCell': return ; + case 'LightUserConfigCell': + return ; case 'EmailCell': return ; case 'EditIntegrationCell': From b4e5932f5ee5536f0b02d4b53642fa53903fdd9c Mon Sep 17 00:00:00 2001 From: JWittmeyer Date: Wed, 28 Jan 2026 13:22:06 +0100 Subject: [PATCH 7/7] PR comments --- components/inbox-mail/InboxMailThreadOverview.tsx | 7 ++----- components/inbox-mail/InboxMailView.tsx | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/components/inbox-mail/InboxMailThreadOverview.tsx b/components/inbox-mail/InboxMailThreadOverview.tsx index 2d9632e..f39341b 100644 --- a/components/inbox-mail/InboxMailThreadOverview.tsx +++ b/components/inbox-mail/InboxMailThreadOverview.tsx @@ -1,9 +1,8 @@ -import { useCallback, useMemo } from "react"; +import { useMemo } from "react"; import { InboxMailThread, InboxMailThreadSupportProgressState } from "./types-mail"; import { Tooltip } from "@nextui-org/react"; import { MemoIconAlertTriangle, MemoIconCircleCheck, MemoIconHelpCircle, MemoIconProgressCheck, MemoIconUser, MemoIconUsers } from "../kern-icons/icons"; import { formatDisplayTimestamp } from "@/submodules/javascript-functions/date-parser"; -import { useRouter } from "next/router"; interface InboxMailThreadOverviewProps { thread: InboxMailThread; @@ -15,7 +14,6 @@ interface InboxMailThreadOverviewProps { export default function InboxMailThreadOverview(props: InboxMailThreadOverviewProps) { const t = useMemo(() => props.translator, [props.translator]); - const router = useRouter(); const { displayName, displayInitials, @@ -34,7 +32,6 @@ export default function InboxMailThreadOverview(props: InboxMailThreadOverviewPr } }, [props.thread, props.isAdmin]); - const clickHome = useCallback(() => router.push("/"), [router]); return (
) : (
- Kern AI + Kern AI
)} diff --git a/components/inbox-mail/InboxMailView.tsx b/components/inbox-mail/InboxMailView.tsx index 9765d9a..ef65e88 100644 --- a/components/inbox-mail/InboxMailView.tsx +++ b/components/inbox-mail/InboxMailView.tsx @@ -361,7 +361,7 @@ export default function InboxMailView(props: InboxMailViewProps) {
{selectedThread && threadMails && threadMails.length > 0 ? ( <> - {isAdmin && + {isAdmin && selectedThread.isAdminSupportThread &&