pmtool / src /pages /features.tsx
devarshia5's picture
Upload 487 files
d97b8f9 verified
Raw
History Blame Contribute Delete
27.6 kB
import React, { useState, useEffect } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { PlusCircle, Search, Loader2, Edit, Trash2, Save, X, Plus } from "lucide-react";
import { Label } from "@/components/ui/label";
import CustomDialog from "@/components/CustomDialog";
import { featureApi } from "@/services/featureApi";
import { Feature, FeatureCreateRequest, FeatureUpdateRequest } from "@/types";
import Header from "@/components/layout/header";
import Sidebar from "@/components/layout/sidebar";
import { useIsMobile } from "@/hooks/use-mobile";
import { toast } from "@/lib/custom-toast";
import { Checkbox } from "@/components/ui/checkbox";
import { Badge } from "@/components/ui/badge";
const Features = () => {
const queryClient = useQueryClient();
const isMobile = useIsMobile();
const [isSidebarOpen, setIsSidebarOpen] = useState(!isMobile);
const [searchQuery, setSearchQuery] = useState("");
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
const [newFeatureName, setNewFeatureName] = useState("");
const [newFeatureDescription, setNewFeatureDescription] = useState("");
const [newFeatureCategory, setNewFeatureCategory] = useState("");
const [newFeatureStatus, setNewFeatureStatus] = useState(true);
const [editingFeature, setEditingFeature] = useState<Feature | null>(null);
const [editFeatureName, setEditFeatureName] = useState("");
const [editFeatureDescription, setEditFeatureDescription] = useState("");
const [editFeatureCategory, setEditFeatureCategory] = useState("");
const [editFeatureStatus, setEditFeatureStatus] = useState(true);
// Fetch features
const { data: features = [], isLoading, error, refetch } = useQuery({
queryKey: ["features"],
queryFn: featureApi.getAll,
});
// Create feature mutation
const createMutation = useMutation({
mutationFn: featureApi.create,
onSuccess: () => {
toast.success("Feature created successfully");
queryClient.invalidateQueries({ queryKey: ["features"] });
setIsAddDialogOpen(false);
setNewFeatureName("");
},
onError: (error) => {
toast.error("Failed to create feature");
console.error("Error creating feature:", error);
}
});
// Update feature mutation
const updateMutation = useMutation({
mutationFn: ({ id, feature }: { id: number, feature: FeatureUpdateRequest }) =>
featureApi.update(id, feature),
onSuccess: () => {
toast.success("Feature updated successfully");
queryClient.invalidateQueries({ queryKey: ["features"] });
setEditingFeature(null);
},
onError: (error) => {
toast.error("Failed to update feature");
console.error("Error updating feature:", error);
}
});
// Delete feature mutation
const deleteMutation = useMutation({
mutationFn: featureApi.delete,
onSuccess: () => {
toast.success("Feature deleted successfully");
queryClient.invalidateQueries({ queryKey: ["features"] });
},
onError: (error) => {
toast.error("Failed to delete feature");
console.error("Error deleting feature:", error);
}
});
// Toggle sidebar
const toggleSidebar = () => {
setIsSidebarOpen(!isSidebarOpen);
};
// Handle create feature
const handleCreateFeature = (e: React.FormEvent) => {
e.preventDefault();
e.stopPropagation();
if (!newFeatureName.trim()) {
toast.error("Feature name cannot be empty");
return;
}
const newFeature: FeatureCreateRequest = {
featureId: 0, // This will be assigned by the server
featureName: newFeatureName.trim(),
description: newFeatureDescription.trim(),
category: newFeatureCategory.trim(),
isActive: newFeatureStatus
};
createMutation.mutate(newFeature);
};
// Handle create feature input change
const handleCreateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
switch(name) {
case 'featureName':
setNewFeatureName(value);
break;
case 'featureDescription':
setNewFeatureDescription(value);
break;
case 'featureCategory':
setNewFeatureCategory(value);
break;
default:
break;
}
};
// Handle edit feature
const handleEditFeature = (feature: Feature) => {
setEditingFeature(feature);
setEditFeatureName(feature.featureName);
setEditFeatureDescription(feature.description || "");
setEditFeatureCategory(feature.category || "");
setEditFeatureStatus(feature.isActive !== undefined ? feature.isActive : true);
};
// Handle update feature
const handleUpdateFeature = () => {
if (!editingFeature) return;
if (!editFeatureName.trim()) {
toast.error("Feature name cannot be empty");
return;
}
const updatedFeature: FeatureUpdateRequest = {
featureId: editingFeature.featureId,
featureName: editFeatureName.trim(),
description: editFeatureDescription.trim(),
category: editFeatureCategory.trim(),
isActive: editFeatureStatus
};
updateMutation.mutate({
id: editingFeature.featureId,
feature: updatedFeature
});
};
// Handle delete feature
const handleDeleteFeature = (id: number) => {
if (window.confirm("Are you sure you want to delete this feature?")) {
deleteMutation.mutate(id);
}
};
// Cancel editing
const cancelEditing = () => {
setEditingFeature(null);
};
// Filter features based on search query
const filteredFeatures = features?.filter((feature: Feature) => {
if (!searchQuery) return true;
const query = searchQuery.toLowerCase();
return feature.featureName.toLowerCase().includes(query);
});
// Function to ensure all standard features exist in the database
const ensureAllMissingFeatures = async () => {
try {
const existingFeatures = await featureApi.getAll();
// Define all required features with unique IDs (1-53)
const requiredFeatures = [
// Core features
{ featureId: 1, featureName: "Dashboard", description: "Main dashboard with overview statistics", category: "Core", isActive: true },
{ featureId: 2, featureName: "Projects", description: "Project management and tracking", category: "Core", isActive: true },
{ featureId: 3, featureName: "Deliverables", description: "Project deliverables management", category: "Project", isActive: true },
{ featureId: 4, featureName: "Teams", description: "Team management and organization", category: "Core", isActive: true },
{ featureId: 5, featureName: "Employees", description: "Employee management and profiles", category: "HR", isActive: true },
// Admin features
{ featureId: 6, featureName: "Roles", description: "Role management and permissions", category: "Administration", isActive: true },
{ featureId: 7, featureName: "Permissions", description: "Permissions management", category: "Administration", isActive: true },
{ featureId: 8, featureName: "Role Features", description: "Manage feature access for roles", category: "Administration", isActive: true },
// Project features
{ featureId: 9, featureName: "Backlog", description: "Product backlog management", category: "Project", isActive: true },
{ featureId: 10, featureName: "Issues", description: "Issue tracking and resolution", category: "Project", isActive: true },
{ featureId: 11, featureName: "Tasks", description: "Task tracking and assignment", category: "Project", isActive: true },
{ featureId: 12, featureName: "Time Logs", description: "Time tracking for tasks and projects", category: "Project", isActive: true },
// Additional core features
{ featureId: 13, featureName: "Calendar", description: "Calendar view for events and deadlines", category: "Core", isActive: true },
{ featureId: 14, featureName: "Reports", description: "Reporting and analytics", category: "Management", isActive: true },
{ featureId: 15, featureName: "Messages", description: "Internal messaging system", category: "Communication", isActive: true },
{ featureId: 16, featureName: "Settings", description: "System settings and configuration", category: "Administration", isActive: true },
{ featureId: 17, featureName: "Notifications", description: "User notifications", category: "Core", isActive: true },
{ featureId: 18, featureName: "Audit Logs", description: "Audit logs and security tracking", category: "Administration", isActive: true },
{ featureId: 19, featureName: "Integrations", description: "Third-party system integrations", category: "Administration", isActive: true },
{ featureId: 20, featureName: "File Management", description: "Document and file storage system", category: "Core", isActive: true },
// HR management features
{ featureId: 21, featureName: "Attendance", description: "Employee attendance tracking", category: "HR", isActive: true },
{ featureId: 22, featureName: "Leaves", description: "Employee leave requests", category: "HR", isActive: true },
{ featureId: 23, featureName: "Leaves Management", description: "Administrative leave management", category: "HR", isActive: true },
{ featureId: 24, featureName: "Leave Types", description: "Configure leave types and policies", category: "HR", isActive: true },
{ featureId: 42, featureName: "Leave Balances", description: "View your leave balances and history", category: "HR", isActive: true },
// Productivity features
{ featureId: 25, featureName: "Todos", description: "Personal to-do list management", category: "Productivity", isActive: true },
{ featureId: 26, featureName: "Holidays", description: "Company holidays management", category: "HR", isActive: true },
{ featureId: 27, featureName: "Bugs", description: "Bug tracking and resolution", category: "Project", isActive: true },
{ featureId: 39, featureName: "Production Bugs", description: "Track and manage production bugs", category: "Project", isActive: true },
{ featureId: 40, featureName: "Test Cases", description: "Create and manage test cases", category: "Project", isActive: true },
// Appraisal system features
{ featureId: 28, featureName: "My Appraisals", description: "Access to your own appraisals", category: "Appraisal", isActive: true },
{ featureId: 29, featureName: "Appraisal Cycles", description: "Manage appraisal cycle periods", category: "Appraisal", isActive: true },
{ featureId: 30, featureName: "Assessment Areas", description: "Manage assessment areas for appraisals", category: "Appraisal", isActive: true },
{ featureId: 31, featureName: "Competencies", description: "Manage competencies for appraisals", category: "Appraisal", isActive: true },
{ featureId: 32, featureName: "Manager Appraisals", description: "Access to team member appraisals for managers", category: "Appraisal", isActive: true },
{ featureId: 36, featureName: "Role Assessment Questions", description: "Configure assessment questions per role", category: "Appraisal", isActive: true },
{ featureId: 37, featureName: "HR Remarks", description: "HR comments on appraisals", category: "Appraisal", isActive: true },
{ featureId: 38, featureName: "Management Remarks", description: "Management comments on appraisals", category: "Appraisal", isActive: true },
// Facilities features
{ featureId: 33, featureName: "Meeting Rooms", description: "Meeting room management", category: "Facilities", isActive: true },
{ featureId: 34, featureName: "Room Bookings", description: "Book and schedule meeting rooms", category: "Facilities", isActive: true },
// Communication features
{ featureId: 35, featureName: "Communications", description: "Communication tools and channels", category: "Communication", isActive: true },
// Other Project & Management features
{ featureId: 41, featureName: "My Profile", description: "Access and manage your personal profile", category: "Core", isActive: true },
{ featureId: 43, featureName: "Employee Projects", description: "Manage projects assigned to employees", category: "Project", isActive: true },
{ featureId: 44, featureName: "Health Dashboard", description: "System and project health overview", category: "Management", isActive: true },
{ featureId: 50, featureName: "Gantt Chart", description: "Visual project timeline and schedule", category: "Project", isActive: true },
{ featureId: 51, featureName: "Resource Allocation", description: "Manage and track resource distribution", category: "Project", isActive: true },
// Asset Management features
{ featureId: 45, featureName: "Assets", description: "General asset management", category: "Asset Management", isActive: true },
{ featureId: 46, featureName: "System Details", description: "System and hardware details for assets", category: "Asset Management", isActive: true },
{ featureId: 47, featureName: "Asset Assignments", description: "Track asset assignments to employees", category: "Asset Management", isActive: true },
{ featureId: 48, featureName: "My Asset Requests", description: "Employee interface for requesting assets", category: "Asset Management", isActive: true },
{ featureId: 49, featureName: "Asset Request Management", description: "Admin interface for managing asset requests", category: "Asset Management", isActive: true },
// New Admin features
{ featureId: 52, featureName: "Feature Management", description: "System features and functionality management", category: "Administration", isActive: true },
{ featureId: 53, featureName: "Role Permissions", description: "Manage permissions for system roles", category: "Administration", isActive: true },
];
let createdCount = 0;
for (const reqFeature of requiredFeatures) {
const exists = existingFeatures.some(f => f.featureId === reqFeature.featureId);
if (!exists) {
console.log(`Creating missing feature: ${reqFeature.featureName}`);
await featureApi.create(reqFeature);
createdCount++;
}
}
queryClient.invalidateQueries({ queryKey: ["features"] });
if (createdCount > 0) {
toast.success(`Added ${createdCount} missing features`, {
description: "All standard features have been added to the system."
});
} else {
toast.info("All features already exist", {
description: "No new features were added."
});
}
} catch (error) {
console.error("Error ensuring features:", error);
toast.error("Failed to create features", {
description: "An error occurred while creating the required features."
});
}
};
return (
<div className="flex min-h-screen flex-col">
<Header toggleSidebar={toggleSidebar} isSidebarOpen={isSidebarOpen} />
<Sidebar isOpen={isSidebarOpen} setIsOpen={setIsSidebarOpen} />
<main className={`flex-1 p-6 pt-20 md:p-10 md:pt-24 ${isSidebarOpen ? "md:ml-64" : ""} transition-all duration-300`}>
<div className="mx-auto max-w-6xl">
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between mb-8 gap-4">
<div>
<h2 className="text-3xl font-bold tracking-tight">Features</h2>
<p className="text-muted-foreground">
System features and functionality
</p>
</div>
<div className="flex flex-wrap gap-2">
<Button onClick={() => ensureAllMissingFeatures()}>
<Plus className="mr-2 h-4 w-4" />
Add All Missing Features
</Button>
<Button onClick={(e) => {
e.stopPropagation();
setIsAddDialogOpen(true);
}}>
<PlusCircle className="mr-2 h-4 w-4" />
Add Feature
</Button>
</div>
<CustomDialog
isOpen={isAddDialogOpen}
onClose={() => !createMutation.isPending && setIsAddDialogOpen(false)}
title="Create New Feature"
description="Add a new feature to the system"
allowClose={!createMutation.isPending}
isPending={createMutation.isPending}
>
<form onSubmit={handleCreateFeature}>
<div className="grid gap-4 py-4">
<div className="space-y-2">
<Label htmlFor="featureName">Feature Name*</Label>
<Input
id="featureName"
name="featureName"
value={newFeatureName}
onChange={handleCreateChange}
placeholder="Dashboard, Projects, Teams, etc."
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="featureDescription">Feature Description</Label>
<Input
id="featureDescription"
name="featureDescription"
value={newFeatureDescription}
onChange={handleCreateChange}
placeholder="Brief description of this feature"
/>
</div>
<div className="space-y-2">
<Label htmlFor="featureCategory">Category</Label>
<Input
id="featureCategory"
name="featureCategory"
value={newFeatureCategory}
onChange={handleCreateChange}
placeholder="Admin, User, Reports, etc."
/>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="featureStatus"
checked={newFeatureStatus}
onCheckedChange={(checked) => setNewFeatureStatus(checked as boolean)}
/>
<Label htmlFor="featureStatus">Active</Label>
</div>
</div>
<div className="flex justify-end gap-2 mt-4">
<Button type="button" variant="outline" onClick={() => !createMutation.isPending && setIsAddDialogOpen(false)}>
Cancel
</Button>
<Button type="submit" disabled={createMutation.isPending || !newFeatureName.trim()}>
{createMutation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Creating...
</>
) : "Create Feature"}
</Button>
</div>
</form>
</CustomDialog>
</div>
<Card>
<CardHeader>
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4">
<div>
<CardTitle>Feature Management</CardTitle>
<CardDescription>
{features?.length || 0} features defined in the system
</CardDescription>
</div>
<div className="w-full sm:w-auto relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search features..."
className="pl-9 w-full sm:w-[250px]"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</div>
</div>
</CardHeader>
<CardContent>
{isLoading ? (
<div className="flex justify-center items-center h-64">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
</div>
) : error ? (
<div className="flex justify-center items-center h-64 text-destructive">
<p>Error loading features. Please try again later.</p>
</div>
) : filteredFeatures?.length === 0 ? (
<div className="flex justify-center items-center h-64 text-muted-foreground">
<p>No features found.</p>
</div>
) : (
<div className="rounded-md border">
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Feature Name</TableHead>
<TableHead>Description</TableHead>
<TableHead>Category</TableHead>
<TableHead>Status</TableHead>
<TableHead className="w-24 text-right">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filteredFeatures?.map((feature: Feature) => (
<TableRow key={feature.featureId}>
<TableCell>{feature.featureId}</TableCell>
{editingFeature?.featureId === feature.featureId ? (
<TableCell>
<Input
value={editFeatureName}
onChange={(e) => setEditFeatureName(e.target.value)}
className="w-full"
/>
</TableCell>
) : (
<TableCell className="font-medium">{feature.featureName}</TableCell>
)}
{editingFeature?.featureId === feature.featureId ? (
<TableCell>
<Input
value={editFeatureDescription}
onChange={(e) => setEditFeatureDescription(e.target.value)}
className="w-full"
/>
</TableCell>
) : (
<TableCell>{feature.description || "-"}</TableCell>
)}
{editingFeature?.featureId === feature.featureId ? (
<TableCell>
<Input
value={editFeatureCategory}
onChange={(e) => setEditFeatureCategory(e.target.value)}
className="w-full"
/>
</TableCell>
) : (
<TableCell>{feature.category || "-"}</TableCell>
)}
{editingFeature?.featureId === feature.featureId ? (
<TableCell>
<div className="flex items-center space-x-2">
<Checkbox
id={`edit-status-${feature.featureId}`}
checked={editFeatureStatus}
onCheckedChange={(checked) => setEditFeatureStatus(checked as boolean)}
/>
<Label htmlFor={`edit-status-${feature.featureId}`}>Active</Label>
</div>
</TableCell>
) : (
<TableCell>
<Badge variant={feature.isActive ? "success" : "secondary"}>
{feature.isActive ? "Active" : "Inactive"}
</Badge>
</TableCell>
)}
<TableCell className="text-right">
{editingFeature?.featureId === feature.featureId ? (
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="outline"
onClick={cancelEditing}
>
<X className="h-4 w-4" />
</Button>
<Button
size="sm"
onClick={handleUpdateFeature}
>
<Save className="h-4 w-4" />
</Button>
</div>
) : (
<div className="flex justify-end gap-2">
<Button
size="sm"
variant="outline"
onClick={() => handleEditFeature(feature)}
>
<Edit className="h-4 w-4" />
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => handleDeleteFeature(feature.featureId)}
>
<Trash2 className="h-4 w-4" />
</Button>
</div>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)}
</CardContent>
</Card>
</div>
</main>
</div>
);
};
export default Features;