Newer
Older
job-tracker / frontend / src / components / JobList.js
import React from 'react';
import { apiDelete, apiGet, apiExport } from '../api';
import { Link } from 'react-router-dom';

function JobList() {
    const [jobs, setJobs] = React.useState([]);
    const [error,setError]=React.useState(null);
    const [selectedJob, setSelectedJob] = React.useState(null);

    const handleExportClick = async () => {
      try {
        const response = await apiExport('/api/jobs/export');
        if (!response.ok) throw new Error('Export failed');

        // Extract filename with timestamp from header
        const disposition = response.headers.get('Content-Disposition');
        const filenameMatch = disposition?.match(/filename="(.+)"/);
        const filename = filenameMatch ? filenameMatch[1] : 'jobs.csv';

        const blob = await response.blob();
        const url = window.URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        a.click();
        window.URL.revokeObjectURL(url);
      } catch (err) {
        setError(err.message);
      }
    };

    React.useEffect(() => {
        apiGet('/api/jobs')
            .then(setJobs)
                .catch(error => setError(error.message));
    }, []);

    const handleDelete = async (id) => {
        if(window.confirm("Are you sure you want to delete this job?")) {
            try {
                await apiDelete(`/api/jobs/${id}`);
                setJobs(prev => prev.filter(j => j.id !== id)); // update local state
            } catch (err) {
                setError(err.message || "Failed to delete job");
            }
        }
    };

    return (
        <div className="container mt-4">
        <div className="d-flex justify-content-between align-items-center mb-3">
            <h2>Jobs</h2>
            <p><Link to="/jobs/new" className="btn btn-primary">+ Add job</Link>
             <button onClick={handleExportClick} className="btn btn-secondary ms-2">Export CSV</button>

            </p>
            </div>            
            {error && <div className="alert alert-danger">{error}</div>}
            <table className='table table-striped table-hover'>
                <thead className='table-dark'>
                    <tr>
                        <th>Role</th>
                        <th>Company</th>
                        <th>Status</th>
                        <th>Job Url</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    {jobs.map(job => (
                        <tr key={job.id}>
                            <td>{job.role}</td>
                            <td>{job.company?.name || "-"}</td>
                            <td>{job.status}</td>
                            <td>{job.jobUrl ? <a href={job.jobUrl} target="_blank" rel="noopener noreferrer" className="text-decoration-none">Job Url</a> : '-'}</td>
                            <td>
                                <Link to={`/jobs/${job.id}/edit`} className="btn btn-sm btn-warning me-2">Edit</Link>
                                <button onClick={() => handleDelete(job.id)} className="btn btn-sm btn-danger">Delete</button>
                                <button onClick={() => setSelectedJob(job)} className="btn btn-sm btn-info ms-2">...</button>
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
            {selectedJob && (
                <div className="mt-4 p-3 border rounded">
                    <div className="d-flex justify-content-between align-items-center mb-3">
                        <h4>Details for {selectedJob.role}</h4>
                        <button onClick={() => setSelectedJob(null)} className="btn btn-sm btn-secondary">Hide Details</button>
                    </div>
                    <p><strong>Role:</strong> {selectedJob.role}</p>
                    <p><strong>Company:</strong> {selectedJob.company?.name || '-'}</p>
                    <p><strong>Status:</strong> {selectedJob.status}</p>
                    <p><strong>Location:</strong> {selectedJob.location || '-'}</p>
                    <p><strong>Job Url:</strong> {selectedJob.jobUrl ? <a href={selectedJob.jobUrl} target="_blank" rel="noopener noreferrer" className="text-decoration-none">{selectedJob.jobUrl}</a> : '-'}</p>
                    {selectedJob.notes && <p><strong>Notes:</strong> {selectedJob.notes}</p>}
                </div>
            )}
            </div>
    );  
}

export default JobList;