feat: Add translation support for map-related components and improve loading messages

This commit is contained in:
2025-09-16 16:32:04 +02:00
parent cf8ff874da
commit 029b091b10
2 changed files with 118 additions and 46 deletions

View File

@@ -4,22 +4,15 @@ import React, { useEffect, useState, Suspense } from "react";
import Link from "next/link"; import Link from "next/link";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
import { useSearchParams, useRouter } from "next/navigation"; import { useSearchParams, useRouter } from "next/navigation";
import { useTranslation } from "@/lib/i18n";
import Button from "@/components/ui/Button"; import Button from "@/components/ui/Button";
import { mapLayers } from "@/components/ui/mapLayers"; import { mapLayers } from "@/components/ui/mapLayers";
import { formatProjectStatus } from "@/lib/utils";
// Dynamically import the map component to avoid SSR issues
const DynamicMap = dynamic(() => import("@/components/ui/LeafletMap"), {
ssr: false,
loading: () => (
<div className="w-full h-96 bg-gray-100 animate-pulse rounded-lg flex items-center justify-center">
<span className="text-gray-500">Loading map...</span>
</div>
),
});
function ProjectsMapPageContent() { function ProjectsMapPageContent() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
const [projects, setProjects] = useState([]); const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [mapCenter, setMapCenter] = useState([50.0614, 19.9366]); // Default to Krakow, Poland const [mapCenter, setMapCenter] = useState([50.0614, 19.9366]); // Default to Krakow, Poland
@@ -36,31 +29,41 @@ function ProjectsMapPageContent() {
const [showLayerPanel, setShowLayerPanel] = useState(true); const [showLayerPanel, setShowLayerPanel] = useState(true);
const [currentTool, setCurrentTool] = useState("move"); // Current map tool const [currentTool, setCurrentTool] = useState("move"); // Current map tool
// Dynamically import the map component to avoid SSR issues
const DynamicMap = dynamic(() => import("@/components/ui/LeafletMap"), {
ssr: false,
loading: () => (
<div className="w-full h-96 bg-gray-100 animate-pulse rounded-lg flex items-center justify-center">
<span className="text-gray-500">{t('map.loadingMap')}</span>
</div>
),
});
// Status configuration with colors and labels // Status configuration with colors and labels
const statusConfig = { const statusConfig = {
registered: { registered: {
color: "#6B7280", color: "#6B7280",
label: "Registered", label: formatProjectStatus("registered"),
shortLabel: "Zarejestr.", shortLabel: "Zarejestr.",
}, },
in_progress_design: { in_progress_design: {
color: "#3B82F6", color: "#3B82F6",
label: "In Progress (Design)", label: formatProjectStatus("in_progress_design"),
shortLabel: "W real. (P)", shortLabel: "W real. (P)",
}, },
in_progress_construction: { in_progress_construction: {
color: "#F59E0B", color: "#F59E0B",
label: "In Progress (Construction)", label: formatProjectStatus("in_progress_construction"),
shortLabel: "W real. (R)", shortLabel: "W real. (R)",
}, },
fulfilled: { fulfilled: {
color: "#10B981", color: "#10B981",
label: "Completed", label: formatProjectStatus("fulfilled"),
shortLabel: "Zakończony", shortLabel: "Zakończony",
}, },
cancelled: { cancelled: {
color: "#EF4444", color: "#EF4444",
label: "Cancelled", label: formatProjectStatus("cancelled"),
shortLabel: "Wycofany", shortLabel: "Wycofany",
}, },
}; };
@@ -331,7 +334,7 @@ function ProjectsMapPageContent() {
d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
/> />
</svg> </svg>
View Project Details {t('map.viewProjectDetails')}
</Button> </Button>
</Link> </Link>
</div> </div>
@@ -346,9 +349,9 @@ function ProjectsMapPageContent() {
<div className="fixed inset-0 bg-gray-50 flex items-center justify-center"> <div className="fixed inset-0 bg-gray-50 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<div className="w-12 h-12 mx-auto mb-4 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div> <div className="w-12 h-12 mx-auto mb-4 border-4 border-blue-200 border-t-blue-600 rounded-full animate-spin"></div>
<p className="text-gray-600 font-medium">Loading projects map...</p> <p className="text-gray-600 font-medium">{t('map.loadingProjectsMap')}</p>
<p className="text-sm text-gray-500 mt-2"> <p className="text-sm text-gray-500 mt-2">
Preparing your full-screen map experience {t('map.preparingMap')}
</p> </p>
</div> </div>
</div> </div>
@@ -362,10 +365,10 @@ function ProjectsMapPageContent() {
<div className="bg-white/95 backdrop-blur-sm rounded-lg shadow-lg px-4 py-3 border border-gray-200"> <div className="bg-white/95 backdrop-blur-sm rounded-lg shadow-lg px-4 py-3 border border-gray-200">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<h1 className="text-lg font-semibold text-gray-900"> <h1 className="text-lg font-semibold text-gray-900">
Projects Map {t('map.projectsMap')}
</h1> </h1>
<div className="text-sm text-gray-600"> <div className="text-sm text-gray-600">
{markers.length} of {projects.length} projects with coordinates {markers.length} of {projects.length} {t('map.projectsWithCoordinates')}
</div> </div>
</div>{" "} </div>{" "}
</div> </div>
@@ -380,7 +383,7 @@ function ProjectsMapPageContent() {
const event = new CustomEvent("mapZoomIn"); const event = new CustomEvent("mapZoomIn");
window.dispatchEvent(event); window.dispatchEvent(event);
}} }}
title="Zoom In" title={t('map.zoomIn')}
> >
+ +
</button> </button>
@@ -391,7 +394,7 @@ function ProjectsMapPageContent() {
const event = new CustomEvent("mapZoomOut"); const event = new CustomEvent("mapZoomOut");
window.dispatchEvent(event); window.dispatchEvent(event);
}} }}
title="Zoom Out" title={t('map.zoomOut')}
> >
</button>{" "} </button>{" "}
@@ -410,7 +413,7 @@ function ProjectsMapPageContent() {
: "text-gray-700 hover:bg-gray-50" : "text-gray-700 hover:bg-gray-50"
}`} }`}
onClick={() => setCurrentTool("move")} onClick={() => setCurrentTool("move")}
title="Move Tool (Pan Map)" title={t('map.moveTool')}
> >
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 512 512"> <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 512 512">
<path d="M256 0c-25.3 0-47.2 14.7-57.6 36c-7-2.6-14.5-4-22.4-4c-35.3 0-64 28.7-64 64l0 165.5-2.7-2.7c-25-25-65.5-25-90.5 0s-25 65.5 0 90.5L106.5 437c48 48 113.1 75 181 75l8.5 0 8 0c1.5 0 3-.1 4.5-.4c91.7-6.2 165-79.4 171.1-171.1c.3-1.5 .4-3 .4-4.5l0-176c0-35.3-28.7-64-64-64c-5.5 0-10.9 .7-16 2l0-2c0-35.3-28.7-64-64-64c-7.9 0-15.4 1.4-22.4 4C303.2 14.7 281.3 0 256 0zM240 96.1l0-.1 0-32c0-8.8 7.2-16 16-16s16 7.2 16 16l0 31.9 0 .1 0 136c0 13.3 10.7 24 24 24s24-10.7 24-24l0-136c0 0 0 0 0-.1c0-8.8 7.2-16 16-16s16 7.2 16 16l0 55.9c0 0 0 .1 0 .1l0 80c0 13.3 10.7 24 24 24s24-10.7 24-24l0-71.9c0 0 0-.1 0-.1c0-8.8 7.2-16 16-16s16 7.2 16 16l0 172.9c-.1 .6-.1 1.3-.2 1.9c-3.4 69.7-59.3 125.6-129 129c-.6 0-1.3 .1-1.9 .2l-4.9 0-8.5 0c-55.2 0-108.1-21.9-147.1-60.9L52.7 315.3c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0L119 336.4c6.9 6.9 17.2 8.9 26.2 5.2s14.8-12.5 14.8-22.2L160 96c0-8.8 7.2-16 16-16c8.8 0 16 7.1 16 15.9L192 232c0 13.3 10.7 24 24 24s24-10.7 24-24l0-135.9z" /> <path d="M256 0c-25.3 0-47.2 14.7-57.6 36c-7-2.6-14.5-4-22.4-4c-35.3 0-64 28.7-64 64l0 165.5-2.7-2.7c-25-25-65.5-25-90.5 0s-25 65.5 0 90.5L106.5 437c48 48 113.1 75 181 75l8.5 0 8 0c1.5 0 3-.1 4.5-.4c91.7-6.2 165-79.4 171.1-171.1c.3-1.5 .4-3 .4-4.5l0-176c0-35.3-28.7-64-64-64c-5.5 0-10.9 .7-16 2l0-2c0-35.3-28.7-64-64-64c-7.9 0-15.4 1.4-22.4 4C303.2 14.7 281.3 0 256 0zM240 96.1l0-.1 0-32c0-8.8 7.2-16 16-16s16 7.2 16 16l0 31.9 0 .1 0 136c0 13.3 10.7 24 24 24s24-10.7 24-24l0-136c0 0 0 0 0-.1c0-8.8 7.2-16 16-16s16 7.2 16 16l0 55.9c0 0 0 .1 0 .1l0 80c0 13.3 10.7 24 24 24s24-10.7 24-24l0-71.9c0 0 0-.1 0-.1c0-8.8 7.2-16 16-16s16 7.2 16 16l0 172.9c-.1 .6-.1 1.3-.2 1.9c-3.4 69.7-59.3 125.6-129 129c-.6 0-1.3 .1-1.9 .2l-4.9 0-8.5 0c-55.2 0-108.1-21.9-147.1-60.9L52.7 315.3c-6.2-6.2-6.2-16.4 0-22.6s16.4-6.2 22.6 0L119 336.4c6.9 6.9 17.2 8.9 26.2 5.2s14.8-12.5 14.8-22.2L160 96c0-8.8 7.2-16 16-16c8.8 0 16 7.1 16 15.9L192 232c0 13.3 10.7 24 24 24s24-10.7 24-24l0-135.9z" />
@@ -424,7 +427,7 @@ function ProjectsMapPageContent() {
: "text-gray-700 hover:bg-gray-50" : "text-gray-700 hover:bg-gray-50"
}`} }`}
onClick={() => setCurrentTool("select")} onClick={() => setCurrentTool("select")}
title="Select Tool" title={t('map.selectTool')}
> >
<svg <svg
className="w-5 h-5" className="w-5 h-5"
@@ -448,7 +451,7 @@ function ProjectsMapPageContent() {
: "text-gray-700 hover:bg-gray-50" : "text-gray-700 hover:bg-gray-50"
}`} }`}
onClick={() => setCurrentTool("measure")} onClick={() => setCurrentTool("measure")}
title="Measure Distance" title={t('map.measureDistance')}
> >
<svg <svg
className="w-5 h-5" className="w-5 h-5"
@@ -472,7 +475,7 @@ function ProjectsMapPageContent() {
: "text-gray-700 hover:bg-gray-50" : "text-gray-700 hover:bg-gray-50"
}`} }`}
onClick={() => setCurrentTool("draw")} onClick={() => setCurrentTool("draw")}
title="Draw/Markup" title={t('map.drawMarkup')}
> >
<svg <svg
className="w-5 h-5" className="w-5 h-5"
@@ -496,7 +499,7 @@ function ProjectsMapPageContent() {
: "text-gray-700 hover:bg-gray-50" : "text-gray-700 hover:bg-gray-50"
}`} }`}
onClick={() => setCurrentTool("pin")} onClick={() => setCurrentTool("pin")}
title="Add Pin/Marker" title={t('map.addPinMarker')}
> >
<svg <svg
className="w-5 h-5" className="w-5 h-5"
@@ -526,7 +529,7 @@ function ProjectsMapPageContent() {
: "text-gray-700 hover:bg-gray-50" : "text-gray-700 hover:bg-gray-50"
}`} }`}
onClick={() => setCurrentTool("area")} onClick={() => setCurrentTool("area")}
title="Measure Area" title={t('map.measureArea')}
> >
<svg <svg
className="w-5 h-5" className="w-5 h-5"
@@ -567,7 +570,7 @@ function ProjectsMapPageContent() {
d="M4 6h16M4 10h16M4 14h16M4 18h16" d="M4 6h16M4 10h16M4 14h16M4 18h16"
/> />
</svg> </svg>
List View {t('map.listView')}
</Button> </Button>
</Link> </Link>
<Link href="/projects/new"> <Link href="/projects/new">
@@ -585,7 +588,7 @@ function ProjectsMapPageContent() {
d="M12 4v16m8-8H4" d="M12 4v16m8-8H4"
/> />
</svg> </svg>
Add Project {t('map.addProject')}
</Button> </Button>
</Link> </Link>
</div> </div>
@@ -597,7 +600,7 @@ function ProjectsMapPageContent() {
<button <button
onClick={toggleLayerPanel} onClick={toggleLayerPanel}
className="flex items-center justify-between w-full text-left layer-toggle-button" className="flex items-center justify-between w-full text-left layer-toggle-button"
title="Toggle Layer Controls" title={t('map.toggleLayerControls')}
> >
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<svg <svg
@@ -614,10 +617,10 @@ function ProjectsMapPageContent() {
/> />
</svg> </svg>
<span className="text-sm font-medium text-gray-700"> <span className="text-sm font-medium text-gray-700">
Map Layers {t('map.mapLayers')}
</span> </span>
<span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full"> <span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded-full">
{1 + activeOverlays.length} active {1 + activeOverlays.length} {t('map.active')}
</span> </span>
</div> </div>
<svg <svg
@@ -662,7 +665,7 @@ function ProjectsMapPageContent() {
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
/> />
</svg> </svg>
Base Maps {t('map.baseMaps')}
</h3> </h3>
<div className="space-y-2"> <div className="space-y-2">
{mapLayers.base.map((layer, index) => ( {mapLayers.base.map((layer, index) => (
@@ -702,7 +705,7 @@ function ProjectsMapPageContent() {
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M9 19l3 3m0 0l3-3m-3 3V10"
/> />
</svg> </svg>
Overlay Layers {t('map.overlayLayers')}
</h3>{" "} </h3>{" "}
<div className="space-y-2"> <div className="space-y-2">
{mapLayers.overlays.map((layer, index) => ( {mapLayers.overlays.map((layer, index) => (
@@ -733,7 +736,7 @@ function ProjectsMapPageContent() {
<div className="bg-white/95 backdrop-blur-sm rounded-lg shadow-lg px-4 py-3 border border-gray-200"> <div className="bg-white/95 backdrop-blur-sm rounded-lg shadow-lg px-4 py-3 border border-gray-200">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700 mr-2"> <span className="text-sm font-medium text-gray-700 mr-2">
Filters: {t('map.filters')}
</span> </span>
{/* Toggle All Button */} {/* Toggle All Button */}
<button <button
@@ -774,7 +777,7 @@ function ProjectsMapPageContent() {
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-all duration-200 hover:bg-gray-100 ${ className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-all duration-200 hover:bg-gray-100 ${
isActive ? "opacity-100 scale-100" : "opacity-40 scale-95" isActive ? "opacity-100 scale-100" : "opacity-40 scale-95"
}`} }`}
title={`Toggle ${config.label} (${projectCount} projects)`} title={`Toggle ${config.label} (${projectCount} ${t('map.projects')})`}
> >
<div <div
className={`w-3 h-3 rounded-full border-2 transition-all duration-200 ${ className={`w-3 h-3 rounded-full border-2 transition-all duration-200 ${
@@ -809,7 +812,7 @@ function ProjectsMapPageContent() {
<div className="bg-white/95 backdrop-blur-sm rounded-lg shadow-lg px-4 py-3 border border-gray-200"> <div className="bg-white/95 backdrop-blur-sm rounded-lg shadow-lg px-4 py-3 border border-gray-200">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-sm font-medium text-gray-700 mr-2"> <span className="text-sm font-medium text-gray-700 mr-2">
Filters: {t('map.filters')}
</span> </span>
{/* Toggle All Button */} {/* Toggle All Button */}
@@ -833,8 +836,8 @@ function ProjectsMapPageContent() {
</svg> </svg>
<span className="text-gray-600"> <span className="text-gray-600">
{Object.values(statusFilters).every((v) => v) {Object.values(statusFilters).every((v) => v)
? "Hide All" ? t('map.hideAll')
: "Show All"} : t('map.showAll')}
</span> </span>
</button> </button>
@@ -852,7 +855,7 @@ function ProjectsMapPageContent() {
className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-all duration-200 hover:bg-gray-100 ${ className={`flex items-center gap-1 px-2 py-1 rounded text-xs font-medium transition-all duration-200 hover:bg-gray-100 ${
isActive ? "opacity-100 scale-100" : "opacity-40 scale-95" isActive ? "opacity-100 scale-100" : "opacity-40 scale-95"
}`} }`}
title={`Toggle ${config.label} (${projectCount} projects)`} title={`Toggle ${config.label} (${projectCount} ${t('map.projects')})`}
> >
<div <div
className={`w-3 h-3 rounded-full border-2 transition-all duration-200 ${ className={`w-3 h-3 rounded-full border-2 transition-all duration-200 ${
@@ -900,18 +903,17 @@ function ProjectsMapPageContent() {
</svg> </svg>
</div> </div>
<h3 className="text-lg font-medium text-gray-900 mb-2"> <h3 className="text-lg font-medium text-gray-900 mb-2">
No projects with coordinates {t('map.noProjectsWithCoordinates')}
</h3> </h3>
<p className="text-gray-500 mb-6"> <p className="text-gray-500 mb-6">
Projects need coordinates to appear on the map. Add coordinates {t('map.noProjectsMessage')}
when creating or editing projects.
</p> </p>
<div className="flex gap-3 justify-center"> <div className="flex gap-3 justify-center">
<Link href="/projects"> <Link href="/projects">
<Button variant="outline">View All Projects</Button> <Button variant="outline">{t('map.viewAllProjects')}</Button>
</Link> </Link>
<Link href="/projects/new"> <Link href="/projects/new">
<Button variant="primary">Add Project</Button> <Button variant="primary">{t('map.addProject')}</Button>
</Link> </Link>
</div> </div>
</div> </div>

View File

@@ -69,6 +69,7 @@ const translations = {
actions: "Akcje", actions: "Akcje",
view: "Wyświetl", view: "Wyświetl",
clearSearch: "Wyczyść wyszukiwanie", clearSearch: "Wyczyść wyszukiwanie",
filters:"Filtry",
clearAllFilters: "Wyczyść wszystkie filtry", clearAllFilters: "Wyczyść wszystkie filtry",
sortBy: "Sortuj według" sortBy: "Sortuj według"
}, },
@@ -273,6 +274,40 @@ const translations = {
failedToDeleteFile: "Nie udało się usunąć pliku" failedToDeleteFile: "Nie udało się usunąć pliku"
}, },
// Map
map: {
loadingMap: "Ładowanie mapy...",
preparingMap: "Przygotowywanie pełnoekranowego widoku mapy",
projectsMap: "Mapa projektów",
projectsWithCoordinates: "projektów z współrzędnymi",
loadingProjectsMap: "Ładowanie mapy projektów...",
moveTool: "Narzędzie przesuwania (przesuń mapę)",
selectTool: "Narzędzie wyboru",
measureDistance: "Zmierz odległość",
drawMarkup: "Rysuj/oznacz",
addPinMarker: "Dodaj pinezkę/znacznik",
measureArea: "Zmierz powierzchnię",
listView: "Widok listy",
addProject: "Dodaj projekt",
toggleLayerControls: "Przełącz kontrolki warstw",
mapLayers: "Warstwy mapy",
active: "aktywnych",
baseMaps: "Mapy bazowe",
overlayLayers: "Warstwy nakładkowe",
filters: "Filtry:",
toggleAllFilters: "Przełącz wszystkie filtry",
hideAll: "Ukryj wszystkie",
showAll: "Pokaż wszystkie",
toggleFilter: "Przełącz filtr",
projects: "projektów",
noProjectsWithCoordinates: "Brak projektów ze współrzędnymi",
noProjectsMessage: "Projekty potrzebują współrzędnych, aby pojawić się na mapie. Dodaj współrzędne podczas tworzenia lub edycji projektów.",
viewAllProjects: "Zobacz wszystkie projekty",
zoomIn: "Przybliż",
zoomOut: "Oddal",
viewProjectDetails: "Zobacz szczegóły projektu"
},
// Tasks // Tasks
tasks: { tasks: {
title: "Zadania", title: "Zadania",
@@ -552,6 +587,7 @@ const translations = {
actions: "Actions", actions: "Actions",
view: "View", view: "View",
clearSearch: "Clear search", clearSearch: "Clear search",
filters: "Filters",
clearAllFilters: "Clear all filters", clearAllFilters: "Clear all filters",
sortBy: "Sort by" sortBy: "Sort by"
}, },
@@ -672,6 +708,40 @@ const translations = {
} }
}, },
// Map
map: {
loadingMap: "Loading map...",
preparingMap: "Preparing your full-screen map experience",
projectsMap: "Projects Map",
projectsWithCoordinates: "projects with coordinates",
loadingProjectsMap: "Loading projects map...",
moveTool: "Move Tool (Pan Map)",
selectTool: "Select Tool",
measureDistance: "Measure Distance",
drawMarkup: "Draw/Markup",
addPinMarker: "Add Pin/Marker",
measureArea: "Measure Area",
listView: "List View",
addProject: "Add Project",
toggleLayerControls: "Toggle Layer Controls",
mapLayers: "Map Layers",
active: "active",
baseMaps: "Base Maps",
overlayLayers: "Overlay Layers",
filters: "Filters:",
toggleAllFilters: "Toggle all filters",
hideAll: "Hide All",
showAll: "Show All",
toggleFilter: "Toggle filter",
projects: "projects",
noProjectsWithCoordinates: "No projects with coordinates",
noProjectsMessage: "Projects need coordinates to appear on the map. Add coordinates when creating or editing projects.",
viewAllProjects: "View All Projects",
zoomIn: "Zoom In",
zoomOut: "Zoom Out",
viewProjectDetails: "View Project Details"
},
contracts: { contracts: {
title: "Contracts", title: "Contracts",
subtitle: "Manage your contracts and agreements", subtitle: "Manage your contracts and agreements",