All files / src/components/projects/tabs DetailsTab.tsx

94.59% Statements 35/37
90% Branches 27/30
78.57% Functions 11/14
93.54% Lines 29/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112                          6x             84x 84x   84x 34x 34x 34x   34x     84x 7x 7x 5x             84x 55x 14x       14x 8x 4x 8x     4x     18x     84x 34x 13x     84x 84x     84x         7x                       2x                                                    
import { useState, useMemo, useEffect } from "react";
import { FileText, Layers } from "lucide-react";
import { SectionCard } from "../../profile/SectionCard";
import { ReadOnlyField } from "../../profile/ReadOnlyField";
import { BasicInfoModal } from "../modals/BasicInfoModal";
import { ScopeAreasModal } from "../modals/ScopeAreasModal";
import { taxonomyApi, type Project, type ServiceCategory } from "../../../lib/api";
 
interface DetailsTabProps {
	project: Project;
	onSave: (data: Partial<Project>) => void | Promise<void>;
}
 
const SCOPE_LABELS: Record<string, string> = {
	design: "Design",
	material: "Material",
	execution: "Execution",
};
 
export function DetailsTab({ project, onSave }: DetailsTabProps) {
	const [editingSection, setEditingSection] = useState<"basic" | "scope" | null>(null);
	const [serviceCategories, setServiceCategories] = useState<ServiceCategory[]>([]);
 
	useEffect(() => {
		let cancelled = false;
		taxonomyApi.getServiceCategories().then((res) => {
			if (!cancelled) setServiceCategories(res.data || []);
		}).catch(() => {});
		return () => { cancelled = true; };
	}, []);
 
	const handleSave = async (data: Partial<Project>) => {
		try {
			await onSave(data);
			setEditingSection(null);
		} catch {
			// Error is handled by the parent component
		}
	};
 
	// Resolve worked area IDs to names
	const workedAreaNames = useMemo(() => {
		if (!project.workedAreaIds || project.workedAreaIds.length === 0) return null;
		const idToName: Record<string, string> = {
			full_home: "Full Home",
			full_office: "Full Office",
		};
		for (const cat of serviceCategories) {
			if (cat.children && cat.children.length > 0) {
				for (const child of cat.children) {
					idToName[child.id] = child.name;
				}
			} else {
				idToName[cat.id] = cat.name;
			}
		}
		return project.workedAreaIds.map((id) => idToName[id] || id);
	}, [project.workedAreaIds, serviceCategories]);
 
	const scopeLabels = useMemo(() => {
		if (!project.scope || project.scope.length === 0) return null;
		return (project.scope as string[]).map((s) => SCOPE_LABELS[s] || s);
	}, [project.scope]);
 
	const isBasicEmpty = !project.title && !project.description;
	const isScopeEmpty = (!project.scope || project.scope.length === 0) &&
		(!project.workedAreaIds || project.workedAreaIds.length === 0);
 
	return (
		<div className="space-y-6">
			<SectionCard
				title="Basic Info"
				icon={<FileText className="h-4 w-4" />}
				onEdit={() => setEditingSection("basic")}
				isEmpty={isBasicEmpty}
			>
				<dl className="grid grid-cols-1 gap-4">
					<ReadOnlyField label="Title" value={project.title} />
					<ReadOnlyField label="Description" value={project.description} />
				</dl>
			</SectionCard>
 
			<SectionCard
				title="Scope & Areas"
				icon={<Layers className="h-4 w-4" />}
				onEdit={() => setEditingSection("scope")}
				isEmpty={isScopeEmpty}
			>
				<dl className="grid grid-cols-1 sm:grid-cols-2 gap-4">
					<ReadOnlyField label="Scope of Work" value={scopeLabels} />
					<ReadOnlyField label="Worked Areas" value={workedAreaNames} />
				</dl>
			</SectionCard>
 
			{editingSection === "basic" && (
				<BasicInfoModal
					project={project}
					onSave={handleSave}
					onClose={() => setEditingSection(null)}
				/>
			)}
			{editingSection === "scope" && (
				<ScopeAreasModal
					project={project}
					onSave={handleSave}
					onClose={() => setEditingSection(null)}
				/>
			)}
		</div>
	);
}