jobs/src/App.vue

424 lines
11 KiB
Vue
Raw Normal View History

2025-03-30 11:52:59 +00:00
<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>