diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..d6f90e8 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,314 @@ +# App Development Roadmap + +## Current Application Assessment + +This is a solid Next.js-based project management system for construction/engineering projects with the following existing features: + +### βœ… Currently Implemented + +- **Project Management**: CRUD operations for projects with detailed information +- **Contract Management**: Contract creation, linking to projects, status tracking +- **Task Management**: Template-based and custom tasks with status tracking +- **Dashboard**: Statistics overview, recent projects, quick actions +- **Map Integration**: Leaflet maps with multiple layer support (OpenStreetMap, Polish Geoportal) +- **Database**: SQLite with better-sqlite3, well-structured schema +- **UI/UX**: Modern Tailwind CSS interface with responsive design +- **API Structure**: RESTful API endpoints for all entities +- **Docker Support**: Containerized development and deployment +- **Testing Setup**: Jest, Playwright, Testing Library configured + +--- + +## Critical Missing Features for App + +### πŸ” **1. Authentication & Authorization (HIGH PRIORITY)** + +**Current State**: No authentication system +**Required**: + +- User login/logout system +- Role-based access control (Admin, Project Manager, User, Read-only) +- Session management +- Password reset functionality +- User management interface +- API route protection + +**Implementation Options**: + +- NextAuth.js with database sessions +- Auth0 integration +- Custom JWT implementation + +### πŸ”’ **2. Security & Data Protection (HIGH PRIORITY)** + +**Current State**: No security measures +**Required**: + +- Input validation and sanitization +- SQL injection protection (prepared statements are good start) +- XSS protection +- CSRF protection +- Rate limiting +- Environment variable security +- Data encryption for sensitive fields +- Audit logging + +### πŸ“Š **3. Advanced Reporting & Analytics (MEDIUM PRIORITY)** + +**Current State**: Basic dashboard statistics +**Required**: + +- Project timeline reports +- Budget tracking and financial reports +- Task completion analytics +- Project performance metrics +- Export to PDF/Excel +- Custom report builder +- Charts and graphs (Chart.js, D3.js) + +### πŸ’Ύ **4. Backup & Data Management (HIGH PRIORITY)** + +**Current State**: Single SQLite file +**Required**: + +- Automated database backups +- Data export/import functionality +- Database migration system +- Data archiving for old projects +- Recovery procedures + +### πŸ“± **5. Mobile Responsiveness & PWA (MEDIUM PRIORITY)** + +**Current State**: Basic responsive design +**Required**: + +- Progressive Web App capabilities +- Offline functionality +- Mobile-optimized interface +- Push notifications +- App manifest and service workers + +### πŸ”— **6. API & Integration (MEDIUM PRIORITY)** + +**Current State**: Internal REST API only +**Required**: + +- External API integrations (accounting software, CRM) +- Webhook support +- API documentation (Swagger/OpenAPI) +- API versioning +- Third-party service integrations + +### πŸ“§ **7. Communication & Notifications (MEDIUM PRIORITY)** + +**Current State**: No notification system +**Required**: + +- Email notifications for deadlines, status changes +- In-app notifications +- SMS notifications (optional) +- Email templates +- Notification preferences per user + +### πŸ“‹ **8. Enhanced Project Management (MEDIUM PRIORITY)** + +**Current State**: Basic project tracking +**Required**: + +- Gantt charts for project timelines +- Resource allocation and management +- Budget tracking per project +- Document attachment system +- Project templates +- Milestone tracking +- Dependencies between tasks + +### πŸ” **9. Search & Filtering (LOW PRIORITY)** + +**Current State**: Basic search implemented +**Required**: + +- Advanced search with filters +- Full-text search +- Saved search queries +- Search autocomplete +- Global search across all entities + +### ⚑ **10. Performance & Scalability (MEDIUM PRIORITY)** + +**Current State**: Good for small-medium datasets +**Required**: + +- Database optimization and indexing +- Caching layer (Redis) +- Image optimization +- Lazy loading +- Pagination for large datasets +- Background job processing + +### πŸ“ **11. Documentation & Help System (LOW PRIORITY)** + +**Current State**: README.md only +**Required**: + +- User manual/documentation +- In-app help system +- API documentation +- Video tutorials +- FAQ section + +### πŸ§ͺ **12. Testing & Quality Assurance (MEDIUM PRIORITY)** + +**Current State**: Testing frameworks set up but no tests +**Required**: + +- Unit tests for all components +- Integration tests for API endpoints +- E2E tests for critical user flows +- Performance testing +- Accessibility testing +- Code coverage reports + +### πŸš€ **13. DevOps & Deployment (MEDIUM PRIORITY)** + +**Current State**: Docker setup exists +**Required**: + +- CI/CD pipeline +- Production deployment strategy +- Environment management (dev, staging, prod) +- Monitoring and logging +- Error tracking (Sentry) +- Health checks + +### 🎨 **14. UI/UX Improvements (LOW PRIORITY)** + +**Current State**: Clean, functional interface +**Required**: + +- Dark mode support +- Customizable themes +- Accessibility improvements (WCAG compliance) +- Keyboard navigation +- Better loading states +- Drag and drop functionality + +--- + +## Implementation Priority Levels + +### Phase 1: Security & Stability (Weeks 1-4) + +1. Authentication system +2. Authorization and role management +3. Input validation and security +4. Backup system +5. Basic testing coverage + +### Phase 2: Core Features (Weeks 5-8) + +1. Advanced reporting +2. Mobile optimization +3. Notification system +4. Enhanced project management features + +### Phase 3: Professional Features (Weeks 9-12) + +1. API integrations +2. Performance optimization +3. Advanced UI features +4. Documentation + +### Phase 4: Scale & Polish (Weeks 13-16) + +1. DevOps improvements +2. Comprehensive testing +3. Advanced analytics +4. Third-party integrations + +--- + +## Immediate Next Steps (Recommended Order) + +1. **Set up Authentication** + + - Install NextAuth.js or implement custom auth + - Create user management system + - Add login/logout functionality + +2. **Implement Input Validation** + + - Add Zod or Joi for schema validation + - Protect all API endpoints + - Add error handling + +3. **Create Backup System** + + - Implement database backup scripts + - Set up automated backups + - Create recovery procedures + +4. **Add Basic Tests** + + - Write unit tests for critical functions + - Add integration tests for API routes + - Set up test automation + +5. **Implement Reporting** + - Add Chart.js for visualizations + - Create project timeline reports + - Add export functionality + +--- + +## Technology Recommendations + +### Authentication + +- **NextAuth.js** - For easy authentication setup +- **Prisma** - For better database management (optional upgrade from better-sqlite3) + +### Security + +- **Zod** - Runtime type checking and validation +- **bcryptjs** - Password hashing +- **rate-limiter-flexible** - Rate limiting + +### Reporting + +- **Chart.js** or **Recharts** - Data visualization +- **jsPDF** - PDF generation +- **xlsx** - Excel export + +### Notifications + +- **Nodemailer** - Email sending +- **Socket.io** - Real-time notifications + +### Testing + +- **MSW** - API mocking for tests +- **Testing Library** - Component testing +- **Faker.js** - Test data generation + +--- + +## Current Strengths + +1. **Well-structured codebase** with clear separation of concerns +2. **Modern tech stack** (Next.js, React, Tailwind) +3. **Good database design** with proper relationships +4. **Responsive UI** with professional appearance +5. **Docker support** for easy deployment +6. **Map integration** with multiple layers +7. **Modular components** that are reusable + +--- + +## Estimated Development Time + +- **Minimum Viable Professional App**: 8-12 weeks +- **Full-featured Professional App**: 16-20 weeks +- **Enterprise-grade Application**: 24-30 weeks + +This assessment is based on a single developer working full-time. Team development could reduce these timelines significantly. diff --git a/scripts/create-additional-test-data.js b/scripts/create-additional-test-data.js index 074dadc..dbce03e 100644 --- a/scripts/create-additional-test-data.js +++ b/scripts/create-additional-test-data.js @@ -1,21 +1,25 @@ -import db from '../src/lib/db.js'; +import db from "../src/lib/db.js"; // Create another test project with coordinates in a different location -const project = db.prepare(` +const project = db + .prepare( + ` INSERT INTO projects ( contract_id, project_name, project_number, address, city, coordinates, project_type, project_status ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) -`).run( - 3, // Using the existing contract - 'Test Project in Warsaw', - '2/TEST/2025', - 'Warsaw Center, Poland', - 'Warsaw', - '52.2297,21.0122', // Warsaw coordinates - 'construction', - 'in_progress_construction' -); +` + ) + .run( + 2, // Using the test contract we just created + "Test Project in Warsaw", + "2/TEST/2025", + "Warsaw Center, Poland", + "Warsaw", + "52.2297,21.0122", // Warsaw coordinates + "construction", + "in_progress_construction" + ); -console.log('Additional test project created!'); -console.log('Project ID:', project.lastInsertRowid); +console.log("Additional test project created!"); +console.log("Project ID:", project.lastInsertRowid); diff --git a/scripts/create-diverse-test-data.js b/scripts/create-diverse-test-data.js new file mode 100644 index 0000000..2aa6cd2 --- /dev/null +++ b/scripts/create-diverse-test-data.js @@ -0,0 +1,88 @@ +import db from "../src/lib/db.js"; + +// Create projects with different statuses and locations around Poland +const testProjects = [ + { + name: "Gdansk Port Project", + address: "Port of Gdansk, Gdansk", + city: "Gdansk", + coordinates: "54.3520,18.6466", + status: "registered", + type: "design", + }, + { + name: "Wroclaw Shopping Center", + address: "Market Square, Wroclaw", + city: "Wroclaw", + coordinates: "51.1079,17.0385", + status: "in_progress_design", + type: "design+construction", + }, + { + name: "Poznan Office Complex", + address: "Old Town, Poznan", + city: "Poznan", + coordinates: "52.4064,16.9252", + status: "in_progress_construction", + type: "construction", + }, + { + name: "Lodz Residential Development", + address: "City Center, Lodz", + city: "Lodz", + coordinates: "51.7592,19.4600", + status: "fulfilled", + type: "design+construction", + }, + { + name: "Katowice Industrial Park", + address: "Silesia Region, Katowice", + city: "Katowice", + coordinates: "50.2649,19.0238", + status: "in_progress_design", + type: "design", + }, + { + name: "Lublin University Campus", + address: "University District, Lublin", + city: "Lublin", + coordinates: "51.2465,22.5684", + status: "fulfilled", + type: "construction", + }, +]; + +let projectCounter = 3; // Starting from 3 since we already have projects 1 and 2 + +testProjects.forEach((project) => { + try { + const result = db + .prepare( + ` + INSERT INTO projects ( + contract_id, project_name, project_number, address, city, coordinates, + project_type, project_status + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ` + ) + .run( + 2, // Use the test contract + project.name, + `${projectCounter}/TEST/2025`, + project.address, + project.city, + project.coordinates, + project.type, + project.status + ); + + console.log( + `Created project: ${project.name} (ID: ${result.lastInsertRowid}) - Status: ${project.status}` + ); + projectCounter++; + } catch (error) { + console.error(`Error creating project ${project.name}:`, error.message); + } +}); + +console.log("Diverse test projects created successfully!"); diff --git a/src/app/projects/[id]/page.js b/src/app/projects/[id]/page.js index 7c676cd..45a7d07 100644 --- a/src/app/projects/[id]/page.js +++ b/src/app/projects/[id]/page.js @@ -365,7 +365,7 @@ export default async function ProjectViewPage({ params }) { Edit Project - + {" "} + @@ -395,16 +417,38 @@ export default async function ProjectViewPage({ params }) { {/* Project Location Map */} {project.coordinates && (
+ {" "} -

- Project Location -

+
+

+ Project Location +

+ + + +
{ + const allActive = Object.values(statusFilters).every((value) => value); + const newState = allActive + ? Object.keys(statusFilters).reduce( + (acc, key) => ({ ...acc, [key]: false }), + {} + ) + : Object.keys(statusFilters).reduce( + (acc, key) => ({ ...acc, [key]: true }), + {} + ); + setStatusFilters(newState); + }; + + // Toggle status filter + const toggleStatusFilter = (status) => { + setStatusFilters((prev) => ({ + ...prev, + [status]: !prev[status], + })); + }; // Hide navigation and ensure full-screen layout useEffect(() => { // Hide navigation bar for full-screen experience @@ -76,20 +129,24 @@ export default function ProjectsMapPage() { setLoading(false); }); }, []); - - // Convert projects to map markers + // Convert projects to map markers with filtering const markers = projects .filter((project) => project.coordinates) + .filter((project) => statusFilters[project.project_status] !== false) .map((project) => { const [lat, lng] = project.coordinates .split(",") .map((coord) => parseFloat(coord.trim())); - if (isNaN(lat) || isNaN(lng)) { return null; } + + const statusInfo = + statusConfig[project.project_status] || statusConfig.registered; + return { position: [lat, lng], + color: statusInfo.color, popup: (
@@ -135,7 +192,6 @@ export default function ProjectsMapPage() {
)} -
{project.wp && (
@@ -149,29 +205,15 @@ export default function ProjectsMapPage() { {project.plot}
)} -
- +
{" "} {project.project_status && (
Status: - {project.project_status === "registered" - ? "Zarejestr." - : project.project_status === "in_progress_design" - ? "W real. (P)" - : project.project_status === "in_progress_construction" - ? "W real. (R)" - : project.project_status === "fulfilled" - ? "ZakoΕ„czony" - : project.project_status} + {statusInfo.shortLabel}
)} @@ -223,15 +265,94 @@ export default function ProjectsMapPage() { } return (
+ {" "} {/* Floating Header with Controls */}
-
-
-

- Projects Map -

-
- {markers.length} of {projects.length} projects with coordinates +
+
+
+

+ Projects Map +

+
+ {markers.length} of {projects.length} projects with coordinates +
+
+
+ {/* Status Filter Panel */} +
+
+ + Filters: + + + {/* Toggle All Button */} + + + {/* Individual Status Filters */} + {Object.entries(statusConfig).map(([status, config]) => { + const isActive = statusFilters[status]; + const projectCount = projects.filter( + (p) => p.project_status === status && p.coordinates + ).length; + + return ( + + ); + })}
@@ -278,37 +399,79 @@ export default function ProjectsMapPage() {
-
- +
{" "} {/* Stats Panel - Bottom Left */} {markers.length > 0 && (
-
+
- {markers.length} projects shown + {markers.length} of{" "} + {projects.filter((p) => p.coordinates).length} projects shown
- {projects.length > markers.length && ( -
+ + {/* Status breakdown */} +
+ {Object.entries(statusConfig).map(([status, config]) => { + const totalCount = projects.filter( + (p) => p.project_status === status && p.coordinates + ).length; + const visibleCount = markers.filter((m) => { + const project = projects.find( + (p) => + p.coordinates && + p.coordinates + .split(",") + .map((c) => parseFloat(c.trim())) + .toString() === m.position.toString() + ); + return project && project.project_status === status; + }).length; + + if (totalCount === 0) return null; + + return ( +
+
+ + {config.shortLabel}:{" "} + {statusFilters[status] ? visibleCount : 0}/{totalCount} + +
+ ); + })} +
+ + {projects.length > projects.filter((p) => p.coordinates).length && ( +
- - {projects.length - markers.length} missing coordinates + + {projects.length - + projects.filter((p) => p.coordinates).length}{" "} + missing coordinates
)}
)} - {/* Help Panel - Bottom Right */}
Click markers for details β€’ Use πŸ“š to switch layers
- {/* Full Screen Map */} {markers.length === 0 ? (
diff --git a/src/components/ui/LeafletMap.js b/src/components/ui/LeafletMap.js index e83efdb..068b4cb 100644 --- a/src/components/ui/LeafletMap.js +++ b/src/components/ui/LeafletMap.js @@ -1,74 +1,107 @@ "use client"; -import { MapContainer, TileLayer, Marker, Popup, LayersControl } from 'react-leaflet'; -import 'leaflet/dist/leaflet.css'; -import { useEffect } from 'react'; -import { mapLayers } from './mapLayers'; +import { + MapContainer, + TileLayer, + Marker, + Popup, + LayersControl, +} from "react-leaflet"; +import "leaflet/dist/leaflet.css"; +import { useEffect } from "react"; +import { mapLayers } from "./mapLayers"; // Fix for default markers in react-leaflet const fixLeafletIcons = () => { - if (typeof window !== 'undefined') { - const L = require('leaflet'); - - delete L.Icon.Default.prototype._getIconUrl; - L.Icon.Default.mergeOptions({ - iconRetinaUrl: '/leaflet/marker-icon-2x.png', - iconUrl: '/leaflet/marker-icon.png', - shadowUrl: '/leaflet/marker-shadow.png', - }); - } + if (typeof window !== "undefined") { + const L = require("leaflet"); + + delete L.Icon.Default.prototype._getIconUrl; + L.Icon.Default.mergeOptions({ + iconRetinaUrl: "/leaflet/marker-icon-2x.png", + iconUrl: "/leaflet/marker-icon.png", + shadowUrl: "/leaflet/marker-shadow.png", + }); + } }; -export default function EnhancedLeafletMap({ - center, - zoom = 13, - markers = [], - showLayerControl = true, - defaultLayer = 'OpenStreetMap' +// Create colored marker icons +const createColoredMarkerIcon = (color) => { + if (typeof window !== "undefined") { + const L = require("leaflet"); + + return new L.Icon({ + iconUrl: `data:image/svg+xml;base64,${btoa(` + + + + + `)}`, + shadowUrl: "/leaflet/marker-shadow.png", + iconSize: [25, 41], + iconAnchor: [12, 41], + popupAnchor: [1, -34], + shadowSize: [41, 41], + }); + } + return null; +}; + +export default function EnhancedLeafletMap({ + center, + zoom = 13, + markers = [], + showLayerControl = true, + defaultLayer = "OpenStreetMap", }) { - useEffect(() => { - fixLeafletIcons(); - }, []); + useEffect(() => { + fixLeafletIcons(); + }, []); - const { BaseLayer } = LayersControl; + const { BaseLayer } = LayersControl; - return ( - - {showLayerControl ? ( - - {mapLayers.base.map((layer, index) => ( - - - - ))} - - ) : ( - // Default layer when no layer control - - )} - - {markers.map((marker, index) => ( - - {marker.popup && {marker.popup}} - - ))} - - ); + return ( + + {showLayerControl ? ( + + {mapLayers.base.map((layer, index) => ( + + + + ))} + + ) : ( + // Default layer when no layer control + + )}{" "} + {markers.map((marker, index) => ( + + {marker.popup && {marker.popup}} + + ))} + + ); } diff --git a/src/components/ui/ProjectMap.js b/src/components/ui/ProjectMap.js index 2dd0847..89959f8 100644 --- a/src/components/ui/ProjectMap.js +++ b/src/components/ui/ProjectMap.js @@ -1,65 +1,173 @@ "use client"; -import { useEffect, useState } from 'react'; -import dynamic from 'next/dynamic'; +import { useEffect, useState } from "react"; +import dynamic from "next/dynamic"; // Dynamically import the map component to avoid SSR issues -const DynamicMap = dynamic(() => import('./LeafletMap'), { - ssr: false, - loading: () =>
- Loading map... -
+const DynamicMap = dynamic(() => import("./LeafletMap"), { + ssr: false, + loading: () => ( +
+ Loading map... +
+ ), }); -export default function ProjectMap({ - coordinates, - projectName, - showLayerControl = true, - mapHeight = 'h-64', - defaultLayer = 'OpenStreetMap' +export default function ProjectMap({ + coordinates, + projectName, + projectStatus = "registered", + showLayerControl = true, + mapHeight = "h-64", + defaultLayer = "OpenStreetMap", }) { - const [coords, setCoords] = useState(null); + const [coords, setCoords] = useState(null); - useEffect(() => { - if (coordinates) { - // Parse coordinates string (e.g., "49.622958,20.629562") - const [lat, lng] = coordinates.split(',').map(coord => parseFloat(coord.trim())); - - if (!isNaN(lat) && !isNaN(lng)) { - setCoords({ lat, lng }); - } - } - }, [coordinates]); + // Status configuration matching the main map + const statusConfig = { + registered: { color: "#6B7280", label: "Registered" }, + in_progress_design: { color: "#3B82F6", label: "In Progress (Design)" }, + in_progress_construction: { + color: "#F59E0B", + label: "In Progress (Construction)", + }, + fulfilled: { color: "#10B981", label: "Completed" }, + }; - if (!coords) { - return null; - } + useEffect(() => { + if (coordinates) { + // Parse coordinates string (e.g., "49.622958,20.629562") + const [lat, lng] = coordinates + .split(",") + .map((coord) => parseFloat(coord.trim())); - return ( -
-
-

Project Location

- {showLayerControl && ( -
- Use the layer control (πŸ“š) to switch map views -
- )} -
-
- -
-

- Coordinates: {coords.lat.toFixed(6)}, {coords.lng.toFixed(6)} -

-
- ); + if (!isNaN(lat) && !isNaN(lng)) { + setCoords({ lat, lng }); + } + } + }, [coordinates]); + if (!coords) { + return ( +
+
+

+ Project Location +

+
No coordinates available
+
+
+
+ + + +

Map unavailable

+

No coordinates provided

+
+
+
+ ); + } + + const statusInfo = statusConfig[projectStatus] || statusConfig.registered; + + return ( +
+
+
+

+ Project Location +

+
+
+ {showLayerControl && ( +
+ Use the layer control (πŸ“š) to switch map views +
+ )} +
+
+ +
+

+ {projectName || "Project Location"} +

+ + {statusInfo.label} + +
+
+
+ + + + + + {coords.lat.toFixed(6)}, {coords.lng.toFixed(6)} + +
+
+
+ ), + }, + ]} + showLayerControl={showLayerControl} + defaultLayer={defaultLayer} + /> +
+
+

+ Coordinates: {coords.lat.toFixed(6)}, {coords.lng.toFixed(6)} +

+
+
+ {statusInfo.label} +
+
+
+ ); } diff --git a/test-mobile.html b/test-mobile.html new file mode 100644 index 0000000..d443c31 --- /dev/null +++ b/test-mobile.html @@ -0,0 +1,41 @@ + + + + + + Map Test - Mobile View + + + +
+
+ Mobile View Test (400px width)
+ Testing responsive behavior of the projects map +
+ +
+ +