feat(workspaces): expand detail page member list

This commit is contained in:
2026-04-28 10:46:15 +03:30
parent b1ad372474
commit 581cfab1ac
4 changed files with 292 additions and 248 deletions

View File

@@ -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;

View File

@@ -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`,
}, },
} }

View File

@@ -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}`,

View File

@@ -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>