Build premium data portal with React + Tailwind CSS

Frontend Features:
- Landing page with glassmorphism, animated counters, hero section
- Interactive data tables with search, sort, filter, CSV export
- Premium dark theme (navy + gold accents)
- Framer Motion animations and micro-interactions
- Responsive design with Inter + Playfair Display typography
- DataTable component with pagination and live search

Backend Updates:
- New API endpoints: /api/rates/per-diem, /api/rates/accommodations, /api/stats
- Database service methods for bulk data retrieval
- Production mode serves built React app from /dist/client
- Fallback to legacy HTML for development

Tech Stack:
- React 18 + TypeScript
- Vite 7 build tool
- Tailwind CSS 4 with @tailwindcss/postcss
- Framer Motion for animations
- Lucide React icons
- SQLite3 backend

Build Output:
- 351KB optimized JavaScript bundle
- 29KB CSS bundle
- Fully tree-shaken and minified
This commit is contained in:
2026-01-13 11:05:54 -05:00
parent 66b72d5f74
commit 4d915aa3ea
18 changed files with 1211 additions and 7 deletions

View File

@@ -80,13 +80,22 @@ app.use((req, res, next) => {
next();
});
// Serve static files from the current directory
app.use(
express.static(__dirname, {
maxAge: "1d",
// Serve React app (production build) or legacy static files
if (process.env.NODE_ENV === 'production' && require('fs').existsSync(path.join(__dirname, 'dist', 'client'))) {
// Serve React production build
app.use(express.static(path.join(__dirname, 'dist', 'client'), {
maxAge: '1d',
etag: true,
})
);
}));
} else {
// Serve legacy static files from the current directory
app.use(
express.static(__dirname, {
maxAge: "1d",
etag: true,
})
);
}
// Disable caching for HTML and JS files
app.use((req, res, next) => {
@@ -204,6 +213,50 @@ app.get(
}
})();
// ============ DATA PORTAL ENDPOINTS ============
/**
* Get all per-diem rates
* GET /api/rates/per-diem
*/
app.get("/api/rates/per-diem", async (req, res) => {
try {
const data = await dbService.getAllPerDiemRates();
res.json({ success: true, data, count: data.length });
} catch (error) {
logger.error("Error fetching per-diem rates:", error);
res.status(500).json({ error: "Failed to fetch per-diem rates" });
}
});
/**
* Get all accommodation rates
* GET /api/rates/accommodations
*/
app.get("/api/rates/accommodations", async (req, res) => {
try {
const data = await dbService.getAllAccommodations();
res.json({ success: true, data, count: data.length });
} catch (error) {
logger.error("Error fetching accommodation rates:", error);
res.status(500).json({ error: "Failed to fetch accommodation rates" });
}
});
/**
* Get portal statistics
* GET /api/stats
*/
app.get("/api/stats", async (req, res) => {
try {
const stats = await dbService.getStats();
res.json({ success: true, stats });
} catch (error) {
logger.error("Error fetching stats:", error);
res.status(500).json({ error: "Failed to fetch statistics" });
}
});
// ============ DATABASE SEARCH ENDPOINTS ============
/**