Skip to content

Commit 4fbe202

Browse files
remyjeleeminhle35gyoumipeternuynPeter Nguyen
authored
Email templates (purely frontend) (#579)
* feat(frontend): implement question type components (#540) - Add five question component types: ShortAnswer, Dropdown, MultiChoice, MultiSelect, and Ranking - Create test route for component demonstration - Modify ApplicationForm to handle different question types - Update typing for question data structure * fix(): use React DnD for ranking options * fix(dropdown): add borderline, improve hover effects, and highlight selected options - Added prominent border for dropdown box - Implemented light blue hover effect for options - Added conditional highlighting for selected options with checkmark * fix(shortanswer): enlarge textarea to span 77 columns and 3 rows - Increased width of textarea using cols attribute - Set default height to 3 rows - Improved visual presentation of text input area * chore(deps): update yarn.lock with automatic modifications - Reflect dependency resolution changes from recent package updates * feat(shortAnswer): enhance textarea focus visibility and border styling * refactor: update question components structure and remove unneccsary input files in each component's directory * setup shadcn components * Feat/shad cn UI question components (#559) * refactor: replace MultipleChoice twin.macro with ShadCN UI components * refactor: replace MultiSelect React Component with ShadCN UI components * fix dropdown component * refactor multiple choice * refactor short answers * refactor testing route for question components * update yarn.lock * Chaos 551 feature/interview-booking-component (#565) * feat: interview page for user and admin. User still has css issues. * feat: fix the calendar css isue, still persist * feat: add DayPilot library for enhanced calendar functionality and improve code formatting in admin and user booking components --------- Co-authored-by: Peter Nguyen <z5662723@ad.unsw.edu.au> * feat: Implement application review page for recruitment form (#563) * feat: create new page for application review with static path * feat: fix route to include campaign_id not static id and provide fallback if campaign ID not provided and fix ranking component with a default value check to make sure defaultValue is not undefined, null * fix: add dynamic width support to Dropdown, Ranking, and ShortAnswer components; remove display=flex from Box in App.tsx - Enabled dynamic width prop on Dropdown, Ranking, and ShortAnswer for better layout flexibility - Removed from Box in App.tsx to fix children layout issues * fix: add more details for fallback campaign * fix: make API call as written in this PR https://github.com/devsoc-unsw/chaos/pull/562/files and keep current fallback * feat: add explanation for pages/application_review/index.tsx at the top * fix: modify explanation in URL patterns and remove unused import --------- Co-authored-by: gyoumi <87557702+Gyoumi@users.noreply.github.com> * commiting lock file * email page initialisation * Made email template functional and fixed obvious flaws (Duplicate text truncating, button misalignment, overflowing) * Finished email templates and editor page * fixed accidental dupe line while merging * edited according to isaacs recommendations * Fixed left leaning issue * Split email-templates into smaller components * removed redundant lock files * added yarn install lock (latest) * deleted useless files on root dir --------- Co-authored-by: Thao Minh Le <118033201+minhle35@users.noreply.github.com> Co-authored-by: Isaac Kim <isaackim1412@gmail.com> Co-authored-by: Peter N <116279468+peternuyn@users.noreply.github.com> Co-authored-by: Peter Nguyen <z5662723@ad.unsw.edu.au> Co-authored-by: gyoumi <87557702+Gyoumi@users.noreply.github.com>
1 parent c1a27b1 commit 4fbe202

File tree

11 files changed

+1019
-5
lines changed

11 files changed

+1019
-5
lines changed

frontend/src/components/ui/select.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const SelectItem = React.forwardRef<
116116
<SelectPrimitive.Item
117117
ref={ref}
118118
className={cn(
119-
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
119+
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-gray-100 hover:text-gray-900 focus:bg-accent focus:text-accent-foreground data-[highlighted]:bg-gray-100 data-[highlighted]:text-gray-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
120120
className
121121
)}
122122
{...props}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import Button from "@/components/Button";
2+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
3+
import { Badge } from "@/components/ui/badge";
4+
import {
5+
AlertDialog,
6+
AlertDialogAction,
7+
AlertDialogCancel,
8+
AlertDialogContent,
9+
AlertDialogDescription,
10+
AlertDialogFooter,
11+
AlertDialogHeader,
12+
AlertDialogTitle,
13+
AlertDialogTrigger,
14+
} from "@/components/ui/alert-dialog";
15+
import { EyeIcon, PencilIcon, DocumentDuplicateIcon, TrashIcon } from "@heroicons/react/24/outline";
16+
import { templateCategories } from "../../constants";
17+
import type { EmailTemplate } from "../../types";
18+
19+
20+
interface EmailTemplateCardProps {
21+
template: EmailTemplate;
22+
onPreview: (template: EmailTemplate) => void;
23+
onEdit: (template: EmailTemplate) => void;
24+
onDuplicate: (template: EmailTemplate) => void;
25+
onDelete: (templateId: number) => void;
26+
}
27+
28+
29+
const EmailTemplateCard = ({
30+
template,
31+
onPreview,
32+
onEdit,
33+
onDuplicate,
34+
onDelete,
35+
}: EmailTemplateCardProps) => {
36+
const getCategoryBadgeColor = (category: string) => {
37+
switch (category) {
38+
case "interview":
39+
return "bg-blue-100 text-blue-800";
40+
case "acceptance":
41+
return "bg-green-100 text-green-800";
42+
case "rejection":
43+
return "bg-red-100 text-red-800";
44+
case "confirmation":
45+
return "bg-purple-100 text-purple-800";
46+
case "reminder":
47+
return "bg-orange-100 text-orange-800";
48+
default:
49+
return "bg-gray-100 text-gray-800";
50+
}
51+
};
52+
53+
54+
return (
55+
<Card key={template.id} className="hover:shadow-md transition-shadow flex flex-col h-full">
56+
<CardHeader className="pb-3">
57+
<div className="flex items-start justify-between">
58+
<div className="flex-1">
59+
<CardTitle className="text-lg">{template.name}</CardTitle>
60+
<Badge className={`mt-2 ${getCategoryBadgeColor(template.category)}`}>
61+
{templateCategories.find((c) => c.value === template.category)?.label}
62+
</Badge>
63+
</div>
64+
</div>
65+
</CardHeader>
66+
<CardContent className="flex flex-col flex-1">
67+
<div className="space-y-3 flex-1">
68+
<div>
69+
<p className="text-sm font-medium text-gray-700">Subject:</p>
70+
<p className="text-sm text-gray-600 line-clamp-2">{template.subject}</p>
71+
</div>
72+
<div>
73+
<p className="text-sm font-medium text-gray-700">Preview:</p>
74+
<p className="text-sm text-gray-600 line-clamp-3">{template.body}</p>
75+
</div>
76+
</div>
77+
78+
79+
<div className="mt-auto">
80+
<div className="text-xs text-gray-500 mb-4">
81+
<p>Created: {new Date(template.createdAt).toLocaleDateString()}</p>
82+
<p>Updated: {new Date(template.updatedAt).toLocaleDateString()}</p>
83+
</div>
84+
85+
86+
<div className="flex justify-between items-center gap-2">
87+
<div className="flex gap-2">
88+
<Button color="white" onClick={() => onPreview(template)} className="w-24 text-xs">
89+
<EyeIcon className="w-4 h-4 mr-1 flex-shrink-0" />
90+
<span>Preview</span>
91+
</Button>
92+
<Button color="white" onClick={() => onEdit(template)} className="w-20 text-xs">
93+
<PencilIcon className="w-4 h-4 mr-1 flex-shrink-0" />
94+
<span>Edit</span>
95+
</Button>
96+
<Button color="white" onClick={() => onDuplicate(template)} className="w-20 text-xs">
97+
<DocumentDuplicateIcon className="w-4 h-4 mr-1 flex-shrink-0" />
98+
<span>Copy</span>
99+
</Button>
100+
</div>
101+
<AlertDialog>
102+
<AlertDialogTrigger asChild>
103+
<Button
104+
color="danger"
105+
className="flex-shrink-0 bg-red-600 text-white hover:bg-red-700"
106+
aria-label="Delete template"
107+
>
108+
<TrashIcon className="w-4 h-4" />
109+
</Button>
110+
</AlertDialogTrigger>
111+
<AlertDialogContent>
112+
<AlertDialogHeader>
113+
<AlertDialogTitle>Delete template?</AlertDialogTitle>
114+
<AlertDialogDescription>
115+
This action cannot be undone. This will permanently delete the email template
116+
"{template.name}".
117+
</AlertDialogDescription>
118+
</AlertDialogHeader>
119+
<AlertDialogFooter>
120+
<AlertDialogCancel>Cancel</AlertDialogCancel>
121+
<AlertDialogAction onClick={() => onDelete(template.id)}>
122+
Delete
123+
</AlertDialogAction>
124+
</AlertDialogFooter>
125+
</AlertDialogContent>
126+
</AlertDialog>
127+
</div>
128+
</div>
129+
</CardContent>
130+
</Card>
131+
);
132+
};
133+
134+
135+
export default EmailTemplateCard;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import Button from "@/components/Button";
2+
import { Card, CardContent } from "@/components/ui/card";
3+
import { Input } from "@/components/ui/input";
4+
import { Label } from "@/components/ui/label";
5+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
6+
import { templateCategories } from "../../constants";
7+
8+
interface EmailTemplateFiltersProps {
9+
searchTerm: string;
10+
setSearchTerm: (value: string) => void;
11+
selectedCategory: string;
12+
setSelectedCategory: (value: string) => void;
13+
}
14+
15+
const EmailTemplateFilters = ({
16+
searchTerm,
17+
setSearchTerm,
18+
selectedCategory,
19+
setSelectedCategory,
20+
}: EmailTemplateFiltersProps) => {
21+
const handleClearFilters = () => {
22+
setSelectedCategory("all");
23+
setSearchTerm("");
24+
};
25+
26+
return (
27+
<Card className="mb-8">
28+
<CardContent className="p-4">
29+
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
30+
<div>
31+
<Label htmlFor="search">Search Templates</Label>
32+
<Input
33+
id="search"
34+
placeholder="Search by name or subject..."
35+
value={searchTerm}
36+
onChange={(e) => setSearchTerm(e.target.value)}
37+
/>
38+
</div>
39+
<div>
40+
<Label htmlFor="category">Filter by Category</Label>
41+
<Select value={selectedCategory} onValueChange={setSelectedCategory}>
42+
<SelectTrigger>
43+
<SelectValue />
44+
</SelectTrigger>
45+
<SelectContent>
46+
{templateCategories.map((category) => (
47+
<SelectItem
48+
key={category.value}
49+
value={category.value}
50+
className="cursor-pointer hover:bg-accent hover:text-accent-foreground"
51+
>
52+
{category.label}
53+
</SelectItem>
54+
))}
55+
</SelectContent>
56+
</Select>
57+
</div>
58+
<div className="flex items-end">
59+
<Button
60+
color="white"
61+
onClick={handleClearFilters}
62+
className="w-full"
63+
>
64+
Clear Filters
65+
</Button>
66+
</div>
67+
</div>
68+
</CardContent>
69+
</Card>
70+
);
71+
};
72+
73+
export default EmailTemplateFilters;

0 commit comments

Comments
 (0)