feat(workspaces): expand detail page member list
This commit is contained in:
@@ -19,13 +19,15 @@ export interface PaginatedResponse<T> {
|
|||||||
export interface WorkspaceMembership {
|
export interface WorkspaceMembership {
|
||||||
id: string;
|
id: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
user: {
|
user: {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email?: string;
|
||||||
first_name?: string;
|
first_name?: string;
|
||||||
last_name?: string;
|
last_name?: string;
|
||||||
[key: string]: any;
|
mobile?: string;
|
||||||
};
|
profile_picture?: string | null;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
role: 'owner' | 'admin' | 'member' | 'guest';
|
role: 'owner' | 'admin' | 'member' | 'guest';
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
joined_at?: string;
|
joined_at?: string;
|
||||||
|
|||||||
@@ -151,30 +151,31 @@ export const en = {
|
|||||||
emptyState: "You are not a member of any workspace.",
|
emptyState: "You are not a member of any workspace.",
|
||||||
createTitle: "Create Workspace",
|
createTitle: "Create Workspace",
|
||||||
editTitle: "Edit Workspace",
|
editTitle: "Edit Workspace",
|
||||||
detailTitle: "Workspace Details",
|
detailTitle: "Workspace Details",
|
||||||
save: "Save",
|
save: "Save",
|
||||||
create: "Create",
|
create: "Create",
|
||||||
noWorkspaceTitle: "Welcome!",
|
noWorkspaceTitle: "Welcome!",
|
||||||
noWorkspaceDesc: "Please create your first workspace.",
|
noWorkspaceDesc: "Please create your first workspace.",
|
||||||
back: "Back to Workspaces",
|
back: "Back to Workspaces",
|
||||||
roleLabel: "Your Role",
|
roleLabel: "Your Role",
|
||||||
openReports: "Open reports",
|
openReports: "Open reports",
|
||||||
statsMembers: "Members",
|
statsMembers: "Members",
|
||||||
statsRates: "Rates set",
|
statsRates: "Rates set",
|
||||||
statsOwnersAdmins: "Owners & admins",
|
statsOwnersAdmins: "Owners & admins",
|
||||||
statsGuests: "Guests",
|
statsGuests: "Guests",
|
||||||
membersSectionTitle: "Members",
|
membersSectionTitle: "Members",
|
||||||
membersSectionSubtitle: "People in this workspace and their current roles.",
|
membersSectionSubtitle: "People in this workspace and their current roles.",
|
||||||
membersLocked: "Only owners and admins can view the full member list.",
|
membersLocked: "Only owners and admins can view the full member list.",
|
||||||
manageMembers: "Manage members",
|
manageMembers: "Manage members",
|
||||||
joinedLabel: "Joined",
|
mobileNumber: "Mobile Number",
|
||||||
|
youLabel: "You",
|
||||||
resourcesTitle: "Resources",
|
resourcesTitle: "Resources",
|
||||||
resourceOpen: "Open",
|
resourceOpen: "Open",
|
||||||
roleDistributionTitle: "Role distribution",
|
roleDistributionTitle: "Role distribution",
|
||||||
unknownMember: "Unknown member",
|
unknownMember: "Unknown member",
|
||||||
roles: {
|
roles: {
|
||||||
owner: "Owner",
|
owner: "Owner",
|
||||||
admin: "Admin",
|
admin: "Admin",
|
||||||
member: "Member",
|
member: "Member",
|
||||||
guest: "Guest",
|
guest: "Guest",
|
||||||
},
|
},
|
||||||
@@ -258,16 +259,16 @@ export const en = {
|
|||||||
next: "Next",
|
next: "Next",
|
||||||
},
|
},
|
||||||
|
|
||||||
sidebar: {
|
sidebar: {
|
||||||
timesheet: "Timesheet",
|
timesheet: "Timesheet",
|
||||||
reports: "Reports",
|
reports: "Reports",
|
||||||
workspaces: 'Workspaces',
|
workspaces: 'Workspaces',
|
||||||
clients: 'Clients',
|
clients: 'Clients',
|
||||||
projects: "Projects",
|
projects: "Projects",
|
||||||
tags: "Tags",
|
tags: "Tags",
|
||||||
expand: 'Expand',
|
expand: 'Expand',
|
||||||
collapse: 'Collapse',
|
collapse: 'Collapse',
|
||||||
},
|
},
|
||||||
|
|
||||||
ordering: {
|
ordering: {
|
||||||
createdAtDesc: "Newest First",
|
createdAtDesc: "Newest First",
|
||||||
@@ -277,7 +278,7 @@ export const en = {
|
|||||||
nameDesc: "Name (Z-A)",
|
nameDesc: "Name (Z-A)",
|
||||||
},
|
},
|
||||||
|
|
||||||
projects: {
|
projects: {
|
||||||
title: "Projects",
|
title: "Projects",
|
||||||
description: (workspaceName: string) => `Manage projects for ${workspaceName}`,
|
description: (workspaceName: string) => `Manage projects for ${workspaceName}`,
|
||||||
active: "Active Projects",
|
active: "Active Projects",
|
||||||
@@ -314,12 +315,12 @@ export const en = {
|
|||||||
addAllWorkspaceMembers: "Add all workspace members",
|
addAllWorkspaceMembers: "Add all workspace members",
|
||||||
confirmDeleteTitle: "Remove Member",
|
confirmDeleteTitle: "Remove Member",
|
||||||
confirmDeleteDesc: "Are you sure you want to remove this member from the project?",
|
confirmDeleteDesc: "Are you sure you want to remove this member from the project?",
|
||||||
createSuccess: "Project created successfully.",
|
createSuccess: "Project created successfully.",
|
||||||
createError: "Failed to create project.",
|
createError: "Failed to create project.",
|
||||||
updateSuccess: "Project updated successfully.",
|
updateSuccess: "Project updated successfully.",
|
||||||
updateError: "Failed to update project.",
|
updateError: "Failed to update project.",
|
||||||
edit: "Edit Project",
|
edit: "Edit Project",
|
||||||
memberAlreadyAdded: "This user is already on the project team.",
|
memberAlreadyAdded: "This user is already on the project team.",
|
||||||
roles: {
|
roles: {
|
||||||
member: "Member",
|
member: "Member",
|
||||||
manager: "Manager"
|
manager: "Manager"
|
||||||
@@ -330,173 +331,173 @@ export const en = {
|
|||||||
userNotFound: "No user found with this mobile number.",
|
userNotFound: "No user found with this mobile number.",
|
||||||
alreadyInProject: "Already Added",
|
alreadyInProject: "Already Added",
|
||||||
addToProject: "Add to Project",
|
addToProject: "Add to Project",
|
||||||
noWorkspaceMembers: "No members found.",
|
noWorkspaceMembers: "No members found.",
|
||||||
},
|
},
|
||||||
|
|
||||||
tags: {
|
tags: {
|
||||||
title: "Tags",
|
title: "Tags",
|
||||||
description: (workspaceName: string) => `Manage tags for ${workspaceName}`,
|
description: (workspaceName: string) => `Manage tags for ${workspaceName}`,
|
||||||
create: "Create Tag",
|
create: "Create Tag",
|
||||||
createTitle: "Create Tag",
|
createTitle: "Create Tag",
|
||||||
editTitle: "Edit Tag",
|
editTitle: "Edit Tag",
|
||||||
deleteTitle: "Delete Tag",
|
deleteTitle: "Delete Tag",
|
||||||
deleteConfirmMessage: (name: string) => `Are you sure you want to delete ${name}?`,
|
deleteConfirmMessage: (name: string) => `Are you sure you want to delete ${name}?`,
|
||||||
searchPlaceholder: "Search tags...",
|
searchPlaceholder: "Search tags...",
|
||||||
nameLabel: "Tag Name",
|
nameLabel: "Tag Name",
|
||||||
namePlaceholder: "e.g. Design",
|
namePlaceholder: "e.g. Design",
|
||||||
colorLabel: "Color",
|
colorLabel: "Color",
|
||||||
emptyState: "No tags found",
|
emptyState: "No tags found",
|
||||||
selectWorkspace: "Please select a workspace first.",
|
selectWorkspace: "Please select a workspace first.",
|
||||||
fetchError: "Failed to load tags",
|
fetchError: "Failed to load tags",
|
||||||
createSuccess: "Tag created successfully.",
|
createSuccess: "Tag created successfully.",
|
||||||
updateSuccess: "Tag updated successfully.",
|
updateSuccess: "Tag updated successfully.",
|
||||||
saveError: "Failed to save tag.",
|
saveError: "Failed to save tag.",
|
||||||
deleteSuccess: "Tag deleted successfully.",
|
deleteSuccess: "Tag deleted successfully.",
|
||||||
deleteError: "Failed to delete tag.",
|
deleteError: "Failed to delete tag.",
|
||||||
},
|
},
|
||||||
|
|
||||||
rates: {
|
rates: {
|
||||||
workspaceSectionTitle: "Workspace User Rates",
|
workspaceSectionTitle: "Workspace User Rates",
|
||||||
projectSectionTitle: "Project User Rates",
|
projectSectionTitle: "Project User Rates",
|
||||||
workspaceRate: "Workspace rate",
|
workspaceRate: "Workspace rate",
|
||||||
projectOverride: "Project override",
|
projectOverride: "Project override",
|
||||||
inheritsWorkspaceRate: "Inherits workspace rate",
|
inheritsWorkspaceRate: "Inherits workspace rate",
|
||||||
noRate: "No rate",
|
noRate: "No rate",
|
||||||
hourlyRatePlaceholder: "0.00",
|
hourlyRatePlaceholder: "0.00",
|
||||||
currencyPlaceholder: "USD",
|
currencyPlaceholder: "USD",
|
||||||
searchUnitPlaceholder: "Search unit...",
|
searchUnitPlaceholder: "Search unit...",
|
||||||
removeRate: "Remove rate",
|
removeRate: "Remove rate",
|
||||||
workspaceSaveSuccess: "Workspace user rate saved.",
|
workspaceSaveSuccess: "Workspace user rate saved.",
|
||||||
workspaceSaveError: "Failed to save workspace user rate.",
|
workspaceSaveError: "Failed to save workspace user rate.",
|
||||||
workspaceRemoveSuccess: "Workspace user rate removed.",
|
workspaceRemoveSuccess: "Workspace user rate removed.",
|
||||||
workspaceRemoveError: "Failed to remove workspace user rate.",
|
workspaceRemoveError: "Failed to remove workspace user rate.",
|
||||||
projectSaveSuccess: "Project user rate saved.",
|
projectSaveSuccess: "Project user rate saved.",
|
||||||
projectSaveError: "Failed to save project user rate.",
|
projectSaveError: "Failed to save project user rate.",
|
||||||
projectRemoveSuccess: "Project user rate removed.",
|
projectRemoveSuccess: "Project user rate removed.",
|
||||||
projectRemoveError: "Failed to remove project user rate.",
|
projectRemoveError: "Failed to remove project user rate.",
|
||||||
},
|
},
|
||||||
|
|
||||||
timesheet: {
|
timesheet: {
|
||||||
title: "Timesheet",
|
title: "Timesheet",
|
||||||
description: (workspaceName: string) => `Track time inside ${workspaceName}`,
|
description: (workspaceName: string) => `Track time inside ${workspaceName}`,
|
||||||
selectWorkspace: "Please select a workspace first.",
|
selectWorkspace: "Please select a workspace first.",
|
||||||
addEntry: "Add Entry",
|
addEntry: "Add Entry",
|
||||||
startTimer: "Start Timer",
|
startTimer: "Start Timer",
|
||||||
stopTimer: "Stop Timer",
|
stopTimer: "Stop Timer",
|
||||||
timerRunning: "Timer Running",
|
timerRunning: "Timer Running",
|
||||||
runningLabel: "Current timer",
|
runningLabel: "Current timer",
|
||||||
runningBadge: "Running",
|
runningBadge: "Running",
|
||||||
noRunningEntry: "No running entry",
|
noRunningEntry: "No running entry",
|
||||||
searchPlaceholder: "Search time entries...",
|
searchPlaceholder: "Search time entries...",
|
||||||
orderingNewest: "Newest first",
|
orderingNewest: "Newest first",
|
||||||
orderingOldest: "Oldest first",
|
orderingOldest: "Oldest first",
|
||||||
emptyState: "No time entries found",
|
emptyState: "No time entries found",
|
||||||
emptyDescription: "No description",
|
emptyDescription: "No description",
|
||||||
createTitle: "Add Time Entry",
|
createTitle: "Add Time Entry",
|
||||||
startTitle: "Start Timer",
|
startTitle: "Start Timer",
|
||||||
editTitle: "Edit Time Entry",
|
editTitle: "Edit Time Entry",
|
||||||
createSuccess: "Time entry created successfully.",
|
createSuccess: "Time entry created successfully.",
|
||||||
startSuccess: "Timer started successfully.",
|
startSuccess: "Timer started successfully.",
|
||||||
updateSuccess: "Time entry updated successfully.",
|
updateSuccess: "Time entry updated successfully.",
|
||||||
saveError: "Failed to save time entry.",
|
saveError: "Failed to save time entry.",
|
||||||
stopSuccess: "Timer stopped successfully.",
|
stopSuccess: "Timer stopped successfully.",
|
||||||
stopError: "Failed to stop timer.",
|
stopError: "Failed to stop timer.",
|
||||||
deleteSuccess: "Time entry deleted successfully.",
|
deleteSuccess: "Time entry deleted successfully.",
|
||||||
deleteError: "Failed to delete time entry.",
|
deleteError: "Failed to delete time entry.",
|
||||||
fetchError: "Failed to load time entries.",
|
fetchError: "Failed to load time entries.",
|
||||||
optionsError: "Failed to load projects and tags.",
|
optionsError: "Failed to load projects and tags.",
|
||||||
descriptionLabel: "Description",
|
descriptionLabel: "Description",
|
||||||
descriptionPlaceholder: "What are you working on?",
|
descriptionPlaceholder: "What are you working on?",
|
||||||
projectLabel: "Project",
|
projectLabel: "Project",
|
||||||
noProject: "No project",
|
noProject: "No project",
|
||||||
startLabel: "Start",
|
startLabel: "Start",
|
||||||
endLabel: "End",
|
endLabel: "End",
|
||||||
billable: "Billable",
|
billable: "Billable",
|
||||||
noTagsHint: "Create tags first from the Tags page.",
|
noTagsHint: "Create tags first from the Tags page.",
|
||||||
clearFilters: "Clear filters",
|
clearFilters: "Clear filters",
|
||||||
customFromLabel: "From date",
|
customFromLabel: "From date",
|
||||||
customToLabel: "To date",
|
customToLabel: "To date",
|
||||||
allClientsLabel: "All clients",
|
allClientsLabel: "All clients",
|
||||||
allProjectsLabel: "All projects",
|
allProjectsLabel: "All projects",
|
||||||
allTagsLabel: "All tags",
|
allTagsLabel: "All tags",
|
||||||
showFiltersLabel: "Show filters",
|
showFiltersLabel: "Show filters",
|
||||||
hideFiltersLabel: "Hide filters",
|
hideFiltersLabel: "Hide filters",
|
||||||
applyFiltersLabel: "Apply",
|
applyFiltersLabel: "Apply",
|
||||||
clientFilterPrefix: "Client",
|
clientFilterPrefix: "Client",
|
||||||
projectFilterPrefix: "Project",
|
projectFilterPrefix: "Project",
|
||||||
tagFilterPrefix: "Tag",
|
tagFilterPrefix: "Tag",
|
||||||
fromFilterPrefix: "From",
|
fromFilterPrefix: "From",
|
||||||
toFilterPrefix: "To",
|
toFilterPrefix: "To",
|
||||||
deletedProjectLabel: "Deleted project",
|
deletedProjectLabel: "Deleted project",
|
||||||
deletedTagLabel: "Deleted tag",
|
deletedTagLabel: "Deleted tag",
|
||||||
},
|
},
|
||||||
|
|
||||||
reports: {
|
reports: {
|
||||||
title: "Reports",
|
title: "Reports",
|
||||||
description: (workspaceName: string) => `Review activity reports for ${workspaceName}`,
|
description: (workspaceName: string) => `Review activity reports for ${workspaceName}`,
|
||||||
selectWorkspace: "Please select a workspace first.",
|
selectWorkspace: "Please select a workspace first.",
|
||||||
chartTab: "Chart",
|
chartTab: "Chart",
|
||||||
tableTab: "Table",
|
tableTab: "Table",
|
||||||
period: "Period",
|
period: "Period",
|
||||||
periodThisWeek: "This week",
|
periodThisWeek: "This week",
|
||||||
periodThisMonth: "This month",
|
periodThisMonth: "This month",
|
||||||
periodThisYear: "This year",
|
periodThisYear: "This year",
|
||||||
periodFirstHalf: "First half of year",
|
periodFirstHalf: "First half of year",
|
||||||
periodSecondHalf: "Second half of year",
|
periodSecondHalf: "Second half of year",
|
||||||
periodCustom: "Custom period",
|
periodCustom: "Custom period",
|
||||||
fromDate: "From date",
|
fromDate: "From date",
|
||||||
toDate: "To date",
|
toDate: "To date",
|
||||||
user: "User",
|
user: "User",
|
||||||
allUsers: "All users",
|
allUsers: "All users",
|
||||||
searchUsers: "Search users...",
|
searchUsers: "Search users...",
|
||||||
client: "Client",
|
client: "Client",
|
||||||
allClients: "All clients",
|
allClients: "All clients",
|
||||||
searchClients: "Search clients...",
|
searchClients: "Search clients...",
|
||||||
project: "Project",
|
project: "Project",
|
||||||
allProjects: "All projects",
|
allProjects: "All projects",
|
||||||
searchProjects: "Search projects...",
|
searchProjects: "Search projects...",
|
||||||
tags: "Tags",
|
tags: "Tags",
|
||||||
allTags: "All tags",
|
allTags: "All tags",
|
||||||
searchTags: "Search tags...",
|
searchTags: "Search tags...",
|
||||||
name: "Name",
|
name: "Name",
|
||||||
clear: "Clear",
|
clear: "Clear",
|
||||||
apply: "Apply",
|
apply: "Apply",
|
||||||
totalHours: "Total hours",
|
totalHours: "Total hours",
|
||||||
billableHours: "Billable hours",
|
billableHours: "Billable hours",
|
||||||
nonBillableHours: "Non-billable hours",
|
nonBillableHours: "Non-billable hours",
|
||||||
totalIncome: "Total income",
|
totalIncome: "Total income",
|
||||||
chartTitle: "Activity chart",
|
chartTitle: "Activity chart",
|
||||||
totalSeconds: "Total seconds",
|
totalSeconds: "Total seconds",
|
||||||
exportExcel: "Export Excel",
|
exportExcel: "Export Excel",
|
||||||
exportPdf: "Export PDF",
|
exportPdf: "Export PDF",
|
||||||
date: "Date",
|
date: "Date",
|
||||||
details: "Details",
|
details: "Details",
|
||||||
total: "Total",
|
total: "Total",
|
||||||
clientsTable: "Clients",
|
clientsTable: "Clients",
|
||||||
projectsTable: "Projects",
|
projectsTable: "Projects",
|
||||||
tagsTable: "Tags",
|
tagsTable: "Tags",
|
||||||
loadError: "Failed to load reports.",
|
loadError: "Failed to load reports.",
|
||||||
loadDayDetailsError: "Failed to load day details.",
|
loadDayDetailsError: "Failed to load day details.",
|
||||||
loadFiltersError: "Failed to load report filters.",
|
loadFiltersError: "Failed to load report filters.",
|
||||||
exportQueued: "Export queued. You will receive a notification with the download link.",
|
exportQueued: "Export queued. You will receive a notification with the download link.",
|
||||||
exportError: "Failed to queue report export.",
|
exportError: "Failed to queue report export.",
|
||||||
},
|
},
|
||||||
|
|
||||||
notifications: {
|
notifications: {
|
||||||
title: "Notifications",
|
title: "Notifications",
|
||||||
open: "Open notifications",
|
open: "Open notifications",
|
||||||
empty: "No notifications yet.",
|
empty: "No notifications yet.",
|
||||||
loading: "Loading notifications...",
|
loading: "Loading notifications...",
|
||||||
loadingMore: "Loading more...",
|
loadingMore: "Loading more...",
|
||||||
loadMore: "Load more",
|
loadMore: "Load more",
|
||||||
markAllRead: "Mark all as read",
|
markAllRead: "Mark all as read",
|
||||||
markSeenError: "Failed to update notification",
|
markSeenError: "Failed to update notification",
|
||||||
markAllError: "Failed to update notifications",
|
markAllError: "Failed to update notifications",
|
||||||
deleteError: "Failed to delete notification",
|
deleteError: "Failed to delete notification",
|
||||||
loadError: "Failed to load notifications",
|
loadError: "Failed to load notifications",
|
||||||
openError: "Failed to open notification",
|
openError: "Failed to open notification",
|
||||||
newTitle: "New notification",
|
newTitle: "New notification",
|
||||||
openAction: "Open",
|
openAction: "Open",
|
||||||
summary: (total: number, unread: number) => `${total} total, ${unread} unread`,
|
summary: (total: number, unread: number) => `${total} total, ${unread} unread`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,11 +165,12 @@ export const fa = {
|
|||||||
statsOwnersAdmins: "مالکان و ادمینها",
|
statsOwnersAdmins: "مالکان و ادمینها",
|
||||||
statsGuests: "مهمانها",
|
statsGuests: "مهمانها",
|
||||||
membersSectionTitle: "اعضا",
|
membersSectionTitle: "اعضا",
|
||||||
membersSectionSubtitle: "اعضای این ورکاسپیس و نقش فعلی آنها.",
|
membersSectionSubtitle: "اعضای این ورکاسپیس و نقش فعلی آنها.",
|
||||||
membersLocked: "فهرست کامل اعضا فقط برای مالک و ادمین قابل مشاهده است.",
|
membersLocked: "فهرست کامل اعضا فقط برای مالک و ادمین قابل مشاهده است.",
|
||||||
manageMembers: "مدیریت اعضا",
|
manageMembers: "مدیریت اعضا",
|
||||||
joinedLabel: "زمان عضویت",
|
mobileNumber: "شماره تماس",
|
||||||
resourcesTitle: "منابع",
|
youLabel: "شما",
|
||||||
|
resourcesTitle: "منابع",
|
||||||
resourceOpen: "مشاهده",
|
resourceOpen: "مشاهده",
|
||||||
roleDistributionTitle: "توزیع نقشها",
|
roleDistributionTitle: "توزیع نقشها",
|
||||||
unknownMember: "عضو ناشناس",
|
unknownMember: "عضو ناشناس",
|
||||||
@@ -421,12 +422,12 @@ export const fa = {
|
|||||||
applyFiltersLabel: "اعمال",
|
applyFiltersLabel: "اعمال",
|
||||||
clientFilterPrefix: "مشتری",
|
clientFilterPrefix: "مشتری",
|
||||||
projectFilterPrefix: "پروژه",
|
projectFilterPrefix: "پروژه",
|
||||||
tagFilterPrefix: "تگ",
|
tagFilterPrefix: "تگ",
|
||||||
fromFilterPrefix: "از",
|
fromFilterPrefix: "از",
|
||||||
toFilterPrefix: "تا",
|
toFilterPrefix: "تا",
|
||||||
deletedProjectLabel: "پروژه حذفشده",
|
deletedProjectLabel: "پروژه حذفشده",
|
||||||
deletedTagLabel: "تگ حذفشده",
|
deletedTagLabel: "تگ حذفشده",
|
||||||
},
|
},
|
||||||
reports: {
|
reports: {
|
||||||
title: "گزارشها",
|
title: "گزارشها",
|
||||||
description: (workspaceName: string) => `مرور گزارش فعالیت برای ${workspaceName}`,
|
description: (workspaceName: string) => `مرور گزارش فعالیت برای ${workspaceName}`,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
type Workspace,
|
type Workspace,
|
||||||
type WorkspaceMembership,
|
type WorkspaceMembership,
|
||||||
} from '../api/workspaces';
|
} from '../api/workspaces';
|
||||||
|
import { useAppContext } from '../context/AppContext';
|
||||||
import { useWorkspace } from '../context/WorkspaceContext';
|
import { useWorkspace } from '../context/WorkspaceContext';
|
||||||
import { useTranslation } from '../hooks/useTranslation';
|
import { useTranslation } from '../hooks/useTranslation';
|
||||||
import {
|
import {
|
||||||
@@ -53,6 +54,7 @@ export default function WorkspaceDetail() {
|
|||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t, lang } = useTranslation();
|
const { t, lang } = useTranslation();
|
||||||
|
const { user } = useAppContext();
|
||||||
const { setActiveWorkspace } = useWorkspace();
|
const { setActiveWorkspace } = useWorkspace();
|
||||||
|
|
||||||
const [workspace, setWorkspace] = useState<Workspace | null>(null);
|
const [workspace, setWorkspace] = useState<Workspace | null>(null);
|
||||||
@@ -74,7 +76,7 @@ export default function WorkspaceDetail() {
|
|||||||
const data = await getWorkspace(id!);
|
const data = await getWorkspace(id!);
|
||||||
setWorkspace(data);
|
setWorkspace(data);
|
||||||
|
|
||||||
const canViewMembers = canWorkspace(data.my_role, WORKSPACE_MEMBERS_VIEW);
|
const canViewMembers = canWorkspace(data.my_role, WORKSPACE_VIEW);
|
||||||
const canViewClients = canWorkspace(data.my_role, CLIENTS_VIEW);
|
const canViewClients = canWorkspace(data.my_role, CLIENTS_VIEW);
|
||||||
const canViewProjects = canWorkspace(data.my_role, PROJECTS_VIEW);
|
const canViewProjects = canWorkspace(data.my_role, PROJECTS_VIEW);
|
||||||
const canViewTags = canWorkspace(data.my_role, TAGS_VIEW);
|
const canViewTags = canWorkspace(data.my_role, TAGS_VIEW);
|
||||||
@@ -180,6 +182,12 @@ export default function WorkspaceDetail() {
|
|||||||
return member.user?.mobile || member.user?.email || '-';
|
return member.user?.mobile || member.user?.email || '-';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getMemberInitials = (member: WorkspaceMembership) => {
|
||||||
|
const firstName = member.user?.first_name?.trim()?.charAt(0) || '';
|
||||||
|
const lastName = member.user?.last_name?.trim()?.charAt(0) || '';
|
||||||
|
return `${firstName}${lastName}`.trim().toUpperCase() || getMemberName(member).charAt(0).toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
const formatRateUnit = (rate?: WorkspaceUserRate) => {
|
const formatRateUnit = (rate?: WorkspaceUserRate) => {
|
||||||
if (!rate) return t.rates?.noRate || 'No rate';
|
if (!rate) return t.rates?.noRate || 'No rate';
|
||||||
const unitLabel =
|
const unitLabel =
|
||||||
@@ -192,7 +200,8 @@ export default function WorkspaceDetail() {
|
|||||||
const workspaceRole = workspace?.my_role;
|
const workspaceRole = workspace?.my_role;
|
||||||
const canEdit = canWorkspace(workspaceRole, WORKSPACE_EDIT);
|
const canEdit = canWorkspace(workspaceRole, WORKSPACE_EDIT);
|
||||||
const canDelete = canWorkspace(workspaceRole, WORKSPACE_DELETE);
|
const canDelete = canWorkspace(workspaceRole, WORKSPACE_DELETE);
|
||||||
const canViewMembers = canWorkspace(workspaceRole, WORKSPACE_MEMBERS_VIEW);
|
const canViewMembers = canWorkspace(workspaceRole, WORKSPACE_VIEW);
|
||||||
|
const canViewMemberSensitiveDetails = canWorkspace(workspaceRole, WORKSPACE_MEMBERS_VIEW);
|
||||||
const canViewReports = canWorkspace(workspaceRole, WORKSPACE_VIEW);
|
const canViewReports = canWorkspace(workspaceRole, WORKSPACE_VIEW);
|
||||||
|
|
||||||
if (isLoading || !workspace) {
|
if (isLoading || !workspace) {
|
||||||
@@ -368,11 +377,11 @@ export default function WorkspaceDetail() {
|
|||||||
<h2 className="text-lg font-semibold text-slate-900 dark:text-white">
|
<h2 className="text-lg font-semibold text-slate-900 dark:text-white">
|
||||||
{t.workspace?.membersSectionTitle || t.workspace?.members || 'Members'}
|
{t.workspace?.membersSectionTitle || t.workspace?.members || 'Members'}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
|
{canViewMembers
|
||||||
{canViewMembers
|
? <p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
|
||||||
? t.workspace?.membersSectionSubtitle || 'People in this workspace and their current roles.'
|
{ t.workspace?.membersSectionSubtitle || 'People in this workspace and their current roles.' }
|
||||||
: t.workspace?.membersLocked || 'Only owners and admins can view the full member list.'}
|
</p>
|
||||||
</p>
|
: ''}
|
||||||
</div>
|
</div>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
<button
|
<button
|
||||||
@@ -394,34 +403,65 @@ export default function WorkspaceDetail() {
|
|||||||
) : (
|
) : (
|
||||||
activeMembers.map((member) => {
|
activeMembers.map((member) => {
|
||||||
const rate = memberRateMap.get(member.user.id);
|
const rate = memberRateMap.get(member.user.id);
|
||||||
|
const isCurrentUser = member.user.id === user?.id;
|
||||||
return (
|
return (
|
||||||
<div key={member.id} className="flex flex-col gap-4 p-5 sm:p-6">
|
<div
|
||||||
|
key={member.id}
|
||||||
|
className={`flex flex-col gap-4 p-5 sm:p-6 ${
|
||||||
|
isCurrentUser
|
||||||
|
? 'bg-blue-50/80 ring-1 ring-blue-200 dark:bg-blue-950/20 dark:ring-blue-900/60'
|
||||||
|
: ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-full bg-slate-200 text-sm font-semibold text-slate-600 shadow-sm dark:bg-slate-800 dark:text-slate-300">
|
||||||
|
{member.user?.profile_picture ? (
|
||||||
|
<img
|
||||||
|
src={member.user.profile_picture}
|
||||||
|
alt={getMemberName(member)}
|
||||||
|
className="h-full w-full object-cover"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span>{getMemberInitials(member)}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
<div className="mb-2 flex flex-wrap items-center gap-2">
|
<div className="mb-2 flex flex-wrap items-center gap-2">
|
||||||
<h3 className="text-base font-semibold text-slate-900 dark:text-white">
|
<h3 className="text-base font-semibold text-slate-900 dark:text-white">
|
||||||
{getMemberName(member)}
|
{getMemberName(member)}
|
||||||
</h3>
|
</h3>
|
||||||
|
{isCurrentUser && (
|
||||||
|
<span className="inline-flex rounded-full border border-blue-200 bg-blue-100 px-2.5 py-1 text-xs font-semibold text-blue-700 dark:border-blue-900/60 dark:bg-blue-900/30 dark:text-blue-300">
|
||||||
|
{t.workspace?.youLabel || 'You'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
<span className={`inline-flex rounded-full px-2.5 py-1 text-xs font-semibold ${roleBadgeStyles[member.role]}`}>
|
<span className={`inline-flex rounded-full px-2.5 py-1 text-xs font-semibold ${roleBadgeStyles[member.role]}`}>
|
||||||
{t.workspace?.roles[member.role]}
|
{t.workspace?.roles[member.role]}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1 text-sm text-slate-500 dark:text-slate-400">
|
{canViewMemberSensitiveDetails && (
|
||||||
<p>{getMemberContact(member)}</p>
|
<div className="space-y-1 text-sm text-slate-500 dark:text-slate-400">
|
||||||
<p>
|
<p>
|
||||||
{t.workspace?.joinedLabel || 'Joined'}: {formatDate(member.joined_at)}
|
{t.workspace?.mobileNumber || 'Mobile Number'}: {getMemberContact(member)}
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 dark:border-slate-700 dark:bg-slate-800/80 sm:min-w-[210px]">
|
{canViewMemberSensitiveDetails && (
|
||||||
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
|
<div className="rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3 dark:border-slate-700 dark:bg-slate-800/80 sm:min-w-[210px]">
|
||||||
{t.rates?.workspaceRate || 'Workspace rate'}
|
<p className="mb-1 text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400">
|
||||||
</p>
|
{t.rates?.workspaceRate || 'Workspace rate'}
|
||||||
<p className="text-sm font-semibold text-slate-900 dark:text-white">
|
</p>
|
||||||
{formatRateUnit(rate)}
|
<p className="text-sm font-semibold text-slate-900 dark:text-white">
|
||||||
</p>
|
{formatRateUnit(rate)}
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -430,7 +470,7 @@ export default function WorkspaceDetail() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-6 text-sm text-slate-500 dark:text-slate-400">
|
<div className="p-6 text-sm text-slate-500 dark:text-slate-400">
|
||||||
{t.workspace?.membersLocked || 'Only owners and admins can view the full member list.'}
|
{t.workspace?.membersLocked || 'This member list is not available for your current role.'}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user