feat(frontend): add project access ui and report summaries
This commit is contained in:
@@ -7,8 +7,9 @@ import { useAppContext } from "../context/AppContext";
|
||||
import { useWorkspace } from "../context/WorkspaceContext";
|
||||
import { ProjectCreateModal } from "../components/projects/ProjectCreateModal";
|
||||
import { ProjectEditModal } from "../components/projects/ProjectEditModal";
|
||||
import { ProjectAccessModal } from "../components/projects/ProjectAccessModal";
|
||||
import { Pagination } from "../components/Pagination";
|
||||
import { Plus, Archive, Building2, Pencil, Trash2, X } from "lucide-react";
|
||||
import { Plus, Archive, Building2, Pencil, ShieldCheck, Trash2, X } from "lucide-react";
|
||||
|
||||
import EmptyStateCard from "../components/EmptyStateCard";
|
||||
import FilterBar from "../components/FilterBar";
|
||||
@@ -47,6 +48,7 @@ export const Projects: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||
const [editingProject, setEditingProject] = useState<Project | null>(null);
|
||||
const [isAccessModalOpen, setIsAccessModalOpen] = useState(false);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const search = useMemo(() => readStringParam(searchParams, "search", ""), [searchParams]);
|
||||
@@ -231,6 +233,16 @@ export const Projects: React.FC = () => {
|
||||
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">{t.projects?.description(activeWorkspace.name) || 'Manage your projects'}</p>
|
||||
</div>
|
||||
<div className="flex w-full items-center gap-3 sm:w-auto">
|
||||
{canEditProject && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => setIsAccessModalOpen(true)}
|
||||
className="flex-1 gap-2 shadow-sm sm:flex-none"
|
||||
>
|
||||
<ShieldCheck className="h-4 w-4" />
|
||||
{t.projects?.manageAccess || "Manage access"}
|
||||
</Button>
|
||||
)}
|
||||
{canArchiveProject && (
|
||||
<Button
|
||||
variant={isArchived ? "default" : "secondary"}
|
||||
@@ -438,6 +450,42 @@ export const Projects: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{canEditProject && (
|
||||
<ProjectAccessModal
|
||||
isOpen={isAccessModalOpen}
|
||||
onClose={() => setIsAccessModalOpen(false)}
|
||||
workspaceId={activeWorkspace.id}
|
||||
onApplied={() => {
|
||||
void fetchProjectList();
|
||||
}}
|
||||
labels={{
|
||||
title: t.projects?.accessModalTitle || "Project access",
|
||||
description: t.projects?.accessModalDescription || "Grant or revoke project access for workspace members.",
|
||||
close: t.actions?.cancel || "Close",
|
||||
member: t.projects?.accessMemberLabel || "Member",
|
||||
loading: t.loading || "Loading...",
|
||||
noMembers: t.projects?.accessNoMembers || "No eligible members were found.",
|
||||
noProjects: t.projects?.accessNoProjects || "No projects found.",
|
||||
searchPlaceholder: t.projects?.searchPlaceholder || "Search projects...",
|
||||
allClients: t.reports?.allClients || "All clients",
|
||||
selectAllVisible: t.projects?.accessSelectVisible || "Select all visible",
|
||||
clearSelection: t.projects?.accessClearSelection || "Clear selection",
|
||||
selectClientProjects: t.projects?.accessSelectClientProjects || "Select all projects for client",
|
||||
grantSelected: t.projects?.accessGrant || "Grant selected",
|
||||
revokeSelected: t.projects?.accessRevoke || "Revoke selected",
|
||||
accessGranted: t.projects?.accessGrantSuccess || "Project access granted.",
|
||||
accessRevoked: t.projects?.accessRevokeSuccess || "Project access revoked.",
|
||||
memberRole: t.workspace?.roleLabel || "Role",
|
||||
client: t.projects?.clientLabel || "Client",
|
||||
noClient: t.projects?.noClient || "No client",
|
||||
accessOn: t.projects?.accessOn || "Has access",
|
||||
accessOff: t.projects?.accessOff || "No access",
|
||||
loadError: t.projects?.accessLoadError || "Failed to load project access state.",
|
||||
saveError: t.projects?.accessSaveError || "Failed to update project access.",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{deleteModal.project && (
|
||||
<Modal
|
||||
isOpen={deleteModal.isOpen}
|
||||
|
||||
@@ -398,6 +398,20 @@ export default function Reports() {
|
||||
details: t.reports?.details || "Details",
|
||||
total: t.reports?.total || "Total",
|
||||
name: t.reports?.name || "Name",
|
||||
mobile: t.reports?.mobile || "Mobile",
|
||||
hourlyRates: t.reports?.hourlyRates || "Hourly rates",
|
||||
workingHours: t.reports?.workingHours || "Working hours",
|
||||
nonWorkingHours: t.reports?.nonWorkingHours || "Non-working hours",
|
||||
projectPercentages: t.reports?.projectPercentages || "Project percentages",
|
||||
clientPercentages: t.reports?.clientPercentages || "Client percentages",
|
||||
tagPercentages: t.reports?.tagPercentages || "Tag percentages",
|
||||
userSummaryTitle: t.reports?.userSummaryTitle || "Summary by user",
|
||||
userSummaryDetailsTitle: t.reports?.userSummaryDetailsTitle || "User details: {name}",
|
||||
userSummaryDetailsDescription: t.reports?.userSummaryDetailsDescription || "Detailed rate history and distribution for the selected user.",
|
||||
rateHistory: t.reports?.rateHistory || "Rate history",
|
||||
fromDate: t.reports?.fromDate || "From",
|
||||
toDate: t.reports?.toDate || "To",
|
||||
noData: t.reports?.noData || "No data",
|
||||
clientsTable: t.reports?.clientsTable || "Clients",
|
||||
projectsTable: t.reports?.projectsTable || "Projects",
|
||||
tagsTable: t.reports?.tagsTable || "Tags",
|
||||
|
||||
Reference in New Issue
Block a user