Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { cn } from "@plane/utils";
import { NotAuthorizedView } from "@/components/auth-screens/not-authorized-view";
import { CountChip } from "@/components/common/count-chip";
import { PageHead } from "@/components/core/page-title";
import { MemberListFiltersDropdown } from "@/components/project/dropdowns/filters/member-list";
import { SettingsContentWrapper } from "@/components/settings/content-wrapper";
import { WorkspaceMembersList } from "@/components/workspace/settings/members-list";
// helpers
Expand All @@ -41,7 +42,7 @@ const WorkspaceMembersSettingsPage = observer(() => {
// store hooks
const { workspaceUserInfo, allowPermissions } = useUserPermissions();
const {
workspace: { workspaceMemberIds, inviteMembersToWorkspace },
workspace: { workspaceMemberIds, inviteMembersToWorkspace, filtersStore },
} = useMember();
const { currentWorkspace } = useWorkspace();
const { t } = useTranslation();
Expand Down Expand Up @@ -88,8 +89,20 @@ const WorkspaceMembersSettingsPage = observer(() => {
});
};

// Handler for role filter updates
const handleRoleFilterUpdate = (role: string) => {
const currentFilters = filtersStore.filters;
const currentRoles = currentFilters?.roles || [];
const updatedRoles = currentRoles.includes(role) ? currentRoles.filter((r) => r !== role) : [...currentRoles, role];

filtersStore.updateFilters({
roles: updatedRoles.length > 0 ? updatedRoles : undefined,
});
};

// derived values
const pageTitle = currentWorkspace?.name ? `${currentWorkspace.name} - Members` : undefined;
const appliedRoleFilters = filtersStore.filters?.roles || [];

// if user is not authorized to view this page
if (workspaceUserInfo && !canPerformWorkspaceMemberActions) {
Expand All @@ -116,27 +129,34 @@ const WorkspaceMembersSettingsPage = observer(() => {
<CountChip count={workspaceMemberIds.length} className="h-5 m-auto" />
)}
</h4>
<div className="ml-auto flex items-center gap-1.5 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5">
<Search className="h-3.5 w-3.5 text-custom-text-400" />
<input
className="w-full max-w-[234px] border-none bg-transparent text-sm outline-none placeholder:text-custom-text-400"
placeholder={`${t("search")}...`}
value={searchQuery}
autoFocus
onChange={(e) => setSearchQuery(e.target.value)}
<div className="flex items-center gap-2">
<div className="flex items-center gap-1.5 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5">
<Search className="h-3.5 w-3.5 text-custom-text-400" />
<input
className="w-full max-w-[234px] border-none bg-transparent text-sm outline-none placeholder:text-custom-text-400"
placeholder={`${t("search")}...`}
value={searchQuery}
autoFocus
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
<MemberListFiltersDropdown
appliedFilters={appliedRoleFilters}
handleUpdate={handleRoleFilterUpdate}
memberType="workspace"
/>
{canPerformWorkspaceAdminActions && (
<Button
variant="primary"
size="sm"
onClick={() => setInviteModal(true)}
data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON}
>
{t("workspace_settings.settings.members.add_member")}
</Button>
)}
<BillingActionsButton canPerformWorkspaceAdminActions={canPerformWorkspaceAdminActions} />
</div>
{canPerformWorkspaceAdminActions && (
<Button
variant="primary"
size="sm"
onClick={() => setInviteModal(true)}
data-ph-element={MEMBER_TRACKER_ELEMENTS.HEADER_ADD_BUTTON}
>
{t("workspace_settings.settings.members.add_member")}
</Button>
)}
<BillingActionsButton canPerformWorkspaceAdminActions={canPerformWorkspaceAdminActions} />
</div>
<WorkspaceMembersList searchQuery={searchQuery} isAdmin={canPerformWorkspaceAdminActions} />
</section>
Expand Down
69 changes: 62 additions & 7 deletions apps/web/ce/components/projects/settings/useProjectColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import { useState } from "react";
// plane imports
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { IWorkspaceMember, TProjectMembership } from "@plane/types";
import { renderFormattedDate } from "@plane/utils";
// components
import { MemberHeaderColumn } from "@/components/project/member-header-column";
import { AccountTypeColumn, NameColumn } from "@/components/project/settings/member-columns";
// hooks
import { useMember } from "@/hooks/store/use-member";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { IMemberFilters } from "@/store/member/utils";

export interface RowData extends Pick<TProjectMembership, "original_role"> {
member: IWorkspaceMember;
Expand All @@ -20,9 +24,15 @@ export const useProjectColumns = (props: TUseProjectColumnsProps) => {
const { projectId, workspaceSlug } = props;
// states
const [removeMemberModal, setRemoveMemberModal] = useState<RowData | null>(null);

// store hooks
const { data: currentUser } = useUser();
const { allowPermissions, getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
const {
project: {
filters: { getFilters, updateFilters },
},
} = useMember();
// derived values
const isAdmin = allowPermissions(
[EUserPermissions.ADMIN],
Expand All @@ -33,18 +43,25 @@ export const useProjectColumns = (props: TUseProjectColumnsProps) => {
const currentProjectRole =
getProjectRoleByWorkspaceSlugAndProjectId(workspaceSlug.toString(), projectId.toString()) ?? EUserPermissions.GUEST;

const getFormattedDate = (dateStr: string) => {
const date = new Date(dateStr);
const displayFilters = getFilters(projectId);

const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric" };
return date.toLocaleDateString("en-US", options);
// handlers
const handleDisplayFilterUpdate = (filters: Partial<IMemberFilters>) => {
updateFilters(projectId, filters);
};

const columns = [
{
key: "Full Name",
content: "Full name",
thClassName: "text-left",
thRender: () => (
<MemberHeaderColumn
property="full_name"
displayFilters={displayFilters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
tdRender: (rowData: RowData) => (
<NameColumn
rowData={rowData}
Expand All @@ -58,12 +75,37 @@ export const useProjectColumns = (props: TUseProjectColumnsProps) => {
{
key: "Display Name",
content: "Display name",
thRender: () => (
<MemberHeaderColumn
property="display_name"
displayFilters={displayFilters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
tdRender: (rowData: RowData) => <div className="w-32">{rowData.member.display_name}</div>,
},

{
key: "Email",
content: "Email",
thRender: () => (
<MemberHeaderColumn
property="email"
displayFilters={displayFilters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
tdRender: (rowData: RowData) => <div className="w-48 text-custom-text-200">{rowData.member.email}</div>,
},
{
key: "Account Type",
content: "Account type",
thRender: () => (
<MemberHeaderColumn
property="role"
displayFilters={displayFilters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
tdRender: (rowData: RowData) => (
<AccountTypeColumn
rowData={rowData}
Expand All @@ -76,8 +118,21 @@ export const useProjectColumns = (props: TUseProjectColumnsProps) => {
{
key: "Joining Date",
content: "Joining date",
tdRender: (rowData: RowData) => <div>{getFormattedDate(rowData?.member?.joining_date || "")}</div>,
thRender: () => (
<MemberHeaderColumn
property="joining_date"
displayFilters={displayFilters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
tdRender: (rowData: RowData) => <div>{renderFormattedDate(rowData?.member?.joining_date)}</div>,
},
];
return { columns, removeMemberModal, setRemoveMemberModal };
return {
columns,
removeMemberModal,
setRemoveMemberModal,
displayFilters,
handleDisplayFilterUpdate,
};
};
79 changes: 66 additions & 13 deletions apps/web/ce/components/workspace/settings/useMemberColumns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import { useState } from "react";
import { useParams } from "next/navigation";
import { EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { renderFormattedDate } from "@plane/utils";
import { MemberHeaderColumn } from "@/components/project/member-header-column";
import { AccountTypeColumn, NameColumn, RowData } from "@/components/workspace/settings/member-columns";
import { useMember } from "@/hooks/store/use-member";
import { useUser, useUserPermissions } from "@/hooks/store/user";
import { IMemberFilters } from "@/store/member/utils";

export const useMemberColumns = () => {
// states
Expand All @@ -13,23 +17,34 @@ export const useMemberColumns = () => {

const { data: currentUser } = useUser();
const { allowPermissions } = useUserPermissions();
const {
workspace: {
filtersStore: { filters, updateFilters },
},
} = useMember();
const { t } = useTranslation();

const getFormattedDate = (dateStr: string) => {
const date = new Date(dateStr);

const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric" };
return date.toLocaleDateString("en-US", options);
};

// derived values
const isAdmin = allowPermissions([EUserPermissions.ADMIN], EUserPermissionsLevel.WORKSPACE);

const isSuspended = (rowData: RowData) => rowData.is_active === false;
// handlers
const handleDisplayFilterUpdate = (filterUpdates: Partial<IMemberFilters>) => {
updateFilters(filterUpdates);
};

const columns = [
{
key: "Full name",
content: t("workspace_settings.settings.members.details.full_name"),
thClassName: "text-left",
thRender: () => (
<MemberHeaderColumn
property="full_name"
displayFilters={filters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
tdRender: (rowData: RowData) => (
<NameColumn
rowData={rowData}
Expand All @@ -44,33 +59,71 @@ export const useMemberColumns = () => {
{
key: "Display name",
content: t("workspace_settings.settings.members.details.display_name"),
tdRender: (rowData: RowData) => <div className="w-32">{rowData.member.display_name}</div>,
tdRender: (rowData: RowData) => (
<div className={`w-32 ${isSuspended(rowData) ? "text-custom-text-400" : ""}`}>
{rowData.member.display_name}
</div>
),
thRender: () => (
<MemberHeaderColumn
property="display_name"
displayFilters={filters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
},

{
key: "Email address",
content: t("workspace_settings.settings.members.details.email_address"),
tdRender: (rowData: RowData) => <div className="w-48 truncate">{rowData.member.email}</div>,
tdRender: (rowData: RowData) => (
<div className={`w-48 truncate ${isSuspended(rowData) ? "text-custom-text-400" : ""}`}>
{rowData.member.email}
</div>
),
thRender: () => (
<MemberHeaderColumn
property="email"
displayFilters={filters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
},

{
key: "Account type",
content: t("workspace_settings.settings.members.details.account_type"),
thRender: () => (
<MemberHeaderColumn
property="role"
displayFilters={filters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
tdRender: (rowData: RowData) => <AccountTypeColumn rowData={rowData} workspaceSlug={workspaceSlug as string} />,
},

{
key: "Authentication",
content: t("workspace_settings.settings.members.details.authentication"),
tdRender: (rowData: RowData) => (
<div className="capitalize">{rowData.member.last_login_medium?.replace("-", " ")}</div>
),
tdRender: (rowData: RowData) =>
isSuspended(rowData) ? null : (
<div className="capitalize">{rowData.member.last_login_medium?.replace("-", " ")}</div>
),
},

{
key: "Joining date",
content: t("workspace_settings.settings.members.details.joining_date"),
tdRender: (rowData: RowData) => <div>{getFormattedDate(rowData?.member?.joining_date || "")}</div>,
tdRender: (rowData: RowData) =>
isSuspended(rowData) ? null : <div>{renderFormattedDate(rowData?.member?.joining_date)}</div>,
thRender: () => (
<MemberHeaderColumn
property="joining_date"
displayFilters={filters}
handleDisplayFilterUpdate={handleDisplayFilterUpdate}
/>
),
},
];
return { columns, workspaceSlug, removeMemberModal, setRemoveMemberModal };
Expand Down
2 changes: 1 addition & 1 deletion apps/web/ce/store/member/project-member.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { EUserProjectRoles } from "@plane/types";
import type { RootStore } from "@/plane-web/store/root.store";
// store
import type { IMemberRootStore } from "@/store/member";
import { BaseProjectMemberStore, IBaseProjectMemberStore } from "@/store/member/base-project-member.store";
import { BaseProjectMemberStore, IBaseProjectMemberStore } from "@/store/member/project/base-project-member.store";

export type IProjectMemberStore = IBaseProjectMemberStore;

Expand Down
Loading
Loading