mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-23 05:57:55 +00:00
09b26fe5c8
Major code quality improvements: - Fixed 433 lint errors (389 errors + 44 warnings) - Removed 200+ unused variables and imports - Replaced 80+ explicit 'any' types with proper TypeScript types - Fixed 50+ useEffect dependency warnings - Escaped 30+ unescaped apostrophes in JSX - Fixed error handling with proper type guards Technical improvements: - Replaced `any` with `Record<string, unknown>`, specific interfaces - Added proper event types (React.ChangeEvent, React.MouseEvent) - Implemented eslint-disable for intentional dependency exclusions - Fixed destructuring patterns and parsing errors - Improved type safety across all components, contexts, and hooks Files affected: 100+ components, contexts, hooks, and pages Quality Gate: Now passes with 0 errors (27 non-blocking warnings remain) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
294 lines
10 KiB
TypeScript
294 lines
10 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import {
|
|
Plus,
|
|
Trash2,
|
|
|
|
|
|
|
|
|
|
|
|
AlertCircle
|
|
} from 'lucide-react';
|
|
|
|
interface BudgetItem {
|
|
id: string;
|
|
description: string;
|
|
amount: number;
|
|
category: string;
|
|
justification: string;
|
|
}
|
|
|
|
interface Milestone {
|
|
id: string;
|
|
title: string;
|
|
deliverables: string;
|
|
amount: number;
|
|
deadline: string;
|
|
}
|
|
|
|
export const FundingProposal: React.FC = () => {
|
|
const [proposalTitle, setProposalTitle] = useState('');
|
|
const [proposalDescription, setProposalDescription] = useState('');
|
|
const [category, setCategory] = useState('');
|
|
const [budgetItems, setBudgetItems] = useState<BudgetItem[]>([
|
|
{ id: '1', description: '', amount: 0, category: '', justification: '' }
|
|
]);
|
|
const [milestones, setMilestones] = useState<Milestone[]>([
|
|
{ id: '1', title: '', deliverables: '', amount: 0, deadline: '' }
|
|
]);
|
|
|
|
const addBudgetItem = () => {
|
|
setBudgetItems([...budgetItems, {
|
|
id: Date.now().toString(),
|
|
description: '',
|
|
amount: 0,
|
|
category: '',
|
|
justification: ''
|
|
}]);
|
|
};
|
|
|
|
const removeBudgetItem = (id: string) => {
|
|
setBudgetItems(budgetItems.filter(item => item.id !== id));
|
|
};
|
|
|
|
const updateBudgetItem = (id: string, field: keyof BudgetItem, value: string | number) => {
|
|
setBudgetItems(budgetItems.map(item =>
|
|
item.id === id ? { ...item, [field]: value } : item
|
|
));
|
|
};
|
|
|
|
const addMilestone = () => {
|
|
setMilestones([...milestones, {
|
|
id: Date.now().toString(),
|
|
title: '',
|
|
deliverables: '',
|
|
amount: 0,
|
|
deadline: ''
|
|
}]);
|
|
};
|
|
|
|
const removeMilestone = (id: string) => {
|
|
setMilestones(milestones.filter(m => m.id !== id));
|
|
};
|
|
|
|
const updateMilestone = (id: string, field: keyof Milestone, value: string | number) => {
|
|
setMilestones(milestones.map(m =>
|
|
m.id === id ? { ...m, [field]: value } : m
|
|
));
|
|
};
|
|
|
|
const totalBudget = budgetItems.reduce((sum, item) => sum + (item.amount || 0), 0);
|
|
const totalMilestoneAmount = milestones.reduce((sum, m) => sum + (m.amount || 0), 0);
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Proposal Header */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Create Funding Proposal</CardTitle>
|
|
<CardDescription>Submit a detailed budget request for treasury funding</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="title">Proposal Title</Label>
|
|
<Input
|
|
id="title"
|
|
placeholder="Enter a clear, descriptive title"
|
|
value={proposalTitle}
|
|
onChange={(e) => setProposalTitle(e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="category">Category</Label>
|
|
<Select value={category} onValueChange={setCategory}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Select category" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="development">Development</SelectItem>
|
|
<SelectItem value="marketing">Marketing</SelectItem>
|
|
<SelectItem value="operations">Operations</SelectItem>
|
|
<SelectItem value="community">Community</SelectItem>
|
|
<SelectItem value="research">Research</SelectItem>
|
|
<SelectItem value="infrastructure">Infrastructure</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="description">Description</Label>
|
|
<Textarea
|
|
id="description"
|
|
placeholder="Provide a detailed description of the proposal"
|
|
rows={4}
|
|
value={proposalDescription}
|
|
onChange={(e) => setProposalDescription(e.target.value)}
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Budget Items */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center justify-between">
|
|
<span>Budget Breakdown</span>
|
|
<Badge variant="outline" className="text-lg px-3 py-1">
|
|
Total: ${totalBudget.toLocaleString()}
|
|
</Badge>
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{budgetItems.map((item, index) => (
|
|
<div key={item.id} className="p-4 border rounded-lg space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-medium">Item {index + 1}</span>
|
|
{budgetItems.length > 1 && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => removeBudgetItem(item.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<div className="space-y-2">
|
|
<Label>Description</Label>
|
|
<Input
|
|
placeholder="Budget item description"
|
|
value={item.description}
|
|
onChange={(e) => updateBudgetItem(item.id, 'description', e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Amount ($)</Label>
|
|
<Input
|
|
type="number"
|
|
placeholder="0"
|
|
value={item.amount || ''}
|
|
onChange={(e) => updateBudgetItem(item.id, 'amount', parseFloat(e.target.value) || 0)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Justification</Label>
|
|
<Textarea
|
|
placeholder="Explain why this expense is necessary"
|
|
rows={2}
|
|
value={item.justification}
|
|
onChange={(e) => updateBudgetItem(item.id, 'justification', e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<Button onClick={addBudgetItem} variant="outline" className="w-full">
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Add Budget Item
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Milestones */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Milestones & Deliverables</CardTitle>
|
|
<CardDescription>Define clear milestones with payment schedule</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{milestones.map((milestone, index) => (
|
|
<div key={milestone.id} className="p-4 border rounded-lg space-y-3">
|
|
<div className="flex items-center justify-between">
|
|
<span className="font-medium">Milestone {index + 1}</span>
|
|
{milestones.length > 1 && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => removeMilestone(milestone.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<div className="space-y-2">
|
|
<Label>Title</Label>
|
|
<Input
|
|
placeholder="Milestone title"
|
|
value={milestone.title}
|
|
onChange={(e) => updateMilestone(milestone.id, 'title', e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Payment Amount ($)</Label>
|
|
<Input
|
|
type="number"
|
|
placeholder="0"
|
|
value={milestone.amount || ''}
|
|
onChange={(e) => updateMilestone(milestone.id, 'amount', parseFloat(e.target.value) || 0)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
<div className="space-y-2">
|
|
<Label>Deliverables</Label>
|
|
<Textarea
|
|
placeholder="What will be delivered"
|
|
rows={2}
|
|
value={milestone.deliverables}
|
|
onChange={(e) => updateMilestone(milestone.id, 'deliverables', e.target.value)}
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Deadline</Label>
|
|
<Input
|
|
type="date"
|
|
value={milestone.deadline}
|
|
onChange={(e) => updateMilestone(milestone.id, 'deadline', e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
<Button onClick={addMilestone} variant="outline" className="w-full">
|
|
<Plus className="h-4 w-4 mr-2" />
|
|
Add Milestone
|
|
</Button>
|
|
|
|
{totalMilestoneAmount !== totalBudget && totalMilestoneAmount > 0 && (
|
|
<div className="flex items-center gap-2 p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-lg text-gray-900">
|
|
<AlertCircle className="h-5 w-5 text-yellow-600" />
|
|
<span className="text-sm text-gray-900">
|
|
Milestone total (${totalMilestoneAmount.toLocaleString()}) doesn't match budget total (${totalBudget.toLocaleString()})
|
|
</span>
|
|
</div>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Submit Button */}
|
|
<div className="flex justify-end gap-3">
|
|
<Button variant="outline">Save Draft</Button>
|
|
<Button>Submit Proposal</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}; |