feat(frontend): add project access ui and report summaries

This commit is contained in:
2026-05-14 17:06:34 +03:30
parent eaafb6c3b4
commit 84b7290fe8
8 changed files with 761 additions and 25 deletions

View File

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

View File

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