diff --git a/src/app/contracts/page.js b/src/app/contracts/page.js index 7ee221d..ed221e0 100644 --- a/src/app/contracts/page.js +++ b/src/app/contracts/page.js @@ -5,6 +5,11 @@ import Link from "next/link"; import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; +import PageContainer from "@/components/ui/PageContainer"; +import PageHeader from "@/components/ui/PageHeader"; +import SearchBar from "@/components/ui/SearchBar"; +import FilterBar from "@/components/ui/FilterBar"; +import { LoadingState } from "@/components/ui/States"; export default function ContractsMainPage() { const [contracts, setContracts] = useState([]); @@ -162,37 +167,75 @@ export default function ContractsMainPage() { }; if (loading) { return ( -
-
-
-

Ładowanie umów...

-
-
+ + + + + + + + ); } const stats = getContractStats(); + const filterOptions = [ + { + label: "Status", + value: statusFilter, + onChange: (e) => setStatusFilter(e.target.value), + options: [ + { value: "all", label: "Wszystkie" }, + { value: "active", label: "Aktywne" }, + { value: "completed", label: "Zakończone" }, + { value: "no_end_date", label: "Bez daty końca" }, + ], + }, + { + label: "Sortuj według", + value: sortBy, + onChange: (e) => setSortBy(e.target.value), + options: [ + { value: "contract_number", label: "Numer umowy" }, + { value: "contract_name", label: "Nazwa umowy" }, + { value: "customer", label: "Klient" }, + { value: "start_date", label: "Data rozpoczęcia" }, + { value: "finish_date", label: "Data zakończenia" }, + ], + }, + { + label: "Kolejność", + value: sortOrder, + onChange: (e) => setSortOrder(e.target.value), + options: [ + { value: "asc", label: "Rosnąco" }, + { value: "desc", label: "Malejąco" }, + ], + }, + ]; + return ( -
- {/* Header */} -
-
-

Umowy

-

- Zarządzaj swoimi umowami i kontraktami -

-
{" "} - - - Nowa umowa - -
+ + + + + {" "} + {/* Statistics Cards */} -
+
@@ -302,93 +345,17 @@ export default function ContractsMainPage() {

- + {" "}
- {/* Filters and Search */} - - -
- {/* Search */} -
- - setSearchTerm(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" - /> -
- - {/* Status Filter */} -
- - -
- - {/* Sort */} -
- - -
-
-
-
{" "} + + {" "} {/* Contracts List */} {filteredContracts.length === 0 ? ( @@ -624,9 +591,9 @@ export default function ContractsMainPage() { Wyczyść filtry )} -

+

{" "}
)} - + ); } diff --git a/src/app/page.js b/src/app/page.js index 6aee7ae..8515b2c 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -5,6 +5,9 @@ import Link from "next/link"; import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; +import PageContainer from "@/components/ui/PageContainer"; +import PageHeader from "@/components/ui/PageHeader"; +import { LoadingState } from "@/components/ui/States"; export default function Home() { const [stats, setStats] = useState({ @@ -44,317 +47,319 @@ export default function Home() { }, []); return ( -
-
-
-

Dashboard

-

- Overview of your projects and tasks -

-
+ + - {/* Stats Cards */} -
- - -
-
-

- Total Projects -

-

- {stats.totalProjects} -

+ {loading ? ( + + ) : ( + <> + {/* Stats Cards */} +
+ + +
+
+

+ Total Projects +

+

+ {stats.totalProjects} +

+
+
+ + + +
-
- - - -
-
- - + + - - -
-
-

- Active Projects -

-

- {stats.activeProjects} -

+ + +
+
+

+ Active Projects +

+

+ {stats.activeProjects} +

+
+
+ + + +
-
- - - -
-
- - + + - - -
-
-

- Pending Tasks -

-

- {stats.pendingTasks} -

+ + +
+
+

+ Pending Tasks +

+

+ {stats.pendingTasks} +

+
+
+ + + +
-
- - - -
-
- - + + - - -
-
-

- Completed Tasks -

-

- {stats.completedTasks} -

+ + +
+
+

+ Completed Tasks +

+

+ {stats.completedTasks} +

+
+
+ + + +
-
- - - -
-
- - -
+
+
+
- {/* Recent Projects */} -
- - -
-

- Recent Projects -

- - - -
-
- - {loading ? ( -
-
- {[...Array(3)].map((_, i) => ( -
-
-
-
-
+ {/* Recent Projects */} +
+ + +
+

+ Recent Projects +

+ + + +
+
+ + {loading ? ( +
+
+ {[...Array(3)].map((_, i) => ( +
+
+
+
+
+
+
+ ))} +
+
+ ) : recentProjects.length === 0 ? ( +
+

No projects yet.

+ + + +
+ ) : ( +
+ {recentProjects.map((project) => ( +
+
+
+ + {project.project_name} + +

+ {project.city} • Due: {project.finish_date} +

+
+ = new Date() + ? "success" + : "danger" + } + size="xs" + > + {new Date(project.finish_date) >= new Date() + ? "Active" + : "Overdue"} +
))}
-
- ) : recentProjects.length === 0 ? ( -
-

No projects yet.

- - - -
- ) : ( -
- {recentProjects.map((project) => ( -
-
-
- - {project.project_name} - -

- {project.city} • Due: {project.finish_date} -

-
- = new Date() - ? "success" - : "danger" - } - size="xs" + )} + + + + {/* Quick Actions */} + + +

+ Quick Actions +

+
+ + +
+
+
+ - {new Date(project.finish_date) >= new Date() - ? "Active" - : "Overdue"} - + + +
+
+

+ New Project +

+

+ Create a new project +

- ))} -
- )} -
-
+
+ - {/* Quick Actions */} - - -

- Quick Actions -

-
- - -
-
-
- - - -
-
-

- New Project -

-

- Create a new project -

+ +
+
+
+ + + +
+
+

+ New Contract +

+

+ Add a new contract +

+
-
- + - -
-
-
- - - -
-
-

- New Contract -

-

- Add a new contract -

+ +
+
+
+ + + +
+
+

+ Task Template +

+

+ Create task template +

+
-
- - - -
-
-
- - - -
-
-

- Task Template -

-

- Create task template -

-
-
-
- - - -
-
-
+ + + +
+ + )} + ); } diff --git a/src/app/projects/[id]/page.js b/src/app/projects/[id]/page.js index 466dfda..7674af4 100644 --- a/src/app/projects/[id]/page.js +++ b/src/app/projects/[id]/page.js @@ -9,6 +9,8 @@ import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; import Link from "next/link"; import { differenceInCalendarDays, parseISO } from "date-fns"; +import PageContainer from "@/components/ui/PageContainer"; +import PageHeader from "@/components/ui/PageHeader"; export default async function ProjectViewPage({ params }) { const project = getProjectWithContract(params.id); @@ -17,10 +19,9 @@ export default async function ProjectViewPage({ params }) { parseISO(project.finish_date), new Date() ); - if (!project) { return ( -
+

Project not found.

@@ -29,7 +30,7 @@ export default async function ProjectViewPage({ params }) {
-
+ ); } @@ -38,197 +39,186 @@ export default async function ProjectViewPage({ params }) { if (days <= 7) return "warning"; return "success"; }; - return ( -
-
-
- - - - - - -
- -
-

- {project.project_name} -

- - {daysRemaining === 0 - ? "Due Today" - : daysRemaining > 0 - ? `${daysRemaining} days left` - : `${Math.abs(daysRemaining)} days overdue`} - -
- -
- - -

- Project Information -

-
- -
-
- - Location - -

{project.city}

-
-
- - Address - -

{project.address}

-
-
- - Plot - -

{project.plot}

-
-
- - District - -

{project.district}

-
-
- - Unit - -

{project.unit}

-
-
- - Deadline - -

{project.finish_date}

-
-
- WP -

{project.wp}

-
-
- - Investment Number - -

{project.investment_number}

-
-
-
- - Contact - -

{project.contact}

-
- {project.notes && ( -
- - Notes - -

{project.notes}

-
- )} -
-
- - - -

- Contract Details -

-
- -
- - Contract Number - -

{project.contract_number}

-
-
- - Contract Name - -

{project.contract_name}

-
-
- - Customer - -

{project.customer}

-
-
- - Investor - -

{project.investor}

-
-
-
-
- - + + + + {daysRemaining === 0 + ? "Due Today" + : daysRemaining > 0 + ? `${daysRemaining} days left` + : `${Math.abs(daysRemaining)} days overdue`} + + + + + + + +
+ } + /> +
-

Notes

+

+ Project Information +

- -
- -
- {notes.length === 0 ? ( -
-
- - - -
-

No notes yet.

+ +
+
+ + Location + +

{project.city}

- ) : ( -
- {notes.map((n) => ( -
-

{n.note_date}

-

{n.note}

-
- ))} +
+ + Address + +

{project.address}

+
+
+ Plot +

{project.plot}

+
+
+ + District + +

{project.district}

+
+
+ Unit +

{project.unit}

+
+
+ + Deadline + +

{project.finish_date}

+
+
+ WP +

{project.wp}

+
+
+ + Investment Number + +

{project.investment_number}

+
+
+
+ Contact +

{project.contact}

+
+ {project.notes && ( +
+ Notes +

{project.notes}

)} + + + +

+ Contract Details +

+
+ +
+ + Contract Number + +

{project.contract_number}

+
+
+ + Contract Name + +

{project.contract_name}

+
+
+ + Customer + +

{project.customer}

+
+
+ + Investor + +

{project.investor}

+
+
+
-
+ + + + + +

Notes

+
+ +
+ +
+ {notes.length === 0 ? ( +
+
+ + + +
+

No notes yet.

+
+ ) : ( +
+ {notes.map((n) => ( +
+

{n.note_date}

+

{n.note}

+
+ ))} +
+ )} +
+
+ ); } diff --git a/src/app/projects/page.js b/src/app/projects/page.js index 3cc854e..a712b71 100644 --- a/src/app/projects/page.js +++ b/src/app/projects/page.js @@ -6,6 +6,10 @@ import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; import { Input } from "@/components/ui/Input"; +import PageContainer from "@/components/ui/PageContainer"; +import PageHeader from "@/components/ui/PageHeader"; +import SearchBar from "@/components/ui/SearchBar"; +import { LoadingState } from "@/components/ui/States"; export default function ProjectListPage() { const [projects, setProjects] = useState([]); @@ -54,227 +58,151 @@ export default function ProjectListPage() { setSearchTerm(e.target.value); }; return ( -
-
-
-
-

Projects

-

Manage and track your projects

-
- - + + + + + {filteredProjects.length === 0 && searchTerm ? ( + + +
- Add Project - - {" "} -
{" "} - {/* Search Bar */} -
-
-
-
-
- - - -
- - {searchTerm && ( - - )} -
- {searchTerm && ( -
-
-

- Found{" "} - - {filteredProjects.length} - {" "} - project - {filteredProjects.length !== 1 ? "s" : ""} matching - - {" "} - "{searchTerm}" - -

- {filteredProjects.length === 0 && ( - - )} -
-
- )} -
-
- {filteredProjects.length === 0 && searchTerm ? ( - - -
- - - -
-

- No projects found -

-

- No projects match your search criteria. Try adjusting your - search terms. -

- -
-
- ) : projects.length === 0 ? ( - - -
- - - -
-

- No projects yet -

-

- Get started by creating your first project -

- - - -
-
- ) : ( -
- {/* Header Row */} -
- {" "} -
Number
-
Project Name
-
WP
-
City
-
Address
-
Plot
{" "} -
Finish Date
-
Actions
-
{" "} - {/* Data Rows */} - {filteredProjects.map((project, index) => ( -
+ No projects found + +

+ No projects match your search criteria. Try adjusting your search + terms. +

+ + + + ) : projects.length === 0 ? ( + + +
+ -
- - {project.project_number} - -
{" "} -
- - {project.project_name} - -
-
- {project.wp || "N/A"} -
-
- {project.city || "N/A"} -
-
- {project.address || "N/A"} -
-
- {project.plot || "N/A"} -
{" "} -
- {project.finish_date || "N/A"} -
-
- - - -
+ + +
+

+ No projects yet +

+

+ Get started by creating your first project +

+ + + +
+
+ ) : ( +
+ {/* Header Row */} +
+ {" "} +
Number
+
Project Name
+
WP
+
City
+
Address
+
Plot
{" "} +
Finish Date
+
Actions
+
{" "} + {/* Data Rows */} + {filteredProjects.map((project, index) => ( +
+
+ + {project.project_number} + +
{" "} +
+ + {project.project_name} +
- ))} -
- )} -
-
+
+ {project.wp || "N/A"} +
+
+ {project.city || "N/A"} +
+
+ {project.address || "N/A"} +
+
+ {project.plot || "N/A"} +
{" "} +
+ {project.finish_date || "N/A"} +
+
+ + + +
{" "} +
+ ))} +
+ )} + ); } diff --git a/src/app/tasks/page.js b/src/app/tasks/page.js index 3b14a0f..00ef406 100644 --- a/src/app/tasks/page.js +++ b/src/app/tasks/page.js @@ -7,6 +7,11 @@ import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; import { Input } from "@/components/ui/Input"; import { formatDistanceToNow, parseISO } from "date-fns"; +import PageContainer from "@/components/ui/PageContainer"; +import PageHeader from "@/components/ui/PageHeader"; +import SearchBar from "@/components/ui/SearchBar"; +import FilterBar from "@/components/ui/FilterBar"; +import { LoadingState } from "@/components/ui/States"; export default function ProjectTasksPage() { const [allTasks, setAllTasks] = useState([]); @@ -153,335 +158,293 @@ export default function ProjectTasksPage() { .length, completed: allTasks.filter((task) => task.status === "completed").length, }; - if (loading) { return ( -
-
-
-
-

Loading tasks...

-
-
-
+ + + + ); } + const filterOptions = [ + { + label: "Status", + value: statusFilter, + onChange: (e) => setStatusFilter(e.target.value), + options: [ + { value: "all", label: "All" }, + { value: "pending", label: "Pending" }, + { value: "in_progress", label: "In Progress" }, + { value: "completed", label: "Completed" }, + ], + }, + { + label: "Priority", + value: priorityFilter, + onChange: (e) => setPriorityFilter(e.target.value), + options: [ + { value: "all", label: "All" }, + { value: "high", label: "High" }, + { value: "normal", label: "Normal" }, + { value: "low", label: "Low" }, + ], + }, + ]; + return ( -
-
-
-
-

Project Tasks

-

- Monitor and manage tasks across all projects -

-
-
- - {/* Stats Cards */} -
- - -
-
- - - -
-
-

- Total Tasks -

-

- {statusCounts.all} -

-
-
-
-
- - - -
-
- - - -
-
-

Pending

-

- {statusCounts.pending} -

-
-
-
-
- - - -
-
- - - -
-
-

- In Progress -

-

- {statusCounts.in_progress} -

-
-
-
-
- - - -
-
- - - -
-
-

Completed

-

- {statusCounts.completed} -

-
-
-
-
-
- - {/* Filters */} - - -
-
- - setSearchTerm(e.target.value)} - /> -
- -
- - + +
- -
- - +
+

Total Tasks

+

+ {statusCounts.all} +

- - {/* Tasks List */} - {filteredTasks.length === 0 ? ( - - -
+ + +
+
-

- No tasks found -

-

- {searchTerm || - statusFilter !== "all" || - priorityFilter !== "all" - ? "Try adjusting your filters to see more tasks" - : "No tasks have been created yet"} -

- - - ) : ( -
- {filteredTasks.map((task) => ( - - -
-
-
-

- {task.task_name} -

- - {getStatusDisplayName(task.status)} +
+

Pending

+

+ {statusCounts.pending} +

+
+
+ + + + +
+
+ + + +
+
+

In Progress

+

+ {statusCounts.in_progress} +

+
+
+
+
+ + +
+
+ + + +
+
+

Completed

+

+ {statusCounts.completed} +

+
+
+
+
{" "} +
+ {/* Tasks List */} + {filteredTasks.length === 0 ? ( + + +
+ + + +
+

+ No tasks found +

+

+ {searchTerm || statusFilter !== "all" || priorityFilter !== "all" + ? "Try adjusting your filters to see more tasks" + : "No tasks have been created yet"} +

+
+
+ ) : ( +
+ {filteredTasks.map((task) => ( + + +
+
+
+

+ {task.task_name} +

+ + {getStatusDisplayName(task.status)} + + + {task.priority} + + {task.task_type === "template" && ( + + Template - - {task.priority} - - {task.task_type === "template" && ( - - Template - - )} -
+ )} +
-
+
+
+

Project

+

+ {task.project_name} +

+
+ {task.wp && (
-

Project

+

WP

+

{task.wp}

+
+ )} + {task.plot && ( +
+

Plot

- {task.project_name} + {task.plot}

- {task.wp && ( -
-

WP

-

- {task.wp} -

-
- )} - {task.plot && ( -
-

Plot

-

- {task.plot} -

-
- )} -
- -
- - Added{" "} - {formatDistanceToNow(parseISO(task.date_added), { - addSuffix: true, - })} - - {task.max_wait_days > 0 && ( - Max wait: {task.max_wait_days} days - )} -
+ )}
-
- {task.status !== "completed" && ( - +
+ + Added{" "} + {formatDistanceToNow(parseISO(task.date_added), { + addSuffix: true, + })} + + {task.max_wait_days > 0 && ( + Max wait: {task.max_wait_days} days )} - - - - - -
- - - ))} -
- )} -
-
+ +
+ {task.status !== "completed" && ( + + )} + + + + + + +
+
+ + + ))}{" "} +
+ )} + ); } diff --git a/src/app/tasks/templates/page.js b/src/app/tasks/templates/page.js index 1e1337a..8bfe709 100644 --- a/src/app/tasks/templates/page.js +++ b/src/app/tasks/templates/page.js @@ -3,6 +3,8 @@ import Link from "next/link"; import { Card, CardHeader, CardContent } from "@/components/ui/Card"; import Button from "@/components/ui/Button"; import Badge from "@/components/ui/Badge"; +import PageContainer from "@/components/ui/PageContainer"; +import PageHeader from "@/components/ui/PageHeader"; export default function TaskTemplatesPage() { const templates = db @@ -12,15 +14,12 @@ export default function TaskTemplatesPage() { ` ) .all(); - return ( -
-
-
-
-

Task Templates

-

Manage reusable task templates

-
+ +
+ } + /> - {templates.length === 0 ? ( - - -
- - - -
-

- No task templates yet -

-

- Create reusable task templates to streamline your workflow -

- - - -
-
- ) : ( -
- {templates.map((template) => ( - + +
+ - -
-

- {template.name} -

- - {template.max_wait_days} days - -
- {template.description && ( -

- {template.description} -

- )}{" "} -
- - Template ID: {template.task_id} - -
- - - -
+

+ No task templates yet +

+

+ Create reusable task templates to streamline your workflow +

+ + + + + + ) : ( +
+ {templates.map((template) => ( + + +
+

+ {template.name} +

+ + {template.max_wait_days} days + +
+ {template.description && ( +

+ {template.description} +

+ )}{" "} +
+ + Template ID: {template.task_id} + +
+ + -
+ +
-
-
- ))} -
- )} -
-
+
+ + + ))}{" "} +
+ )} + ); } diff --git a/src/components/ui/FilterBar.js b/src/components/ui/FilterBar.js new file mode 100644 index 0000000..b44418e --- /dev/null +++ b/src/components/ui/FilterBar.js @@ -0,0 +1,30 @@ +"use client"; + +import { Select } from "./Input"; + +const FilterBar = ({ filters, className = "" }) => { + if (!filters || filters.length === 0) return null; + + return ( +
+ {filters.map((filter, index) => ( +
+ +
+ ))} +
+ ); +}; + +export default FilterBar; diff --git a/src/components/ui/PageContainer.js b/src/components/ui/PageContainer.js new file mode 100644 index 0000000..3e2b84c --- /dev/null +++ b/src/components/ui/PageContainer.js @@ -0,0 +1,11 @@ +"use client"; + +const PageContainer = ({ children, className = "" }) => { + return ( +
+
{children}
+
+ ); +}; + +export default PageContainer; diff --git a/src/components/ui/PageHeader.js b/src/components/ui/PageHeader.js new file mode 100644 index 0000000..22919e2 --- /dev/null +++ b/src/components/ui/PageHeader.js @@ -0,0 +1,15 @@ +"use client"; + +const PageHeader = ({ title, description, children, className = "" }) => { + return ( +
+
+

{title}

+ {description &&

{description}

} +
+ {children &&
{children}
} +
+ ); +}; + +export default PageHeader; diff --git a/src/components/ui/SearchBar.js b/src/components/ui/SearchBar.js new file mode 100644 index 0000000..8b11bdd --- /dev/null +++ b/src/components/ui/SearchBar.js @@ -0,0 +1,86 @@ +"use client"; + +import { Input } from "./Input"; + +const SearchBar = ({ + searchTerm, + onSearchChange, + placeholder = "Search...", + resultsCount = null, + resultsText = "items", + onClear = null, + filters = null, + className = "", +}) => { + return ( +
+
+
+
+ + + +
+ + {searchTerm && ( + + )} +
+ {filters} +
+ + {searchTerm && resultsCount !== null && ( +
+

+ Found{" "} + {resultsCount}{" "} + {resultsText} matching + + {" "} + "{searchTerm}" + +

+
+ )} +
+ ); +}; + +export default SearchBar; diff --git a/src/components/ui/States.js b/src/components/ui/States.js new file mode 100644 index 0000000..33b9e63 --- /dev/null +++ b/src/components/ui/States.js @@ -0,0 +1,113 @@ +"use client"; + +import { Card, CardContent } from "./Card"; +import Button from "./Button"; + +const LoadingSpinner = ({ size = "md" }) => { + const sizeClasses = { + sm: "w-4 h-4", + md: "w-8 h-8", + lg: "w-12 h-12", + }; + + return ( +
+
+
+ ); +}; + +const LoadingState = ({ message = "Loading...", className = "" }) => { + return ( +
+
+ +

{message}

+
+
+ ); +}; + +const EmptyState = ({ + icon, + title, + description, + actionLabel, + actionHref, + onAction, + className = "", +}) => { + return ( + + +
+ {icon || ( + + + + )} +
+

{title}

+ {description &&

{description}

} + {actionLabel && (actionHref || onAction) && ( + + )} +
+
+ ); +}; + +const ErrorState = ({ + title = "Something went wrong", + description = "We encountered an error. Please try again.", + onRetry, + className = "", +}) => { + return ( + + +
+ + + +
+

{title}

+

{description}

+ {onRetry && ( + + )} +
+
+ ); +}; + +export { LoadingSpinner, LoadingState, EmptyState, ErrorState };