feat: Add date formatting utility functions and integrate them across components

This commit is contained in:
Chop
2025-06-19 23:29:06 +02:00
parent 306c96328e
commit 639179ed21
11 changed files with 118 additions and 74 deletions

View File

@@ -9,6 +9,7 @@ import Badge from "@/components/ui/Badge";
import PageContainer from "@/components/ui/PageContainer"; import PageContainer from "@/components/ui/PageContainer";
import PageHeader from "@/components/ui/PageHeader"; import PageHeader from "@/components/ui/PageHeader";
import { LoadingState } from "@/components/ui/States"; import { LoadingState } from "@/components/ui/States";
import { formatDate } from "@/lib/utils";
export default function ContractDetailsPage() { export default function ContractDetailsPage() {
const params = useParams(); const params = useParams();
@@ -135,7 +136,6 @@ export default function ContractDetailsPage() {
{contract.contract_number} {contract.contract_number}
</p> </p>
</div> </div>
{contract.contract_name && ( {contract.contract_name && (
<div> <div>
<span className="text-sm font-medium text-gray-500 block mb-1"> <span className="text-sm font-medium text-gray-500 block mb-1">
@@ -146,7 +146,6 @@ export default function ContractDetailsPage() {
</p> </p>
</div> </div>
)} )}
{contract.customer_contract_number && ( {contract.customer_contract_number && (
<div> <div>
<span className="text-sm font-medium text-gray-500 block mb-1"> <span className="text-sm font-medium text-gray-500 block mb-1">
@@ -157,7 +156,6 @@ export default function ContractDetailsPage() {
</p> </p>
</div> </div>
)} )}
{contract.customer && ( {contract.customer && (
<div> <div>
<span className="text-sm font-medium text-gray-500 block mb-1"> <span className="text-sm font-medium text-gray-500 block mb-1">
@@ -168,7 +166,6 @@ export default function ContractDetailsPage() {
</p> </p>
</div> </div>
)} )}
{contract.investor && ( {contract.investor && (
<div> <div>
<span className="text-sm font-medium text-gray-500 block mb-1"> <span className="text-sm font-medium text-gray-500 block mb-1">
@@ -178,30 +175,24 @@ export default function ContractDetailsPage() {
{contract.investor} {contract.investor}
</p> </p>
</div> </div>
)} )}{" "}
{contract.date_signed && ( {contract.date_signed && (
<div> <div>
<span className="text-sm font-medium text-gray-500 block mb-1"> <span className="text-sm font-medium text-gray-500 block mb-1">
Date Signed Date Signed
</span> </span>
<p className="text-gray-900 font-medium"> <p className="text-gray-900 font-medium">
{new Date(contract.date_signed).toLocaleDateString( {formatDate(contract.date_signed)}
"en-US"
)}
</p> </p>
</div> </div>
)} )}
{contract.finish_date && ( {contract.finish_date && (
<div> <div>
<span className="text-sm font-medium text-gray-500 block mb-1"> <span className="text-sm font-medium text-gray-500 block mb-1">
Finish Date Finish Date
</span> </span>
<p className="text-gray-900 font-medium"> <p className="text-gray-900 font-medium">
{new Date(contract.finish_date).toLocaleDateString( {formatDate(contract.finish_date)}
"en-US"
)}
</p> </p>
</div> </div>
)} )}
@@ -350,10 +341,8 @@ export default function ContractDetailsPage() {
d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" d="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
clipRule="evenodd" clipRule="evenodd"
/> />
</svg> </svg>{" "}
{new Date(project.finish_date).toLocaleDateString( {formatDate(project.finish_date)}
"en-US"
)}
</div> </div>
)} )}
<div className="flex items-center"> <div className="flex items-center">

View File

@@ -10,6 +10,7 @@ import PageHeader from "@/components/ui/PageHeader";
import SearchBar from "@/components/ui/SearchBar"; import SearchBar from "@/components/ui/SearchBar";
import FilterBar from "@/components/ui/FilterBar"; import FilterBar from "@/components/ui/FilterBar";
import { LoadingState } from "@/components/ui/States"; import { LoadingState } from "@/components/ui/States";
import { formatDate } from "@/lib/utils";
export default function ContractsMainPage() { export default function ContractsMainPage() {
const [contracts, setContracts] = useState([]); const [contracts, setContracts] = useState([]);
@@ -485,11 +486,9 @@ export default function ContractsMainPage() {
</svg> </svg>
<span className="font-medium text-gray-700"> <span className="font-medium text-gray-700">
Zawarcie: Zawarcie:
</span> </span>{" "}
<span className="text-gray-600"> <span className="text-gray-600">
{new Date( {formatDate(contract.date_signed)}
contract.date_signed
).toLocaleDateString("pl-PL")}
</span> </span>
</div> </div>
)} )}
@@ -510,11 +509,9 @@ export default function ContractsMainPage() {
</svg> </svg>
<span className="font-medium text-gray-700"> <span className="font-medium text-gray-700">
Zakończenie: Zakończenie:
</span> </span>{" "}
<span className="text-gray-600"> <span className="text-gray-600">
{new Date( {formatDate(contract.finish_date)}
contract.finish_date
).toLocaleDateString("pl-PL")}
</span> </span>
</div> </div>
)} )}

View File

@@ -20,6 +20,7 @@ import {
endOfWeek, endOfWeek,
format, format,
} from "date-fns"; } from "date-fns";
import { formatDate } from "@/lib/utils";
export default function Home() { export default function Home() {
const [stats, setStats] = useState({ const [stats, setStats] = useState({
@@ -767,7 +768,7 @@ export default function Home() {
{deadline.type === "project" {deadline.type === "project"
? deadline.city ? deadline.city
: deadline.customer}{" "} : deadline.customer}{" "}
{deadline.date} {formatDate(deadline.date)}
</p> </p>
</div> </div>
</div> </div>
@@ -839,7 +840,7 @@ export default function Home() {
</div> </div>
{project.finish_date && ( {project.finish_date && (
<p className="text-xs text-gray-500 mt-1"> <p className="text-xs text-gray-500 mt-1">
Due: {project.finish_date} Due: {formatDate(project.finish_date)}
</p> </p>
)} )}
</div> </div>

View File

@@ -9,6 +9,7 @@ import Button from "@/components/ui/Button";
import Badge from "@/components/ui/Badge"; import Badge from "@/components/ui/Badge";
import Link from "next/link"; import Link from "next/link";
import { differenceInCalendarDays, parseISO } from "date-fns"; import { differenceInCalendarDays, parseISO } from "date-fns";
import { formatDate } from "@/lib/utils";
import PageContainer from "@/components/ui/PageContainer"; import PageContainer from "@/components/ui/PageContainer";
import PageHeader from "@/components/ui/PageHeader"; import PageHeader from "@/components/ui/PageHeader";
import ProjectStatusDropdown from "@/components/ProjectStatusDropdown"; import ProjectStatusDropdown from "@/components/ProjectStatusDropdown";
@@ -172,13 +173,15 @@ export default async function ProjectViewPage({ params }) {
<p className="text-gray-900 font-medium"> <p className="text-gray-900 font-medium">
{project.unit || "N/A"} {project.unit || "N/A"}
</p> </p>
</div> </div>{" "}
<div> <div>
<span className="text-sm font-medium text-gray-500 block mb-1"> <span className="text-sm font-medium text-gray-500 block mb-1">
Deadline Deadline
</span> </span>
<p className="text-gray-900 font-medium"> <p className="text-gray-900 font-medium">
{project.finish_date || "N/A"} {project.finish_date
? formatDate(project.finish_date)
: "N/A"}
</p> </p>
</div> </div>
<div> <div>

View File

@@ -10,6 +10,7 @@ import PageContainer from "@/components/ui/PageContainer";
import PageHeader from "@/components/ui/PageHeader"; import PageHeader from "@/components/ui/PageHeader";
import SearchBar from "@/components/ui/SearchBar"; import SearchBar from "@/components/ui/SearchBar";
import { LoadingState } from "@/components/ui/States"; import { LoadingState } from "@/components/ui/States";
import { formatDate } from "@/lib/utils";
export default function ProjectListPage() { export default function ProjectListPage() {
const [projects, setProjects] = useState([]); const [projects, setProjects] = useState([]);
@@ -245,12 +246,14 @@ export default function ProjectListPage() {
title={project.plot} title={project.plot}
> >
{project.plot || "N/A"} {project.plot || "N/A"}
</td> </td>{" "}
<td <td
className="px-2 py-3 text-xs text-gray-600 truncate" className="px-2 py-3 text-xs text-gray-600 truncate"
title={project.finish_date} title={project.finish_date}
> >
{project.finish_date || "N/A"} {project.finish_date
? formatDate(project.finish_date)
: "N/A"}
</td> </td>
<td className="px-2 py-3 text-xs text-gray-600 text-center font-semibold"> <td className="px-2 py-3 text-xs text-gray-600 text-center font-semibold">
{project.project_type === "design" {project.project_type === "design"

View File

@@ -8,6 +8,7 @@ import Badge from "@/components/ui/Badge";
import TaskStatusDropdownSimple from "@/components/TaskStatusDropdownSimple"; import TaskStatusDropdownSimple from "@/components/TaskStatusDropdownSimple";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import { formatDistanceToNow, parseISO } from "date-fns"; import { formatDistanceToNow, parseISO } from "date-fns";
import { formatDate } from "@/lib/utils";
import PageContainer from "@/components/ui/PageContainer"; import PageContainer from "@/components/ui/PageContainer";
import PageHeader from "@/components/ui/PageHeader"; import PageHeader from "@/components/ui/PageHeader";
import SearchBar from "@/components/ui/SearchBar"; import SearchBar from "@/components/ui/SearchBar";

View File

@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import { Card, CardHeader, CardContent } from "@/components/ui/Card";
import Button from "@/components/ui/Button"; import Button from "@/components/ui/Button";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import { formatDateForInput } from "@/lib/utils";
export default function ContractForm() { export default function ContractForm() {
const [form, setForm] = useState({ const [form, setForm] = useState({
@@ -133,11 +134,11 @@ export default function ContractForm() {
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
Date Signed Date Signed
</label> </label>{" "}
<Input <Input
type="date" type="date"
name="date_signed" name="date_signed"
value={form.date_signed || ""} value={formatDateForInput(form.date_signed)}
onChange={handleChange} onChange={handleChange}
/> />
</div> </div>
@@ -145,11 +146,11 @@ export default function ContractForm() {
<div className="md:col-span-2"> <div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
Finish Date Finish Date
</label> </label>{" "}
<Input <Input
type="date" type="date"
name="finish_date" name="finish_date"
value={form.finish_date || ""} value={formatDateForInput(form.finish_date)}
onChange={handleChange} onChange={handleChange}
/> />
</div> </div>

View File

@@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import { Card, CardHeader, CardContent } from "@/components/ui/Card";
import Button from "@/components/ui/Button"; import Button from "@/components/ui/Button";
import { Input } from "@/components/ui/Input"; import { Input } from "@/components/ui/Input";
import { formatDateForInput } from "@/lib/utils";
export default function ProjectForm({ initialData = null }) { export default function ProjectForm({ initialData = null }) {
const [form, setForm] = useState({ const [form, setForm] = useState({
@@ -214,11 +215,11 @@ export default function ProjectForm({ initialData = null }) {
<div> <div>
<label className="block text-sm font-medium text-gray-700 mb-2"> <label className="block text-sm font-medium text-gray-700 mb-2">
Finish Date Finish Date
</label> </label>{" "}
<Input <Input
type="date" type="date"
name="finish_date" name="finish_date"
value={form.finish_date || ""} value={formatDateForInput(form.finish_date)}
onChange={handleChange} onChange={handleChange}
/> />
</div> </div>

View File

@@ -13,6 +13,7 @@ import {
parseISO, parseISO,
formatDistanceToNow, formatDistanceToNow,
} from "date-fns"; } from "date-fns";
import { formatDate } from "@/lib/utils";
export default function ProjectTasksDashboard() { export default function ProjectTasksDashboard() {
const [allTasks, setAllTasks] = useState([]); const [allTasks, setAllTasks] = useState([]);

View File

@@ -6,6 +6,7 @@ import TaskStatusDropdownSimple from "./TaskStatusDropdownSimple";
import { Card, CardHeader, CardContent } from "./ui/Card"; import { Card, CardHeader, CardContent } from "./ui/Card";
import Button from "./ui/Button"; import Button from "./ui/Button";
import Badge from "./ui/Badge"; import Badge from "./ui/Badge";
import { formatDate } from "@/lib/utils";
export default function ProjectTasksSection({ projectId }) { export default function ProjectTasksSection({ projectId }) {
const [projectTasks, setProjectTasks] = useState([]); const [projectTasks, setProjectTasks] = useState([]);
@@ -444,7 +445,7 @@ export default function ProjectTasksSection({ projectId }) {
</td>{" "} </td>{" "}
<td className="px-4 py-4 text-sm text-gray-600"> <td className="px-4 py-4 text-sm text-gray-600">
{task.date_started {task.date_started
? new Date(task.date_started).toLocaleDateString() ? formatDate(task.date_started)
: "Not started"} : "Not started"}
</td>{" "} </td>{" "}
<td className="px-4 py-4"> <td className="px-4 py-4">
@@ -519,15 +520,11 @@ export default function ProjectTasksSection({ projectId }) {
</div> </div>
<p className="text-sm text-gray-800"> <p className="text-sm text-gray-800">
{note.note} {note.note}
</p> </p>{" "}
<p className="text-xs text-gray-500 mt-1"> <p className="text-xs text-gray-500 mt-1">
{new Date( {formatDate(note.note_date, {
note.note_date includeTime: true,
).toLocaleDateString()}{" "} })}
at{" "}
{new Date(
note.note_date
).toLocaleTimeString()}
</p> </p>
</div> </div>
{!note.is_system && ( {!note.is_system && (

View File

@@ -38,3 +38,53 @@ export const getDeadlineText = (daysRemaining) => {
if (daysRemaining > 0) return `${daysRemaining} days left`; if (daysRemaining > 0) return `${daysRemaining} days left`;
return `${Math.abs(daysRemaining)} days overdue`; return `${Math.abs(daysRemaining)} days overdue`;
}; };
export const formatDate = (date, options = {}) => {
if (!date) return "";
try {
const dateObj = typeof date === "string" ? new Date(date) : date;
if (isNaN(dateObj.getTime())) {
return "Invalid date";
}
// Default to DD.MM.YYYY format
const day = String(dateObj.getDate()).padStart(2, "0");
const month = String(dateObj.getMonth() + 1).padStart(2, "0");
const year = dateObj.getFullYear();
if (options.includeTime) {
const hours = String(dateObj.getHours()).padStart(2, "0");
const minutes = String(dateObj.getMinutes()).padStart(2, "0");
return `${day}.${month}.${year} ${hours}:${minutes}`;
}
return `${day}.${month}.${year}`;
} catch (error) {
console.error("Error formatting date:", error);
return "Invalid date";
}
};
export const formatDateForInput = (date) => {
if (!date) return "";
try {
const dateObj = typeof date === "string" ? new Date(date) : date;
if (isNaN(dateObj.getTime())) {
return "";
}
// Format as YYYY-MM-DD for HTML date inputs
const year = dateObj.getFullYear();
const month = String(dateObj.getMonth() + 1).padStart(2, "0");
const day = String(dateObj.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
} catch (error) {
console.error("Error formatting date for input:", error);
return "";
}
};