feat: Initialize monorepo structure and comprehensive documentation

This commit establishes the new monorepo architecture for the KROW Workforce platform.

Key changes include:
- Reorganized project into `frontend-web`, `mobile-apps`, `firebase`, `scripts`, and `secrets` directories.
- Updated `Makefile` to support the new monorepo layout and automate Base44 export integration.
- Fixed `scripts/prepare-export.js` for ES module compatibility and global component import resolution.
- Created and updated `CONTRIBUTING.md` for developer onboarding.
- Restructured, renamed, and translated all `docs/` files for clarity and consistency.
- Implemented an interactive internal launchpad with diagram viewing capabilities.
- Configured base Firebase project files (`firebase.json`, security rules).
- Updated `README.md` to reflect the new project structure and documentation overview.
This commit is contained in:
bwnyasse
2025-11-12 12:50:55 -05:00
parent 92fd0118be
commit 554dc9f9e3
203 changed files with 1414 additions and 732 deletions

View File

@@ -0,0 +1,296 @@
import React, { useState } from "react";
import { base44 } from "@/api/base44Client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { useNavigate } from "react-router-dom";
import { createPageUrl } from "@/utils";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import {
X,
Bell,
Calendar,
UserPlus,
FileText,
MessageSquare,
AlertCircle,
CheckCircle,
ArrowRight,
MoreVertical
} from "lucide-react";
import { motion, AnimatePresence } from "framer-motion";
import { formatDistanceToNow } from "date-fns";
const iconMap = {
calendar: Calendar,
user: UserPlus,
invoice: FileText,
message: MessageSquare,
alert: AlertCircle,
check: CheckCircle,
};
const colorMap = {
blue: "bg-blue-100 text-blue-600",
red: "bg-red-100 text-red-600",
green: "bg-green-100 text-green-600",
yellow: "bg-yellow-100 text-yellow-600",
purple: "bg-purple-100 text-purple-600",
};
export default function NotificationPanel({ isOpen, onClose }) {
const navigate = useNavigate();
const queryClient = useQueryClient();
const { data: user } = useQuery({
queryKey: ['current-user-notifications'],
queryFn: () => base44.auth.me(),
});
const { data: notifications = [] } = useQuery({
queryKey: ['activity-logs', user?.id],
queryFn: async () => {
if (!user?.id) return [];
// Create sample notifications if none exist
const existing = await base44.entities.ActivityLog.filter({ user_id: user.id }, '-created_date', 50);
if (existing.length === 0 && user?.id) {
// Create initial sample notifications
await base44.entities.ActivityLog.bulkCreate([
{
title: "Event Rescheduled",
description: "Team Meeting was moved to July 15, 3:00 PM",
activity_type: "event_rescheduled",
related_entity_type: "event",
action_label: "View Event",
icon_type: "calendar",
icon_color: "blue",
is_read: false,
user_id: user.id
},
{
title: "Event Canceled",
description: "Product Demo scheduled for May 20 has been canceled",
activity_type: "event_canceled",
related_entity_type: "event",
action_label: "View Event",
icon_type: "calendar",
icon_color: "red",
is_read: false,
user_id: user.id
},
{
title: "Invoice Paid",
description: "You've been added to Client Kickoff on June 8, 10:00 AM",
activity_type: "invoice_paid",
related_entity_type: "invoice",
action_label: "View Invoice",
icon_type: "invoice",
icon_color: "green",
is_read: false,
user_id: user.id
},
{
title: "Staff Selected",
description: "10 staff members selected to fill remaining 10 slots",
activity_type: "staff_assigned",
related_entity_type: "event",
icon_type: "user",
icon_color: "purple",
is_read: true,
user_id: user.id
}
]);
return await base44.entities.ActivityLog.filter({ user_id: user.id }, '-created_date', 50);
}
return existing;
},
enabled: !!user?.id,
initialData: [],
});
const markAsReadMutation = useMutation({
mutationFn: ({ id }) => base44.entities.ActivityLog.update(id, { is_read: true }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['activity-logs'] });
},
});
const deleteMutation = useMutation({
mutationFn: ({ id }) => base44.entities.ActivityLog.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['activity-logs'] });
},
});
const newNotifications = notifications.filter(n => !n.is_read);
const olderNotifications = notifications.filter(n => n.is_read);
const handleAction = (notification) => {
if (notification.action_link) {
navigate(createPageUrl(notification.action_link));
markAsReadMutation.mutate({ id: notification.id });
onClose();
}
};
return (
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 bg-black/20 z-40"
/>
{/* Panel */}
<motion.div
initial={{ opacity: 0, x: 300 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: 300 }}
transition={{ type: "spring", damping: 25 }}
className="fixed right-0 top-0 h-full w-full sm:w-[440px] bg-white shadow-2xl z-50 flex flex-col"
>
{/* Header */}
<div className="flex items-center justify-between p-6 border-b border-slate-200">
<div className="flex items-center gap-3">
<Bell className="w-6 h-6 text-[#1C323E]" />
<h2 className="text-xl font-bold text-[#1C323E]">Notifications</h2>
</div>
<div className="flex items-center gap-2">
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-pink-500 to-purple-500 flex items-center justify-center text-white font-bold">
{user?.full_name?.split(' ').map(n => n[0]).join('').slice(0, 2) || 'U'}
</div>
<Button variant="ghost" size="icon" onClick={onClose}>
<MoreVertical className="w-5 h-5" />
</Button>
<Button variant="ghost" size="icon" onClick={onClose}>
<X className="w-5 h-5" />
</Button>
</div>
</div>
{/* Notifications List */}
<div className="flex-1 overflow-y-auto">
{newNotifications.length > 0 && (
<div className="p-6">
<h3 className="text-sm font-bold text-slate-900 mb-4">New</h3>
<div className="space-y-4">
{newNotifications.map((notification) => {
const Icon = iconMap[notification.icon_type] || AlertCircle;
const colorClass = colorMap[notification.icon_color] || colorMap.blue;
return (
<div key={notification.id} className="relative">
<div className="absolute left-0 top-0 w-2 h-2 bg-red-500 rounded-full" />
<div className="flex gap-4 pl-4">
<div className={`w-12 h-12 rounded-full ${colorClass} flex items-center justify-center flex-shrink-0`}>
<Icon className="w-6 h-6" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between mb-1">
<h4 className="font-semibold text-slate-900">{notification.title}</h4>
<span className="text-xs text-slate-500 whitespace-nowrap ml-2">
{formatDistanceToNow(new Date(notification.created_date), { addSuffix: true })}
</span>
</div>
<p className="text-sm text-slate-600 mb-3">{notification.description}</p>
<div className="flex items-center gap-4">
{notification.action_link && (
<button
onClick={() => handleAction(notification)}
className="text-blue-600 hover:text-blue-700 text-sm font-medium flex items-center gap-1"
>
{notification.action_label || 'View'}
<ArrowRight className="w-4 h-4" />
</button>
)}
<button
onClick={() => markAsReadMutation.mutate({ id: notification.id })}
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
>
Mark as Read
</button>
<button
onClick={() => deleteMutation.mutate({ id: notification.id })}
className="text-red-600 hover:text-red-700 text-sm font-medium"
>
Delete
</button>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
)}
{olderNotifications.length > 0 && (
<div className="p-6 border-t border-slate-100">
<h3 className="text-sm font-bold text-slate-900 mb-4">Older</h3>
<div className="space-y-4">
{olderNotifications.map((notification) => {
const Icon = iconMap[notification.icon_type] || AlertCircle;
const colorClass = colorMap[notification.icon_color] || colorMap.blue;
return (
<div key={notification.id} className="flex gap-4 opacity-70 hover:opacity-100 transition-opacity">
<div className={`w-12 h-12 rounded-full ${colorClass} flex items-center justify-center flex-shrink-0`}>
<Icon className="w-6 h-6" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between mb-1">
<h4 className="font-semibold text-slate-900">{notification.title}</h4>
<span className="text-xs text-slate-500 whitespace-nowrap ml-2">
{formatDistanceToNow(new Date(notification.created_date), { addSuffix: true })}
</span>
</div>
<p className="text-sm text-slate-600 mb-3">{notification.description}</p>
<div className="flex items-center gap-4">
{notification.action_link && (
<button
onClick={() => handleAction(notification)}
className="text-blue-600 hover:text-blue-700 text-sm font-medium flex items-center gap-1"
>
{notification.action_label || 'View'}
<ArrowRight className="w-4 h-4" />
</button>
)}
<button
onClick={() => deleteMutation.mutate({ id: notification.id })}
className="text-red-600 hover:text-red-700 text-sm font-medium"
>
Delete
</button>
</div>
</div>
</div>
);
})}
</div>
</div>
)}
{notifications.length === 0 && (
<div className="flex flex-col items-center justify-center h-full text-center p-8">
<Bell className="w-16 h-16 text-slate-300 mb-4" />
<h3 className="text-lg font-semibold text-slate-900 mb-2">No notifications</h3>
<p className="text-slate-600">You're all caught up!</p>
</div>
)}
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}