424 lines
11 KiB
Vue
424 lines
11 KiB
Vue
|
|
<script setup>
|
||
|
|
import JobCard from './components/JobCard.vue'
|
||
|
|
import SideNav from './components/SideNav.vue'
|
||
|
|
import Dashboard from './components/Dashboard.vue'
|
||
|
|
import Applications from './components/Applications.vue'
|
||
|
|
import SavedJobs from './components/SavedJobs.vue'
|
||
|
|
import JobDetail from './components/JobDetail.vue'
|
||
|
|
import JobFilters from './components/JobFilters.vue'
|
||
|
|
import { ref, provide, onMounted, computed } from 'vue'
|
||
|
|
|
||
|
|
// Current active section
|
||
|
|
const activeSection = ref('dashboard')
|
||
|
|
|
||
|
|
// Selected job for detail view
|
||
|
|
const selectedJobId = ref(null)
|
||
|
|
const showJobDetail = ref(false)
|
||
|
|
|
||
|
|
// Theme state
|
||
|
|
const isDarkMode = ref(false)
|
||
|
|
|
||
|
|
// Initialize theme on mount
|
||
|
|
onMounted(() => {
|
||
|
|
// Check for saved preference in localStorage
|
||
|
|
const savedTheme = localStorage.getItem('theme')
|
||
|
|
if (savedTheme) {
|
||
|
|
isDarkMode.value = savedTheme === 'dark'
|
||
|
|
} else {
|
||
|
|
// Use system preference as fallback
|
||
|
|
isDarkMode.value = window.matchMedia('(prefers-color-scheme: dark)').matches
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply initial theme
|
||
|
|
applyTheme(isDarkMode.value)
|
||
|
|
})
|
||
|
|
|
||
|
|
// Apply theme to document
|
||
|
|
const applyTheme = (dark) => {
|
||
|
|
document.documentElement.classList.toggle('dark-mode', dark)
|
||
|
|
document.documentElement.classList.toggle('light-mode', !dark)
|
||
|
|
localStorage.setItem('theme', dark ? 'dark' : 'light')
|
||
|
|
}
|
||
|
|
|
||
|
|
// Handle theme toggle from SideNav
|
||
|
|
const handleThemeToggle = (dark) => {
|
||
|
|
isDarkMode.value = dark
|
||
|
|
applyTheme(dark)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Provide theme state to components
|
||
|
|
provide('isDarkMode', isDarkMode)
|
||
|
|
provide('toggleTheme', handleThemeToggle)
|
||
|
|
|
||
|
|
// Provide navigation state to components
|
||
|
|
provide('activeSection', activeSection)
|
||
|
|
|
||
|
|
// Handle navigation
|
||
|
|
const handleNavigation = (section) => {
|
||
|
|
activeSection.value = section
|
||
|
|
console.log(`Navigated to: ${section}`)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Sample job listings data
|
||
|
|
const allJobListings = [
|
||
|
|
{
|
||
|
|
id: 1,
|
||
|
|
title: 'Frontend Developer',
|
||
|
|
company: 'Tech Innovations Inc.',
|
||
|
|
location: 'San Francisco, CA',
|
||
|
|
salary: '$120,000 - $150,000',
|
||
|
|
employmentType: 'Full-time',
|
||
|
|
experience: '3+ years',
|
||
|
|
description: 'We are looking for an experienced Frontend Developer proficient in Vue.js to join our growing team. You will be responsible for building user interfaces and implementing new features.',
|
||
|
|
applied: false,
|
||
|
|
tags: ['Vue.js', 'JavaScript', 'CSS', 'UI/UX']
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 2,
|
||
|
|
title: 'Backend Engineer',
|
||
|
|
company: 'DataSystems Co.',
|
||
|
|
location: 'Remote',
|
||
|
|
salary: '$130,000 - $160,000',
|
||
|
|
employmentType: 'Full-time',
|
||
|
|
experience: '4+ years',
|
||
|
|
description: 'Join our backend team to develop scalable APIs and services. Experience with Node.js and database design required.',
|
||
|
|
applied: false,
|
||
|
|
tags: ['Node.js', 'API', 'Backend', 'Databases']
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 3,
|
||
|
|
title: 'Full Stack Developer',
|
||
|
|
company: 'WebSolutions Ltd.',
|
||
|
|
location: 'New York, NY',
|
||
|
|
salary: '$140,000 - $170,000',
|
||
|
|
employmentType: 'Full-time',
|
||
|
|
experience: '5+ years',
|
||
|
|
description: 'Looking for a versatile developer who can work across the entire stack. Experience with Vue.js and Node.js is a plus.',
|
||
|
|
applied: true,
|
||
|
|
tags: ['Full Stack', 'Vue.js', 'Node.js', 'JavaScript']
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 4,
|
||
|
|
title: 'UX/UI Designer',
|
||
|
|
company: 'Creative Design Studio',
|
||
|
|
location: 'Chicago, IL',
|
||
|
|
salary: '$110,000 - $135,000',
|
||
|
|
employmentType: 'Full-time',
|
||
|
|
experience: '2+ years',
|
||
|
|
description: 'Join our design team to create beautiful and intuitive user interfaces for web and mobile applications.',
|
||
|
|
applied: false,
|
||
|
|
tags: ['UI/UX', 'Design', 'Figma', 'Prototyping']
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 5,
|
||
|
|
title: 'DevOps Engineer',
|
||
|
|
company: 'CloudTech Solutions',
|
||
|
|
location: 'Remote',
|
||
|
|
salary: '$135,000 - $165,000',
|
||
|
|
employmentType: 'Full-time',
|
||
|
|
experience: '4+ years',
|
||
|
|
description: 'Looking for a skilled DevOps engineer to help us build and maintain our cloud infrastructure and CI/CD pipelines.',
|
||
|
|
applied: false,
|
||
|
|
tags: ['AWS', 'Docker', 'Kubernetes', 'CI/CD']
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 6,
|
||
|
|
title: 'Product Manager',
|
||
|
|
company: 'Tech Innovations Inc.',
|
||
|
|
location: 'San Francisco, CA',
|
||
|
|
salary: '$150,000 - $180,000',
|
||
|
|
employmentType: 'Full-time',
|
||
|
|
experience: '5+ years',
|
||
|
|
description: 'Lead product development initiatives and work closely with engineering, design, and marketing teams to deliver exceptional products.',
|
||
|
|
applied: false,
|
||
|
|
tags: ['Product', 'Agile', 'Leadership', 'Strategy']
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 7,
|
||
|
|
title: 'Data Scientist',
|
||
|
|
company: 'DataSystems Co.',
|
||
|
|
location: 'Boston, MA',
|
||
|
|
salary: '$140,000 - $170,000',
|
||
|
|
employmentType: 'Full-time',
|
||
|
|
experience: '3+ years',
|
||
|
|
description: 'Apply machine learning and statistical techniques to analyze large datasets and extract valuable insights for our clients.',
|
||
|
|
applied: false,
|
||
|
|
tags: ['Python', 'Machine Learning', 'Data Analysis', 'SQL']
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 8,
|
||
|
|
title: 'Frontend Developer (Contract)',
|
||
|
|
company: 'WebSolutions Ltd.',
|
||
|
|
location: 'Remote',
|
||
|
|
salary: '$90,000 - $120,000',
|
||
|
|
employmentType: 'Contract',
|
||
|
|
experience: '2+ years',
|
||
|
|
description: 'Short-term contract role for a Vue.js developer to help us complete a client project over the next 6 months.',
|
||
|
|
applied: false,
|
||
|
|
tags: ['Vue.js', 'JavaScript', 'Contract', 'Remote']
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
// Filtered job listings
|
||
|
|
const filteredJobListings = ref([...allJobListings]);
|
||
|
|
|
||
|
|
// Handle job application
|
||
|
|
const handleJobApplication = (jobTitle) => {
|
||
|
|
console.log(`Applied for: ${jobTitle}`);
|
||
|
|
};
|
||
|
|
|
||
|
|
// View job details
|
||
|
|
const viewJobDetails = (jobId) => {
|
||
|
|
selectedJobId.value = jobId;
|
||
|
|
showJobDetail.value = true;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Handle filter changes
|
||
|
|
const handleFilterChange = (filteredJobs) => {
|
||
|
|
filteredJobListings.value = filteredJobs;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Close job details
|
||
|
|
const closeJobDetails = () => {
|
||
|
|
showJobDetail.value = false;
|
||
|
|
};
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="app-container">
|
||
|
|
<SideNav @navigate="handleNavigation" />
|
||
|
|
|
||
|
|
<div class="content-wrapper">
|
||
|
|
<header>
|
||
|
|
<h1>{{ activeSection === 'dashboard' ? 'Dashboard' : activeSection === 'jobs' ? 'Job Board' : activeSection.charAt(0).toUpperCase() + activeSection.slice(1) }}</h1>
|
||
|
|
</header>
|
||
|
|
|
||
|
|
<main>
|
||
|
|
<section v-if="activeSection === 'jobs'" class="job-listings">
|
||
|
|
<div v-if="!showJobDetail" class="jobs-container">
|
||
|
|
<h2 class="section-heading">Available Positions</h2>
|
||
|
|
|
||
|
|
<!-- Job Filters Component -->
|
||
|
|
<JobFilters
|
||
|
|
:jobs="allJobListings"
|
||
|
|
:initially-expanded="false"
|
||
|
|
@filter-change="filteredJobListings = $event"
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div class="filter-results">
|
||
|
|
<p class="results-count">{{ filteredJobListings.length }} job{{ filteredJobListings.length !== 1 ? 's' : '' }} found</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="job-cards" v-if="filteredJobListings.length > 0">
|
||
|
|
<JobCard
|
||
|
|
v-for="job in filteredJobListings"
|
||
|
|
:key="job.id"
|
||
|
|
:title="job.title"
|
||
|
|
:company="job.company"
|
||
|
|
:location="job.location"
|
||
|
|
:salary="job.salary"
|
||
|
|
:description="job.description"
|
||
|
|
:applied="job.applied"
|
||
|
|
:tags="job.tags"
|
||
|
|
@apply="handleJobApplication"
|
||
|
|
@click="viewJobDetails(job.id)"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div v-else class="no-results">
|
||
|
|
<div class="no-results-icon">🔍</div>
|
||
|
|
<h3>No jobs found</h3>
|
||
|
|
<p>Try adjusting your search filters to find more opportunities.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<JobDetail
|
||
|
|
v-if="showJobDetail"
|
||
|
|
:jobId="selectedJobId"
|
||
|
|
@close="closeJobDetails"
|
||
|
|
@apply="handleJobApplication"
|
||
|
|
/>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section v-else-if="activeSection === 'dashboard'" class="dashboard-section">
|
||
|
|
<Dashboard />
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section v-else-if="activeSection === 'applications'" class="applications-section">
|
||
|
|
<Applications />
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section v-else-if="activeSection === 'saved'" class="saved-section">
|
||
|
|
<SavedJobs />
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section v-else-if="activeSection === 'profile'" class="profile">
|
||
|
|
<h2>My Profile</h2>
|
||
|
|
<p>Manage your profile information.</p>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section v-else-if="activeSection === 'settings'" class="settings">
|
||
|
|
<h2>Settings</h2>
|
||
|
|
<p>Adjust your account settings.</p>
|
||
|
|
</section>
|
||
|
|
</main>
|
||
|
|
|
||
|
|
<footer>
|
||
|
|
<p>Job Board Application</p>
|
||
|
|
</footer>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<style scoped lang="scss">
|
||
|
|
.app-container {
|
||
|
|
display: flex;
|
||
|
|
min-height: 100vh;
|
||
|
|
}
|
||
|
|
|
||
|
|
.content-wrapper {
|
||
|
|
flex: 1;
|
||
|
|
margin-left: 240px;
|
||
|
|
transition: margin-left 0.3s ease;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
min-height: 100vh;
|
||
|
|
|
||
|
|
.side-nav.collapsed + & {
|
||
|
|
margin-left: 60px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
header {
|
||
|
|
padding: 1.5rem 2rem;
|
||
|
|
margin-bottom: 1rem;
|
||
|
|
border-bottom: 1px solid #eee;
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
margin: 0;
|
||
|
|
font-size: 1.8rem;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
main {
|
||
|
|
flex: 1;
|
||
|
|
width: 100%;
|
||
|
|
max-width: 1100px;
|
||
|
|
margin: 0 auto;
|
||
|
|
padding: 0 2rem;
|
||
|
|
|
||
|
|
section {
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
|
||
|
|
h2 {
|
||
|
|
margin-bottom: 1.5rem;
|
||
|
|
font-size: 1.4rem;
|
||
|
|
color: var(--text-color);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.jobs-container {
|
||
|
|
padding: 1.5rem;
|
||
|
|
background-color: var(--bg-color, #f8f9fa);
|
||
|
|
border-radius: 16px;
|
||
|
|
|
||
|
|
.section-heading {
|
||
|
|
font-size: 1.8rem;
|
||
|
|
font-weight: 600;
|
||
|
|
margin-bottom: 2rem;
|
||
|
|
color: var(--text-color, #333);
|
||
|
|
}
|
||
|
|
|
||
|
|
.filter-results {
|
||
|
|
margin: 1.5rem 0;
|
||
|
|
|
||
|
|
.results-count {
|
||
|
|
font-size: 1rem;
|
||
|
|
color: var(--text-color-secondary, #666);
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.no-results {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
padding: 3rem 1rem;
|
||
|
|
text-align: center;
|
||
|
|
background-color: var(--card-bg, #fff);
|
||
|
|
border-radius: 12px;
|
||
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||
|
|
|
||
|
|
.no-results-icon {
|
||
|
|
font-size: 3rem;
|
||
|
|
margin-bottom: 1rem;
|
||
|
|
opacity: 0.7;
|
||
|
|
}
|
||
|
|
|
||
|
|
h3 {
|
||
|
|
font-size: 1.4rem;
|
||
|
|
margin: 0 0 0.5rem;
|
||
|
|
color: var(--text-color, #333);
|
||
|
|
}
|
||
|
|
|
||
|
|
p {
|
||
|
|
color: var(--text-color-secondary, #666);
|
||
|
|
max-width: 400px;
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.job-cards {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
width: 100%;
|
||
|
|
max-width: 900px;
|
||
|
|
margin: 0 auto;
|
||
|
|
gap: 1.5rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
.dashboard-section {
|
||
|
|
width: 100%;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
footer {
|
||
|
|
text-align: center;
|
||
|
|
padding: 1rem 0;
|
||
|
|
margin-top: 2rem;
|
||
|
|
border-top: 1px solid #eee;
|
||
|
|
font-size: 0.9rem;
|
||
|
|
color: #888;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (prefers-color-scheme: dark) {
|
||
|
|
header {
|
||
|
|
border-bottom-color: #333;
|
||
|
|
}
|
||
|
|
|
||
|
|
main section h2 {
|
||
|
|
color: #eee;
|
||
|
|
}
|
||
|
|
|
||
|
|
footer {
|
||
|
|
border-top-color: #333;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 768px) {
|
||
|
|
.content-wrapper {
|
||
|
|
margin-left: 60px;
|
||
|
|
}
|
||
|
|
|
||
|
|
header {
|
||
|
|
padding: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
main {
|
||
|
|
padding: 0 1rem;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|