feat: Initialize License Tracker Pro v1.2 project

Adds initial project structure, dependencies, and basic configuration for the License Tracker Pro v1.2 application. This includes setting up Vite for the build process, defining core types, and incorporating essential UI components like icons and a timeline. The project is now ready for development.
This commit is contained in:
Philip
2026-01-26 18:55:43 -08:00
parent d4895c6d81
commit e045354892
56 changed files with 3410 additions and 8 deletions
+118
View File
@@ -0,0 +1,118 @@
import React, { useMemo } from 'react';
import { License } from '../types';
import { BellIcon, ExclamationIcon, CalendarIcon } from './Icons';
interface DashboardAlertsProps {
licenses: License[];
alertDays: number;
}
const DashboardAlerts: React.FC<DashboardAlertsProps> = ({ licenses = [], alertDays = 45 }) => {
const alerts = useMemo(() => {
if (!Array.isArray(licenses)) return [];
const today = new Date();
today.setHours(0, 0, 0, 0);
const futureThreshold = new Date(today);
futureThreshold.setDate(today.getDate() + alertDays);
const upcomingEvents: Array<{
id: string;
licenseName: string;
date: Date;
type: 'Renewal' | 'Expiration';
daysRemaining: number;
}> = [];
licenses.forEach(lic => {
if (!lic) return;
// Check Contract End Date
if (lic.endDate) {
const endDate = new Date(lic.endDate);
if (!isNaN(endDate.getTime())) {
endDate.setHours(0, 0, 0, 0);
if (endDate >= today && endDate <= futureThreshold) {
const diffTime = endDate.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
upcomingEvents.push({
id: lic.id,
licenseName: lic.licenseName,
date: endDate,
type: 'Expiration',
daysRemaining: diffDays
});
}
}
}
// Check Renewals
if (Array.isArray(lic.renewals)) {
lic.renewals.forEach(r => {
if (!r || !r.renewalDate) return;
const renewalDate = new Date(r.renewalDate);
if (!isNaN(renewalDate.getTime())) {
renewalDate.setHours(0, 0, 0, 0);
if (renewalDate >= today && renewalDate <= futureThreshold) {
const diffTime = renewalDate.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
upcomingEvents.push({
id: lic.id,
licenseName: lic.licenseName,
date: renewalDate,
type: 'Renewal',
daysRemaining: diffDays
});
}
}
});
}
});
return upcomingEvents.sort((a, b) => a.date.getTime() - b.date.getTime()).slice(0, 5);
}, [licenses, alertDays]);
if (alerts.length === 0) return null;
return (
<div className="bg-gradient-to-r from-slate-800 to-slate-900 rounded-lg shadow-lg mb-8 border-l-4 border-cyan-500 overflow-hidden">
<div className="p-4 sm:p-5">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-bold text-gray-100 flex items-center">
<BellIcon className="w-5 h-5 text-cyan-400 mr-2" />
Upcoming Expirations & Renewals
</h2>
<span className="text-xs font-medium px-2 py-1 bg-cyan-900/50 text-cyan-300 rounded-full border border-cyan-700/50">
Next {alertDays} Days
</span>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-3">
{alerts.map((alert, idx) => (
<div key={`${alert.id}-${idx}`} className="bg-slate-800/50 border border-slate-700 rounded p-3 flex flex-col justify-between hover:bg-slate-700/50 transition-colors group">
<div className="flex justify-between items-start mb-2">
<div className={`p-1.5 rounded-md ${alert.daysRemaining < 7 ? 'bg-red-900/30 text-red-400' : 'bg-cyan-900/30 text-cyan-400'}`}>
{alert.type === 'Expiration' ? <ExclamationIcon className="w-4 h-4" /> : <CalendarIcon className="w-4 h-4" />}
</div>
<span className={`text-xs font-bold ${alert.daysRemaining < 7 ? 'text-red-400' : 'text-cyan-400'}`}>
{alert.daysRemaining === 0 ? 'Today' : `${alert.daysRemaining} days`}
</span>
</div>
<div>
<h4 className="font-semibold text-gray-200 text-sm truncate" title={alert.licenseName}>{alert.licenseName}</h4>
<p className="text-xs text-gray-400 mt-1">{alert.type}</p>
<p className="text-xs text-gray-500">{alert.date.toLocaleDateString()}</p>
</div>
</div>
))}
</div>
</div>
</div>
);
};
export default DashboardAlerts;