Initial React project
This commit is contained in:
133
src/presentation/simulator/pages/JobSimulatorPage.tsx
Normal file
133
src/presentation/simulator/pages/JobSimulatorPage.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Sidebar } from '../../layout/components/Sidebar';
|
||||
import { Topbar } from '../../layout/components/Topbar';
|
||||
import { SimulationService } from '../../../mvvm/services/simulation.service';
|
||||
|
||||
interface JobSimulatorPageProps {
|
||||
onLogout: () => Promise<void>;
|
||||
onNavigate: (key: 'dashboard' | 'cv' | 'jobs' | 'beskeder' | 'ai-jobagent' | 'ai-agent' | 'simulator' | 'abonnement') => void;
|
||||
}
|
||||
|
||||
interface InterviewItem {
|
||||
id: string;
|
||||
job_name: string;
|
||||
company_name: string | null;
|
||||
interview_date: string | null;
|
||||
is_completed: boolean;
|
||||
}
|
||||
|
||||
function asInterviews(value: unknown): InterviewItem[] {
|
||||
if (!value || typeof value !== 'object') {
|
||||
return [];
|
||||
}
|
||||
const root = value as { interviews?: unknown[] };
|
||||
if (!Array.isArray(root.interviews)) {
|
||||
return [];
|
||||
}
|
||||
return root.interviews
|
||||
.map((item) => {
|
||||
if (!item || typeof item !== 'object') {
|
||||
return null;
|
||||
}
|
||||
const source = item as Record<string, unknown>;
|
||||
const id = typeof source.id === 'string' ? source.id : '';
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
id,
|
||||
job_name: typeof source.job_name === 'string' ? source.job_name : 'Interview',
|
||||
company_name: typeof source.company_name === 'string' ? source.company_name : null,
|
||||
interview_date: typeof source.interview_date === 'string' ? source.interview_date : null,
|
||||
is_completed: Boolean(source.is_completed),
|
||||
};
|
||||
})
|
||||
.filter((item): item is InterviewItem => Boolean(item))
|
||||
.slice(0, 6);
|
||||
}
|
||||
|
||||
function formatDate(value: string | null): string {
|
||||
if (!value) {
|
||||
return 'Ingen dato';
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return 'Ingen dato';
|
||||
}
|
||||
return date.toLocaleDateString('da-DK', { day: '2-digit', month: 'short', year: 'numeric' });
|
||||
}
|
||||
|
||||
export function JobSimulatorPage({ onLogout, onNavigate }: JobSimulatorPageProps) {
|
||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => window.localStorage.getItem('arbejd.sidebar.collapsed') === '1');
|
||||
const [items, setItems] = useState<InterviewItem[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const simulationService = useMemo(() => new SimulationService(), []);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
setIsLoading(true);
|
||||
void simulationService
|
||||
.listInterviews(10, 0)
|
||||
.then((response) => {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
setItems(asInterviews(response));
|
||||
})
|
||||
.catch(() => {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
setItems([]);
|
||||
})
|
||||
.finally(() => {
|
||||
if (active) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [simulationService]);
|
||||
|
||||
return (
|
||||
<section className="dashboard-layout">
|
||||
<Sidebar
|
||||
collapsed={sidebarCollapsed}
|
||||
activeKey="simulator"
|
||||
onToggle={() => setSidebarCollapsed((prev) => { const next = !prev; window.localStorage.setItem('arbejd.sidebar.collapsed', next ? '1' : '0'); return next; })}
|
||||
onSelect={(key) => {
|
||||
if (key === 'dashboard' || key === 'cv' || key === 'jobs' || key === 'beskeder' || key === 'ai-jobagent' || key === 'ai-agent' || key === 'simulator' || key === 'abonnement') {
|
||||
onNavigate(key);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<main className="dashboard-main">
|
||||
<Topbar title="Interview Simulator" userName="Anders Jensen" planLabel="Jobseeker Pro" onLogout={onLogout} />
|
||||
<div className="dashboard-scroll">
|
||||
<article className="glass-panel dash-card">
|
||||
<div className="dash-header">
|
||||
<h4>Seneste interviews</h4>
|
||||
<button type="button" className="primary-btn jobs-apply-btn">Start nyt interview</button>
|
||||
</div>
|
||||
{isLoading ? <p>Indlæser interviews...</p> : null}
|
||||
{!isLoading && items.length === 0 ? <p>Ingen interviews endnu.</p> : null}
|
||||
<ul className="dashboard-feed-list">
|
||||
{items.map((item) => (
|
||||
<li key={item.id}>
|
||||
<div className="dashboard-feed-item">
|
||||
<strong>{item.job_name}</strong>
|
||||
<span>{item.company_name || 'Ukendt virksomhed'} • {formatDate(item.interview_date)} • {item.is_completed ? 'Gennemført' : 'Ikke færdig'}</span>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user