Skip to content

Commit

Permalink
feat: adding new explore page
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasdavis committed Jul 24, 2024
1 parent 860c523 commit 4ce0e32
Show file tree
Hide file tree
Showing 9 changed files with 821 additions and 28 deletions.
57 changes: 44 additions & 13 deletions apps/registry/app/[username]/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
CardHeader,
CardTitle,
} from '@repo/ui/components/ui/card';

import { Badge } from '@repo/ui/components/ui/badge';
import { Progress } from '@repo/ui/components/ui/progress';
import {
totalExperience,
Expand All @@ -35,7 +35,7 @@ const getMetrics = ({ resume }) => {
totalVolunteer: resume.volunteer?.length,
averageJobDuration: averageJobDuration(resume),
mostFrequentJobTitle: 'Software Engineer',
topSkillCategories: ['Programming', 'Web Development', 'Data Analysis'],
topSkillCategories: resume.skills?.map((skill) => skill.name),
mostRecentSkill: 'React Native',
topIndustries: ['Technology', 'Finance', 'Healthcare'],
educationLevel: getEducationLevel(resume),
Expand All @@ -45,13 +45,53 @@ const getMetrics = ({ resume }) => {
return ResumeData;
};

const SkillsList = ({ skills }) => {
if (!skills || skills.length === 0) {
return <p>No skills listed.</p>;
}

return (
<div className="space-y-4">
{skills.map((skill, index) => (
<div key={index} className="border rounded-lg p-4 shadow-sm">
<h3 className="text-lg font-semibold mb-2">{skill.name}</h3>
{skill.level && (
<p className="text-sm text-gray-600 mb-2">Level: {skill.level}</p>
)}
{skill.keywords && skill.keywords.length > 0 && (
<div className="flex flex-wrap gap-2">
{skill.keywords.map((keyword, keywordIndex) => (
<Badge key={keywordIndex} variant="secondary">
{keyword}
</Badge>
))}
</div>
)}
</div>
))}
</div>
);
};

const ResumeDashboard = () => {
const { resume } = useProfileData();
const ResumeData = getMetrics({ resume });
return (
<>
<div className="min-h-screen p-8">
<div className="">
<Card className="mb-8">
<CardHeader>
<CardTitle className="text-xl font-semibold flex items-center">
<FileText className="w-5 h-5 mr-2" />
Summary
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-lg">{resume.basics?.summary}</p>
</CardContent>
</Card>

<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<Card>
<CardHeader>
Expand Down Expand Up @@ -127,20 +167,11 @@ const ResumeDashboard = () => {
<CardHeader>
<CardTitle className="text-xl font-semibold flex items-center">
<Code className="w-5 h-5 mr-2" />
Top Skill Categories
Skills
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{ResumeData.topSkillCategories.map((skill, index) => (
<span
key={index}
className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm font-medium"
>
{skill}
</span>
))}
</div>
<SkillsList skills={resume.skills} />
</CardContent>
</Card>

Expand Down
4 changes: 2 additions & 2 deletions apps/registry/app/[username]/timeline/Timeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ const SimpleVerticalTimeline = ({ resume }) => {
const opposite = side === 'left' ? 'right' : 'left';
return (
<TimelineItem key={index} status="done">
<TimelineHeading className="text-xl" side={side}>
<TimelineHeading className="text-2xl" side={side}>
{work.name}
</TimelineHeading>
<TimelineHeading side={opposite} variant="secondary">
{work.startDate} - {work.endDate}
</TimelineHeading>
<TimelineDot status="done" />
<TimelineLine done />
<TimelineContent className="text-sm" side={side}>
<TimelineContent className="text-base" side={side}>
<span className="font-bold">{work.position}</span> <br />
{work.summary}
</TimelineContent>
Expand Down
231 changes: 231 additions & 0 deletions apps/registry/app/explore2/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
'use client';

import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Link from 'next/link';
import { motion } from 'framer-motion';
import { Input } from '@repo/ui/components/ui/input';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@repo/ui/components/ui/card';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@repo/ui/components/ui/select';
import { ScrollArea } from '@repo/ui/components/ui/scroll-area';
import { Loader2 } from 'lucide-react';

const formatLocation = (location) => {
if (!location) return 'Location not provided';
const {
postalCode = '',
city = '',
region = '',
countryCode = '',
} = location;
const locationParts = [city, region, postalCode, countryCode].filter(
(part) => part.trim() !== ''
);
return locationParts.length === 0
? 'Location not provided'
: locationParts.join(', ');
};

const Resumes = () => {
const [data, setData] = useState([]);
const [nameFilter, setNameFilter] = useState('');
const [locationFilter, setLocationFilter] = useState('');
const [positionFilter, setPositionFilter] = useState('');
const [filteredResumes, setFilteredResumes] = useState([]);
const [loading, setLoading] = useState(true);
const [uniqueLocations, setUniqueLocations] = useState([]);
const [uniquePositions, setUniquePositions] = useState([]);

useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get('/api/resumes?limit=500');
setData(response.data);
setLoading(false);

// Extract unique locations and positions
const locations = new Set(
response.data.map((resume) => formatLocation(resume.location))
);
setUniqueLocations(Array.from(locations));

const positions = new Set(
response.data.flatMap((resume) =>
resume.work ? resume.work.map((w) => w.position) : []
)
);
setUniquePositions(Array.from(positions));
} catch (error) {
console.error('Error fetching data: ', error);
setLoading(false);
}
};

fetchData();
}, []);

useEffect(() => {
setFilteredResumes(
data.filter((resume) => {
const nameMatch = resume?.name
?.toLowerCase()
.includes(nameFilter.toLowerCase());
const locationMatch =
locationFilter === '' ||
formatLocation(resume.location)
.toLowerCase()
.includes(locationFilter.toLowerCase());
const positionMatch =
positionFilter === '' ||
(resume.work &&
resume.work.some((w) =>
w.position.toLowerCase().includes(positionFilter.toLowerCase())
));
return nameMatch && locationMatch && positionMatch;
})
);
}, [nameFilter, locationFilter, positionFilter, data]);

if (loading) {
return (
<div className="flex justify-center items-center h-screen">
<Loader2 className="mr-2 h-16 w-16 animate-spin" />
</div>
);
}

return (
<div className="flex max-w-7xl mx-auto p-6">
{/* Sidebar */}
<div className="w-64 mr-8">
<Card>
<CardHeader>
<CardTitle>Filters</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-4">
<div>
<label htmlFor="name-filter" className="text-sm font-medium">
Name
</label>
<Input
id="name-filter"
placeholder="Filter by name..."
value={nameFilter}
onChange={(e) => setNameFilter(e.target.value)}
/>
</div>
<div>
<label
htmlFor="location-filter"
className="text-sm font-medium"
>
Location
</label>
<Select
onValueChange={setLocationFilter}
value={locationFilter}
>
<SelectTrigger>
<SelectValue placeholder="Select location" />
</SelectTrigger>
<SelectContent>
<SelectItem value="asdsd">All Locations</SelectItem>
{uniqueLocations.map((location, index) => (
<SelectItem key={index} value={location}>
{location}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<label
htmlFor="position-filter"
className="text-sm font-medium"
>
Position
</label>
<Select
onValueChange={setPositionFilter}
value={positionFilter}
>
<SelectTrigger>
<SelectValue placeholder="Select position" />
</SelectTrigger>
<SelectContent>
<SelectItem value="asdsd">All Positions</SelectItem>
{uniquePositions.map((position, index) => (
<SelectItem key={index} value={position}>
{position}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
</div>

{/* Main content */}
<div className="flex-1">
<ScrollArea className="h-[80vh]">
<div className="space-y-4">
{filteredResumes.map((resume, index) => (
<motion.div
key={index}
whileHover={{ scale: 1.02 }}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<Link href={`/${resume.username}/dashboard`}>
<Card>
<CardHeader>
<CardTitle>{resume.name}</CardTitle>
<CardDescription>
{formatLocation(resume.location)}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex items-center">
<img
src={resume.image}
alt={resume.name}
className="w-12 h-12 rounded-full mr-4"
/>
<div>
{resume.work && resume.work[0] && (
<p className="text-sm text-gray-600">
{resume.work[0].position} at{' '}
{resume.work[0].company}
</p>
)}
</div>
</div>
</CardContent>
</Card>
</Link>
</motion.div>
))}
</div>
</ScrollArea>
</div>
</div>
);
};

export default Resumes;
3 changes: 3 additions & 0 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
"dependencies": {
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-progress": "^1.1.0",
"@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.1.0",
"class-variance-authority": "^0.7.0",
Expand Down
30 changes: 30 additions & 0 deletions packages/ui/src/components/ui/checkbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use client';

import * as React from 'react';
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';

import { cn } from '@repo/ui/lib/utils';

const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={cn(
'peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
className
)}
{...props}
>
<CheckboxPrimitive.Indicator
className={cn('flex items-center justify-center text-current')}
>
<Check className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;

export { Checkbox };
Loading

0 comments on commit 4ce0e32

Please sign in to comment.