feat: Initial commit of KROW Workforce Web client (Base44 export)

This commit is contained in:
bwnyasse
2025-11-11 06:08:01 -05:00
commit e571193362
173 changed files with 50898 additions and 0 deletions

154
src/pages/ActivityLog.jsx Normal file
View File

@@ -0,0 +1,154 @@
import React, { useState } from "react";
import { base44 } from "@/api/base44Client";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
Activity,
Calendar,
UserPlus,
FileText,
MessageSquare,
AlertCircle,
CheckCircle,
Search,
Filter
} from "lucide-react";
import { formatDistanceToNow } from "date-fns";
import PageHeader from "../components/common/PageHeader";
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 ActivityLog() {
const [activeTab, setActiveTab] = useState("all");
const [searchTerm, setSearchTerm] = useState("");
const queryClient = useQueryClient();
const { data: user } = useQuery({
queryKey: ['current-user-activity'],
queryFn: () => base44.auth.me(),
});
const { data: activities = [], isLoading } = useQuery({
queryKey: ['activity-logs', user?.id],
queryFn: () => base44.entities.ActivityLog.filter({ user_id: user?.id }, '-created_date', 100),
enabled: !!user?.id,
initialData: [],
});
const filteredActivities = activities.filter(activity => {
const matchesSearch = !searchTerm ||
activity.title?.toLowerCase().includes(searchTerm.toLowerCase()) ||
activity.description?.toLowerCase().includes(searchTerm.toLowerCase());
const matchesTab = activeTab === "all" ||
(activeTab === "unread" && !activity.is_read) ||
(activeTab === "read" && activity.is_read);
return matchesSearch && matchesTab;
});
const unreadCount = activities.filter(a => !a.is_read).length;
return (
<div className="p-4 md:p-8 bg-slate-50 min-h-screen">
<div className="max-w-5xl mx-auto">
<PageHeader
title="Activity Log"
subtitle={`${activities.length} total activities • ${unreadCount} unread`}
/>
{/* Filters */}
<div className="mb-6 space-y-4">
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="bg-white border border-slate-200">
<TabsTrigger value="all">
All <Badge variant="secondary" className="ml-2">{activities.length}</Badge>
</TabsTrigger>
<TabsTrigger value="unread">
Unread <Badge variant="secondary" className="ml-2">{unreadCount}</Badge>
</TabsTrigger>
<TabsTrigger value="read">
Read <Badge variant="secondary" className="ml-2">{activities.length - unreadCount}</Badge>
</TabsTrigger>
</TabsList>
</Tabs>
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-slate-400" />
<Input
placeholder="Search activities..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
</div>
{/* Activities List */}
<div className="space-y-3">
{filteredActivities.length > 0 ? (
filteredActivities.map((activity) => {
const Icon = iconMap[activity.icon_type] || AlertCircle;
const colorClass = colorMap[activity.icon_color] || colorMap.blue;
return (
<Card key={activity.id} className={`border-2 ${activity.is_read ? 'border-slate-200 opacity-70' : 'border-blue-200'}`}>
<CardContent className="p-6">
<div className="flex gap-4">
<div className={`w-14 h-14 rounded-full ${colorClass} flex items-center justify-center flex-shrink-0`}>
<Icon className="w-7 h-7" />
</div>
<div className="flex-1 min-w-0">
<div className="flex items-start justify-between mb-2">
<h3 className="font-bold text-[#1C323E] text-lg">{activity.title}</h3>
<span className="text-sm text-slate-500 whitespace-nowrap ml-4">
{formatDistanceToNow(new Date(activity.created_date), { addSuffix: true })}
</span>
</div>
<p className="text-slate-600 mb-4">{activity.description}</p>
<div className="flex items-center gap-3">
{!activity.is_read && (
<Badge className="bg-blue-500 text-white">Unread</Badge>
)}
<Badge variant="outline">{activity.activity_type?.replace('_', ' ')}</Badge>
</div>
</div>
</div>
</CardContent>
</Card>
);
})
) : (
<Card>
<CardContent className="p-12 text-center">
<Activity className="w-16 h-16 mx-auto text-slate-300 mb-4" />
<h3 className="text-xl font-semibold text-slate-900 mb-2">No activities found</h3>
<p className="text-slate-600">Try adjusting your filters</p>
</CardContent>
</Card>
)}
</div>
</div>
</div>
);
}