// Quick City — Career pages (Landing, Jobs list, Job detail)
const { useState: useSC, useEffect: useEC } = React;
// ─────────────────────────────────────────────────────────────────────
// Shared form field
// ─────────────────────────────────────────────────────────────────────
function CField({ label, name, type='text', textarea, required, accept, multiple, hint }) {
return (
{label}
{textarea
?
:
}
{hint &&
{hint}
}
);
}
// ─────────────────────────────────────────────────────────────────────
// Career landing page — /ifm/karriere, /lns/karriere
// ─────────────────────────────────────────────────────────────────────
function CareerLanding({ brand }) {
const { lang } = useT();
const tc = (s) => window.QC_TC ? window.QC_TC(s, lang) : s;
const data = window.QC_pickCareer ? window.QC_pickCareer(brand, lang) : window.CAREER_DATA[brand];
const b = window.QC_DATA.brands[brand];
const stellenHref = `/${brand}/karriere/stellen`;
// Merge "why" + top benefits into a single 4-item list — pick the strongest
const reasons = [...data.why.points.slice(0, 4)];
return (
{/* Hero with inline stats */}
{data.hero.eyebrow}
')}} />
{data.hero.sub}
{data.stats.map(s => (
))}
{/* Why us — kompakt: 4 Gründe + 4 Benefits-Zeile */}
{tc('Warum bei uns')}
{data.why.title}
{reasons.map((p, i) => (
))}
{/* Inline benefits row */}
Außerdem im Paket
{data.benefits.map((bn, i) => (
✓ {bn.t}
))}
{/* Bewerbungsprozess — kompakt als horizontale Zeile */}
{tc('Bewerbungsprozess')}
{tc("So läuft's bei uns ab.")}
{/* Connector line */}
{data.process.map((p, i) => (
))}
{/* CTA + FAQ in einer Sektion */}
{tc('Loslegen')}
{tc('Bereit für den')} {tc('nächsten Schritt?')}
{data.jobs.length} {tc('offene Positionen — vom Bauleiter bis zur Auszubildenden.')}
{tc('Alle Stellen ansehen →')}
{tc('FAQ')}
{tc('Häufige Fragen.')}
{data.faq.map((f, i) => )}
);
}
function FAQItem({ q, a }) {
const [open, setOpen] = useSC(false);
return (
setOpen(o=>!o)} style={{width:'100%', background:'none', border:'none', textAlign:'left', cursor:'pointer', display:'flex', justifyContent:'space-between', alignItems:'flex-start', gap:16, padding:0, fontFamily:'inherit'}}>
{q}
+
{open &&
{a}
}
);
}
// ─────────────────────────────────────────────────────────────────────
// Jobs list page — /:brand/karriere/stellen
// ─────────────────────────────────────────────────────────────────────
function JobsPage({ brand }) {
const { lang } = useT();
const tc = (s) => window.QC_TC ? window.QC_TC(s, lang) : s;
const data = window.QC_pickCareer ? window.QC_pickCareer(brand, lang) : window.CAREER_DATA[brand];
const b = window.QC_DATA.brands[brand];
// Read ?init=1 for initiative-application mode
const initParam = (typeof window !== 'undefined' && window.location.hash.includes('init=1'));
const [activeType, setType] = useSC('all');
const [activeSchedule, setSch] = useSC('all');
const [showInit, setShowInit] = useSC(initParam);
const [submitted, setSubmitted] = useSC(false);
const types = [...new Set(data.jobs.map(j => j.type))];
const schedules = [...new Set(data.jobs.map(j => j.schedule))];
const list = data.jobs.filter(j =>
(activeType === 'all' || j.type === activeType) &&
(activeSchedule === 'all' || j.schedule === activeSchedule)
);
return (
{tc('Offene')} {tc('Stellen.')}
{tc('Filtern Sie nach Bereich und Anstellungsart. Klicken Sie auf eine Stelle für Details und das Bewerbungsformular.')}
{/* Filter: Bereich */}
{tc('Bereich')}
setType('all')}>{tc('Alle')} ({data.jobs.length})
{types.map(t => (
setType(t)}>{tc(t)} ({data.jobs.filter(j=>j.type===t).length})
))}
{/* Filter: Anstellungsart */}
{tc('Anstellungsart')}
setSch('all')}>{tc('Alle')}
{schedules.map(s => (
setSch(s)}>{tc(s)}
))}
{list.length} {tc(list.length===1?'Stelle':'Stellen')} {tc('gefunden')}
{/* Initiativ-Bewerbung Block */}
{tc('Nichts Passendes dabei?')}
{tc('Initiativbewerbung')}
{tc('Wir freuen uns auch über Bewerbungen ohne konkrete Stellenausschreibung. Erzählen Sie uns, was Sie mitbringen.')}
{setShowInit(s=>!s); setSubmitted(false);}}>{showInit?tc('Schließen'):tc('Initiativ bewerben')}
{showInit && !submitted &&
setSubmitted(true)} jobsEmail={b.jobsEmail} />}
{showInit && submitted && }
{/* Cross-brand hint */}
);
}
// ─────────────────────────────────────────────────────────────────────
function JobDetailPage({ brand, id }) {
const { lang } = useT();
const tc = (s) => window.QC_TC ? window.QC_TC(s, lang) : s;
const data = window.QC_pickCareer ? window.QC_pickCareer(brand, lang) : window.CAREER_DATA[brand];
const b = window.QC_DATA.brands[brand];
const job = data.jobs.find(j => j.id === id);
const [submitted, setSubmitted] = useSC(false);
useEC(() => { window.scrollTo(0, 0); }, [id]);
if (!job) {
return (
{tc('Stelle nicht gefunden')}
{tc('Diese Stelle gibt es nicht (mehr).')}
{tc('Sie wurde möglicherweise bereits besetzt. Schauen Sie sich unsere aktuellen Angebote an.')}
);
}
// Per-job specific content, with sensible fallbacks
const role = job.role || [
`Sie übernehmen Verantwortung in der Position als ${job.title.replace(/ \([^)]+\)/, '')}.`,
'Sie arbeiten in einem eingespielten Team mit klaren Strukturen und kurzen Wegen.',
'Je nach Position wirken Sie operativ auf der Baustelle, im Büro oder in der Disposition mit.'
];
const profile = job.profile || [
'Abgeschlossene Ausbildung oder relevante Berufserfahrung im genannten Bereich (Quereinsteiger willkommen, wo passend).',
'Selbstständige, zuverlässige Arbeitsweise und Teamgeist.',
'Gute Deutschkenntnisse für die Verständigung im Team.'
];
const offers = job.offers || data.benefits.map(bn => `${bn.t}: ${bn.d}`);
const showGenericBenefits = !job.offers; // only show the generic benefit grid if no specific offers given
return (
{job.title}
{job.intro &&
{job.intro}
}
📍 {job.location}
⏱ {tc(job.schedule)}
· {tc(job.type)}
{job.urgent && {tc('Dringend gesucht')} }
{tc('Deine Rolle bei uns')}
{tc('Was Sie bei uns machen.')}
{role.map((r, i) =>
{r}
)}
{tc('Dein Profil')}
{tc('Was Sie mitbringen.')}
{profile.map((r, i) =>
{r}
)}
{tc('Was wir bieten')}
{tc('Das erwartet Sie.')}
{showGenericBenefits ? (
{data.benefits.map((bn, i) => (
))}
) : (
{offers.map((r, i) =>
{r}
)}
)}
{/* Apply form */}
{tc('Jetzt bewerben')}
{tc('Bewerbung für:')} {job.title}
{tc('Wir melden uns innerhalb von 5 Werktagen.')}
{!submitted
?
setSubmitted(true)} jobsEmail={job.email || b.jobsEmail} />
: }
);
}
// ─────────────────────────────────────────────────────────────────────
// Apply form + success
// ─────────────────────────────────────────────────────────────────────
function ApplyForm({ jobTitle, onSubmit, jobsEmail }) {
const [sending, setSending] = useSC(false);
const [err, setErr] = useSC(false);
const recipient = jobsEmail === 'tiefbau@quickcity.de' ? 'tiefbau@quickcity.de' : 'info@quickcity.de';
const submit = async (e) => {
e.preventDefault();
if (sending) return;
const form = e.target;
if (!form.privacy.checked) return;
const fd = new FormData(form);
fd.append('recipient', recipient);
fd.append('jobTitle', jobTitle || '');
setSending(true); setErr(false);
try {
const res = await fetch('bewerbung.php', { method: 'POST', body: fd });
const data = await res.json().catch(() => ({ ok: false }));
if (res.ok && data.ok) { onSubmit(); }
else { setErr(true); }
} catch (e2) { setErr(true); }
finally { setSending(false); }
};
return (
);
}
function ApplySuccess() {
return (
✓
Vielen Dank!
Ihre Bewerbung ist eingegangen. Eine Bestätigung haben wir Ihnen per E-Mail geschickt — bitte prüfen Sie ggf. auch Ihren Spam-Ordner . Wir melden uns so bald wie möglich bei Ihnen.
);
}
Object.assign(window, { CareerLanding, JobsPage, JobDetailPage });