diff --git a/frontend/__snapshots__/scenes-app-saved-insights--card-view.png b/frontend/__snapshots__/scenes-app-saved-insights--card-view.png index 3d306a9235a94..6ada9d2942660 100644 Binary files a/frontend/__snapshots__/scenes-app-saved-insights--card-view.png and b/frontend/__snapshots__/scenes-app-saved-insights--card-view.png differ diff --git a/frontend/src/lib/components/MemberSelect.tsx b/frontend/src/lib/components/MemberSelect.tsx new file mode 100644 index 0000000000000..cde54586f1fce --- /dev/null +++ b/frontend/src/lib/components/MemberSelect.tsx @@ -0,0 +1,124 @@ +import { LemonButton, LemonButtonProps, LemonDropdown, LemonInput, ProfilePicture } from '@posthog/lemon-ui' +import { useValues } from 'kea' +import { useMemo, useState } from 'react' +import { membersLogic } from 'scenes/organization/membersLogic' + +import { UserBasicType } from '~/types' + +export type MemberSelectProps = Pick & { + defaultLabel?: string + // NOTE: Trying to cover a lot of different cases - if string we assume uuid, if number we assume id + value: UserBasicType | string | number | null + onChange: (value: UserBasicType | null) => void +} + +export function MemberSelect({ + defaultLabel = 'All users', + value, + onChange, + ...buttonProps +}: MemberSelectProps): JSX.Element { + const { meFirstMembers, membersFuse } = useValues(membersLogic) + const [showPopover, setShowPopover] = useState(false) + const [searchTerm, setSearchTerm] = useState('') + + const filteredMembers = useMemo(() => { + return searchTerm ? membersFuse.search(searchTerm).map((result) => result.item) : meFirstMembers + }, [searchTerm, meFirstMembers]) + + const selectedMember = useMemo(() => { + if (!value) { + return null + } + if (typeof value === 'string' || typeof value === 'number') { + const propToCompare = typeof value === 'string' ? 'uuid' : 'id' + return meFirstMembers.find((member) => member.user[propToCompare] === value)?.user ?? `${value}` + } + return value + }, [value, meFirstMembers]) + + const _onChange = (value: UserBasicType | null): void => { + setShowPopover(false) + onChange(value) + } + + return ( + setShowPopover(visible)} + overlay={ +
+ +
    +
  • + _onChange(null)} + > + {defaultLabel} + +
  • + + {filteredMembers.map((member) => ( +
  • + + } + onClick={() => _onChange(member.user)} + > + + {member.user.first_name} + + {meFirstMembers[0] === member && `(you)`} + + + +
  • + ))} + + {filteredMembers.length === 0 ? ( +
    + {searchTerm ? No matches : No users} +
    + ) : null} +
+
+ } + > + + {typeof selectedMember === 'string' ? ( + selectedMember + ) : selectedMember ? ( + + {selectedMember.first_name} + {meFirstMembers[0].user.uuid === selectedMember.uuid ? ` (you)` : ''} + + ) : ( + defaultLabel + )} + +
+ ) +} diff --git a/frontend/src/lib/lemon-ui/LemonDropdown/LemonDropdown.tsx b/frontend/src/lib/lemon-ui/LemonDropdown/LemonDropdown.tsx index 7d7f1d0502739..e55ebcc979ec8 100644 --- a/frontend/src/lib/lemon-ui/LemonDropdown/LemonDropdown.tsx +++ b/frontend/src/lib/lemon-ui/LemonDropdown/LemonDropdown.tsx @@ -1,4 +1,4 @@ -import React, { MouseEventHandler, useContext, useEffect, useRef, useState } from 'react' +import React, { MouseEventHandler, useContext, useRef, useState } from 'react' import { Popover, PopoverOverlayContext, PopoverProps } from '../Popover' @@ -38,6 +38,8 @@ export const LemonDropdown: React.FunctionComponent { + const isControlled = visible !== undefined + const [, parentPopoverLevel] = useContext(PopoverOverlayContext) const [localVisible, setLocalVisible] = useState(visible ?? false) @@ -46,9 +48,12 @@ export const LemonDropdown: React.FunctionComponent { - onVisibilityChange?.(localVisible) - }, [localVisible, onVisibilityChange]) + const setVisible = (value: boolean): void => { + if (!isControlled) { + setLocalVisible(value) + } + onVisibilityChange?.(value) + } return ( { if (trigger === 'click') { - setLocalVisible(false) + setVisible(false) } onClickOutside?.(e) }} onClickInside={(e) => { e.stopPropagation() - closeOnClickInside && setLocalVisible(false) + closeOnClickInside && setVisible(false) onClickInside?.(e) }} onMouseLeaveInside={(e) => { if (trigger === 'hover' && !referenceRef.current?.contains(e.relatedTarget as Node)) { - setLocalVisible(false) + setVisible(false) } onMouseLeaveInside?.(e) }} @@ -77,7 +82,7 @@ export const LemonDropdown: React.FunctionComponent {React.cloneElement(children, { onClick: (e: React.MouseEvent): void => { - setLocalVisible((state) => !state) + setVisible(!effectiveVisible) children.props.onClick?.(e) if (parentPopoverLevel > -1) { // If this button is inside another popover, let's not propagate this event so that @@ -87,12 +92,12 @@ export const LemonDropdown: React.FunctionComponent { if (trigger === 'hover') { - setLocalVisible(true) + setVisible(true) } }, onMouseLeave: (e: React.MouseEvent): void => { if (trigger === 'hover' && !floatingRef.current?.contains(e.relatedTarget as Node)) { - setLocalVisible(false) + setVisible(false) } }, 'aria-haspopup': 'true', diff --git a/frontend/src/scenes/dashboard/dashboards/DashboardsTable.tsx b/frontend/src/scenes/dashboard/dashboards/DashboardsTable.tsx index 00889a9e1e5aa..ab7307fe9e1c8 100644 --- a/frontend/src/scenes/dashboard/dashboards/DashboardsTable.tsx +++ b/frontend/src/scenes/dashboard/dashboards/DashboardsTable.tsx @@ -1,6 +1,7 @@ import { IconPin, IconPinFilled, IconShare } from '@posthog/icons' -import { LemonInput, LemonSelect } from '@posthog/lemon-ui' +import { LemonInput } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' +import { MemberSelect } from 'lib/components/MemberSelect' import { ObjectTags } from 'lib/components/ObjectTags/ObjectTags' import { DashboardPrivilegeLevel } from 'lib/constants' import { IconCottage, IconLock } from 'lib/lemon-ui/icons' @@ -18,7 +19,6 @@ import { dashboardLogic } from 'scenes/dashboard/dashboardLogic' import { DashboardsFilters, dashboardsLogic } from 'scenes/dashboard/dashboards/dashboardsLogic' import { deleteDashboardLogic } from 'scenes/dashboard/deleteDashboardLogic' import { duplicateDashboardLogic } from 'scenes/dashboard/duplicateDashboardLogic' -import { membersLogic } from 'scenes/organization/membersLogic' import { teamLogic } from 'scenes/teamLogic' import { urls } from 'scenes/urls' import { userLogic } from 'scenes/userLogic' @@ -56,7 +56,6 @@ export function DashboardsTable({ const { currentTeam } = useValues(teamLogic) const { showDuplicateDashboardModal } = useActions(duplicateDashboardLogic) const { showDeleteDashboardModal } = useActions(deleteDashboardLogic) - const { meFirstMembers } = useValues(membersLogic) const columns: LemonTableColumns = [ { @@ -246,20 +245,11 @@ export function DashboardsTable({
Created by: - ({ - value: x.user.uuid, - label: x.user.first_name, - })), - ]} + { - setFilters({ createdBy: v }) - }} - dropdownMatchSelectWidth={false} + type="secondary" + value={filters.createdBy === 'All users' ? null : filters.createdBy} + onChange={(user) => setFilters({ createdBy: user?.uuid || 'All users' })} />
{extraActions} diff --git a/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx b/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx index a122aaca2ee4b..03fbdec6bb01f 100644 --- a/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx +++ b/frontend/src/scenes/notebooks/NotebooksTable/NotebooksTable.tsx @@ -1,5 +1,6 @@ -import { LemonButton, LemonInput, LemonSelect, LemonTag } from '@posthog/lemon-ui' +import { LemonButton, LemonInput, LemonTag } from '@posthog/lemon-ui' import { useActions, useValues } from 'kea' +import { MemberSelect } from 'lib/components/MemberSelect' import { IconDelete, IconEllipsis } from 'lib/lemon-ui/icons' import { LemonBanner } from 'lib/lemon-ui/LemonBanner' import { LemonMenu } from 'lib/lemon-ui/LemonMenu' @@ -9,7 +10,6 @@ import { Link } from 'lib/lemon-ui/Link' import { useEffect } from 'react' import { ContainsTypeFilters } from 'scenes/notebooks/NotebooksTable/ContainsTypeFilter' import { DEFAULT_FILTERS, notebooksTableLogic } from 'scenes/notebooks/NotebooksTable/notebooksTableLogic' -import { membersLogic } from 'scenes/organization/membersLogic' import { urls } from 'scenes/urls' import { notebooksModel } from '~/models/notebooksModel' @@ -42,7 +42,6 @@ export function NotebooksTable(): JSX.Element { const { notebooksAndTemplates, filters, notebooksResponseLoading, notebookTemplates, sortValue, pagination } = useValues(notebooksTableLogic) const { loadNotebooks, setFilters, setSortValue } = useActions(notebooksTableLogic) - const { meFirstMembers } = useValues(membersLogic) const { selectNotebook } = useActions(notebookPanelLogic) useEffect(() => { @@ -121,20 +120,11 @@ export function NotebooksTable(): JSX.Element {
Created by: - ({ - value: x.user.uuid, - label: x.user.first_name, - })), - ]} + { - setFilters({ createdBy: v || DEFAULT_FILTERS.createdBy }) - }} - dropdownMatchSelectWidth={false} + onChange={(user) => setFilters({ createdBy: user?.uuid || DEFAULT_FILTERS.createdBy })} />
diff --git a/frontend/src/scenes/saved-insights/SavedInsightsFilters.tsx b/frontend/src/scenes/saved-insights/SavedInsightsFilters.tsx index e1e9e66aa111c..7859518c22366 100644 --- a/frontend/src/scenes/saved-insights/SavedInsightsFilters.tsx +++ b/frontend/src/scenes/saved-insights/SavedInsightsFilters.tsx @@ -1,9 +1,9 @@ import { IconCalendar } from '@posthog/icons' import { useActions, useValues } from 'kea' import { DateFilter } from 'lib/components/DateFilter/DateFilter' +import { MemberSelect } from 'lib/components/MemberSelect' import { LemonInput } from 'lib/lemon-ui/LemonInput/LemonInput' import { LemonSelect } from 'lib/lemon-ui/LemonSelect' -import { membersLogic } from 'scenes/organization/membersLogic' import { INSIGHT_TYPE_OPTIONS } from 'scenes/saved-insights/SavedInsights' import { savedInsightsLogic } from 'scenes/saved-insights/savedInsightsLogic' @@ -17,8 +17,6 @@ export function SavedInsightsFilters(): JSX.Element { const { tab, createdBy, insightType, dateFrom, dateTo, dashboardId, search } = filters - const { meFirstMembers } = useValues(membersLogic) - return (
Created by: - {/* TODO: Fix issues with user name order due to numbers having priority */} - ({ - value: x.user.id, - label: x.user.first_name, - })), - ]} - value={createdBy} - onChange={(v: any): void => { - setSavedInsightsFilters({ createdBy: v }) - }} - dropdownMatchSelectWidth={false} + type="secondary" + value={createdBy === 'All users' ? null : createdBy} + onChange={(user) => setSavedInsightsFilters({ createdBy: user?.id || 'All users' })} />
) : null} diff --git a/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylists.tsx b/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylists.tsx index 69733a2826df3..aafd93bbb834a 100644 --- a/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylists.tsx +++ b/frontend/src/scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylists.tsx @@ -1,13 +1,13 @@ import { TZLabel } from '@posthog/apps-common' -import { LemonButton, LemonDivider, LemonInput, LemonSelect, LemonTable, Link } from '@posthog/lemon-ui' +import { LemonButton, LemonDivider, LemonInput, LemonTable, Link } from '@posthog/lemon-ui' import clsx from 'clsx' import { useActions, useValues } from 'kea' import { DateFilter } from 'lib/components/DateFilter/DateFilter' +import { MemberSelect } from 'lib/components/MemberSelect' import { IconCalendar, IconPinFilled, IconPinOutline } from 'lib/lemon-ui/icons' import { More } from 'lib/lemon-ui/LemonButton/More' import { LemonTableColumn, LemonTableColumns } from 'lib/lemon-ui/LemonTable' import { createdByColumn } from 'lib/lemon-ui/LemonTable/columnUtils' -import { membersLogic } from 'scenes/organization/membersLogic' import { SavedSessionRecordingPlaylistsEmptyState } from 'scenes/session-recordings/saved-playlists/SavedSessionRecordingPlaylistsEmptyState' import { urls } from 'scenes/urls' @@ -40,7 +40,6 @@ export function SavedSessionRecordingPlaylists({ tab }: SavedSessionRecordingPla const logic = savedSessionRecordingPlaylistsLogic({ tab }) const { playlists, playlistsLoading, filters, sorting, pagination } = useValues(logic) const { setSavedPlaylistsFilters, updatePlaylist, duplicatePlaylist, deletePlaylist } = useActions(logic) - const { meFirstMembers } = useValues(membersLogic) const columns: LemonTableColumns = [ { @@ -159,20 +158,11 @@ export function SavedSessionRecordingPlaylists({ tab }: SavedSessionRecordingPla
Created by: - ({ - value: x.user.id, - label: x.user.first_name, - })), - ]} - value={filters.createdBy} - onChange={(v: any): void => { - setSavedPlaylistsFilters({ createdBy: v }) - }} - dropdownMatchSelectWidth={false} + type="secondary" + value={filters.createdBy === 'All users' ? null : filters.createdBy} + onChange={(user) => setSavedPlaylistsFilters({ createdBy: user?.id || 'All users' })} />