Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a6728ee4e | ||
|
|
b52e21838c | ||
|
|
dc7b1b8c84 | ||
|
|
6fdaad5b0c | ||
|
|
8cf3e379dc | ||
|
|
0be71523a4 | ||
|
|
8416a9daf7 | ||
|
|
a3112efee0 |
16
README.md
16
README.md
@@ -1,6 +1,6 @@
|
||||
# TurfTracker - Professional Lawn Care Management
|
||||
# TurfTracking - Professional Lawn Care Management
|
||||
|
||||
TurfTracker is a comprehensive web application designed for homeowners to track and manage their lawn care activities including fertilizer applications, weed control, mowing schedules, and equipment management.
|
||||
TurfTracking is a comprehensive web application designed for homeowners to track and manage their lawn care activities including fertilizer applications, weed control, mowing schedules, and equipment management.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -76,7 +76,7 @@ TurfTracker is a comprehensive web application designed for homeowners to track
|
||||
|
||||
- Docker and Docker Compose
|
||||
- Traefik reverse proxy running with `proxy` network
|
||||
- DNS pointing `turftracker.kaspers.us` to your server
|
||||
- DNS pointing `turftracker.kaspers.us` to your server (or your chosen domain)
|
||||
- Git
|
||||
|
||||
### Traefik Configuration Notes
|
||||
@@ -92,7 +92,7 @@ This application is pre-configured for deployment behind Traefik with:
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd turftracker
|
||||
cd turftracking
|
||||
```
|
||||
|
||||
2. **Environment Configuration**
|
||||
@@ -235,8 +235,8 @@ If you have an Authentik instance for SSO:
|
||||
|
||||
3. **In Authentik Applications:**
|
||||
- Create new Application
|
||||
- Set Name: `TurfTracker`
|
||||
- Set Slug: `turftracker`
|
||||
- Set Name: `TurfTracking`
|
||||
- Set Slug: `turftracking`
|
||||
- Set Provider: (select the provider created above)
|
||||
|
||||
**Scopes Required:** `openid profile email`
|
||||
@@ -244,7 +244,7 @@ If you have an Authentik instance for SSO:
|
||||
## Application Structure
|
||||
|
||||
```
|
||||
turftracker/
|
||||
turftracking/
|
||||
├── backend/ # Node.js API server
|
||||
│ ├── src/
|
||||
│ │ ├── routes/ # API endpoints
|
||||
@@ -373,4 +373,4 @@ For questions or issues:
|
||||
- Use strong JWT secrets
|
||||
- Enable HTTPS in production
|
||||
- Regularly update dependencies
|
||||
- Follow security best practices for API key management
|
||||
- Follow security best practices for API key management
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "turftracker-backend",
|
||||
"name": "turftracking-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Backend API for TurfTracker lawn care management application",
|
||||
"description": "Backend API for TurfTracking lawn care management application",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
"start": "node src/app.js",
|
||||
@@ -38,6 +38,6 @@
|
||||
"fertilizer",
|
||||
"turf management"
|
||||
],
|
||||
"author": "TurfTracker Team",
|
||||
"author": "TurfTracking Team",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ process.on('SIGINT', () => {
|
||||
});
|
||||
|
||||
app.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`TurfTracker API server running on port ${PORT}`);
|
||||
console.log(`TurfTracking API server running on port ${PORT}`);
|
||||
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||
});
|
||||
// Disable etags and set no-store by default to avoid stale cached API responses
|
||||
|
||||
@@ -13,41 +13,24 @@ const calculatePolygonArea = (coordinates) => {
|
||||
console.log('Invalid coordinates for area calculation:', coordinates);
|
||||
return 0;
|
||||
}
|
||||
|
||||
console.log('Calculating area for coordinates:', coordinates);
|
||||
|
||||
// Shoelace formula for polygon area
|
||||
// Coordinates are in [latitude, longitude] format (not GeoJSON standard)
|
||||
let area = 0;
|
||||
const n = coordinates.length;
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
const j = (i + 1) % n;
|
||||
// coordinates[i][0] = latitude, coordinates[i][1] = longitude
|
||||
area += coordinates[i][1] * coordinates[j][0]; // lng * lat
|
||||
area -= coordinates[j][1] * coordinates[i][0]; // lng * lat
|
||||
area += coordinates[i][1] * coordinates[j][0];
|
||||
area -= coordinates[j][1] * coordinates[i][0];
|
||||
}
|
||||
|
||||
area = Math.abs(area) / 2;
|
||||
console.log('Raw shoelace area (in deg²):', area);
|
||||
|
||||
// Convert from decimal degrees to square feet
|
||||
// coordinates are [latitude, longitude] format
|
||||
const avgLat = coordinates.reduce((sum, coord) => sum + coord[0], 0) / n; // coord[0] is latitude
|
||||
const avgLat = coordinates.reduce((sum, coord) => sum + coord[0], 0) / n;
|
||||
console.log('Average latitude:', avgLat);
|
||||
|
||||
// More accurate conversion using Haversine-based approach
|
||||
const latInRadians = avgLat * Math.PI / 180;
|
||||
const metersPerDegreeLat = 111320; // meters per degree latitude (constant)
|
||||
const metersPerDegreeLng = 111320 * Math.cos(latInRadians); // meters per degree longitude (varies with latitude)
|
||||
|
||||
// Convert to square meters, then to square feet
|
||||
const metersPerDegreeLat = 111320;
|
||||
const metersPerDegreeLng = 111320 * Math.cos(avgLat * Math.PI / 180);
|
||||
const areaInSquareMeters = area * metersPerDegreeLat * metersPerDegreeLng;
|
||||
const areaInSquareFeet = areaInSquareMeters * 10.7639; // 1 m² = 10.7639 ft²
|
||||
|
||||
const areaInSquareFeet = areaInSquareMeters * 10.7639;
|
||||
console.log('Area in square meters:', areaInSquareMeters);
|
||||
console.log('Area in square feet:', areaInSquareFeet);
|
||||
|
||||
return areaInSquareFeet;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "turftracker-frontend",
|
||||
"name": "turftracking-frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "Frontend React application for TurfTracker lawn care management",
|
||||
"description": "Frontend React application for TurfTracking lawn care management",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
|
||||
BIN
frontend/public/brand/apple-touch-icon.png
Normal file
BIN
frontend/public/brand/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
frontend/public/brand/favicon-16.png
Normal file
BIN
frontend/public/brand/favicon-16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 670 B |
BIN
frontend/public/brand/favicon-32.png
Normal file
BIN
frontend/public/brand/favicon-32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
frontend/public/brand/logo.png
Normal file
BIN
frontend/public/brand/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
@@ -2,11 +2,16 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<!-- Favicon & brand icons -->
|
||||
<!-- Use PNG favicon for broad compatibility -->
|
||||
<link rel="icon" type="image/png" href="%PUBLIC_URL%/brand/logo.png" />
|
||||
<!-- Precise favicon sizes -->
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/brand/favicon-32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/brand/favicon-16.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#059669" />
|
||||
<meta name="description" content="TurfTracker - Professional lawn care management and tracking application" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<meta name="description" content="TurfTracking - Professional lawn care management and tracking application" />
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/brand/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
||||
<!-- Google Fonts -->
|
||||
@@ -17,9 +22,9 @@
|
||||
<!-- Progressive Web App meta tags -->
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="TurfTracker">
|
||||
<meta name="apple-mobile-web-app-title" content="TurfTracking">
|
||||
|
||||
<title>TurfTracker - Lawn Care Management</title>
|
||||
<title>TurfTracking - Lawn Care Management</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
@@ -29,7 +34,7 @@
|
||||
<div id="loading-indicator" class="fixed inset-0 flex items-center justify-center bg-green-50 z-50">
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto mb-4"></div>
|
||||
<h2 class="text-xl font-semibold text-green-800">TurfTracker</h2>
|
||||
<h2 class="text-xl font-semibold text-green-800">TurfTracking</h2>
|
||||
<p class="text-green-600">Loading your lawn care dashboard...</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,4 +51,4 @@
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
20
frontend/public/manifest.json
Normal file
20
frontend/public/manifest.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"short_name": "TurfTracking",
|
||||
"name": "TurfTracking",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/brand/logo.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "/brand/logo.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"theme_color": "#059669",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -20,7 +20,10 @@ const AuthLayout = ({ children }) => {
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">TurfTracker</h1>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<img src="/brand/logo.png" alt="TurfTracking" className="h-8 w-8" />
|
||||
<h1 className="text-3xl font-bold text-gray-900">TurfTracking</h1>
|
||||
</div>
|
||||
<p className="text-lg text-gray-600">Professional Lawn Care Management</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,4 +43,4 @@ const AuthLayout = ({ children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthLayout;
|
||||
export default AuthLayout;
|
||||
|
||||
@@ -144,8 +144,10 @@ const Layout = ({ children }) => {
|
||||
onClick={() => setSidebarOpen(false)}
|
||||
/>
|
||||
<div className="fixed inset-y-0 left-0 flex w-64 flex-col bg-white shadow-xl">
|
||||
<div className="flex h-16 items-center justify-between px-6 border-b border-gray-200">
|
||||
<h1 className="text-xl font-bold text-primary-600">TurfTracker</h1>
|
||||
<div className="relative flex h-20 items-center px-6 border-b border-gray-200">
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<img src="/brand/logo.png" alt="TurfTracking" className="h-16 w-16 object-contain" />
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
@@ -243,8 +245,8 @@ const Layout = ({ children }) => {
|
||||
{/* Desktop sidebar */}
|
||||
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
|
||||
<div className="flex min-h-0 flex-1 flex-col bg-white border-r border-gray-200">
|
||||
<div className="flex h-16 items-center px-6 border-b border-gray-200">
|
||||
<h1 className="text-xl font-bold text-primary-600">TurfTracker</h1>
|
||||
<div className="flex h-24 items-center justify-center px-6 border-b border-gray-200">
|
||||
<img src="/brand/logo.png" alt="TurfTracking" className="h-20 w-20 object-contain" />
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 space-y-1 px-4 py-6">
|
||||
@@ -328,7 +330,7 @@ const Layout = ({ children }) => {
|
||||
<div className="lg:pl-64">
|
||||
{/* Top bar */}
|
||||
<div className="sticky top-0 z-10 bg-white border-b border-gray-200 lg:hidden">
|
||||
<div className="flex h-16 items-center justify-between px-4">
|
||||
<div className="relative flex h-16 items-center justify-between px-4">
|
||||
<button
|
||||
type="button"
|
||||
className="text-gray-500 hover:text-gray-600"
|
||||
@@ -336,9 +338,10 @@ const Layout = ({ children }) => {
|
||||
>
|
||||
<Bars3Icon className="h-6 w-6" />
|
||||
</button>
|
||||
|
||||
<h1 className="text-lg font-semibold text-gray-900">TurfTracker</h1>
|
||||
|
||||
{/* Centered logo (click-through so it won't block buttons) */}
|
||||
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
|
||||
<img src="/brand/logo.png" alt="TurfTracking" className="h-12 w-12 object-contain" />
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
<button className="text-gray-400 hover:text-gray-500">
|
||||
<BellIcon className="h-6 w-6" />
|
||||
|
||||
Reference in New Issue
Block a user