// Vállalkozó (Contractor) nézet — gyors teljesítés-rögzítés a fókusz
const ContractorHome = ({ user, setView, openLogger }) => {
const myLogs = window.WORK_LOGS.filter(l => l.contractorId === user.id).slice().reverse();
const balance = window.contractorBalance(user.id);
const todayLogs = myLogs.filter(l => l.date === window.TODAY);
return (
{/* Üdvözlő blokk */}
Szia, {user.name.split(' ')[0]}
Mit csináltál ma?
{/* Egyenleg kártya */}
Egyenleged
{window.formatHufShort(balance.balance)} Ft
kifizetésre vár
Jóváhagyva
{window.formatHuf(balance.approved)}
Kifizetve
{window.formatHuf(balance.paid)}
{/* Új teljesítés CTA */}
{/* Mai aktivitás */}
{todayLogs.length > 0 ? (
Mai munka
{todayLogs.length} bejegyzés
{todayLogs.map(l =>
)}
) : (
Tipp: a leggyorsabb, ha rögtön a munka után rögzíted a teljesítést. 4 koppintás, 1 fotó, kész.
)}
{/* Utolsó néhány teljesítés */}
Legutóbbi teljesítések
{myLogs.slice(0, 4).map(l =>
)}
);
};
const LogCard = ({ log, onClick }) => {
const { labor, total } = window.computeWorkLog(log);
const phase = window.getPhase(log.phaseId);
const task = window.getParentTask(log.parentTaskId);
return (
{window.formatDateShort(log.date)} · {phase?.code}
{task?.name}
{log.description}
{(() => {
const stats = window.workLogStats(log);
if (stats.isPurchase || !stats.hasLabor) {
return Beszerzés;
}
return (
<>
{stats.totalWorkers} fő
·
{stats.totalHours} óra
>
);
})()}
{(() => {
const list = Array.isArray(log.photoList) ? log.photoList : (Array.isArray(log.photos) ? log.photos : null);
const n = list ? list.length : (typeof log.photos === 'number' ? log.photos : 0);
return n > 0 ? <>· {n}> : null;
})()}
{window.formatHufShort(total)} Ft
{log.status === 'rejected' && log.rejectReason && (
{log.rejectReason}
)}
);
};
// === Új teljesítés flow ===
// Két típus: "Munka" (egy vagy több csapat × óra) vagy "Beszerzés" (csak anyag/számla).
const Logger = ({ user, onClose, onSubmit }) => {
const [entryType, setEntryType] = React.useState('labor'); // 'labor' | 'purchase'
const [phaseId, setPhaseId] = React.useState(null);
const [taskId, setTaskId] = React.useState(null);
const [date, setDate] = React.useState(window.TODAY);
const [tiers, setTiers] = React.useState([{ workers: 2, hours: 8 }]);
const [materialCost, setMaterial] = React.useState(0);
const [description, setDesc] = React.useState('');
// photos: [{ id, file, previewUrl }]
const [photos, setPhotos] = React.useState([]);
const photoInputRef = React.useRef(null);
const updateTier = (i, patch) => setTiers(tiers.map((t, idx) => idx === i ? { ...t, ...patch } : t));
const addTier = () => setTiers([...tiers, { workers: 1, hours: 8 }]);
const removeTier = (i) => setTiers(tiers.filter((_, idx) => idx !== i));
const onPhotoPick = (e) => {
const files = Array.from(e.target.files || []);
const next = files.map(f => ({
id: Date.now() + Math.random(),
file: f,
previewUrl: f.type.startsWith('image/') ? URL.createObjectURL(f) : null,
}));
setPhotos(p => [...p, ...next]);
e.target.value = '';
};
const labor = entryType === 'labor'
? tiers.reduce((s, t) => s + (t.workers || 0) * (t.hours || 0) * user.hourlyRate, 0)
: 0;
const total = labor + materialCost;
const totalWorkers = tiers.reduce((s, t) => s + (t.workers || 0), 0);
const totalHours = tiers.reduce((s, t) => s + (t.workers || 0) * (t.hours || 0), 0);
const tasks = phaseId ? window.PARENT_TASKS.filter(t => t.phaseId === phaseId) : [];
const taskBudget = taskId ? window.getParentTask(taskId).budget : 0;
const taskSpent = taskId ? window.parentTaskSpent(taskId) : 0;
const taskPending = taskId ? window.parentTaskPending(taskId) : 0;
const remaining = taskBudget - taskSpent - taskPending;
const canSubmit = phaseId && taskId && date && description.trim().length > 0 && (
totalHours > 0 || materialCost > 0
);
return (
Új teljesítés
{user.name}
{/* 1. Fázis */}
1 · Fázis
{phaseId &&
}
{!phaseId ? (
{window.PHASES.map(p => (
))}
) : (
{window.getPhase(phaseId).code}
{window.getPhase(phaseId).name}
)}
{/* 2. Főfeladat */}
{phaseId && (
2 · Főfeladat
{taskId &&
}
{!taskId ? (
{tasks.map(t => {
const sp = window.parentTaskSpent(t.id);
const pct = t.budget > 0 ? (sp / t.budget) * 100 : 0;
return (
);
})}
) : (
{window.getParentTask(taskId).name}
Keret
{window.formatHuf(taskBudget)}
Felhasznált
{window.formatHuf(taskSpent)}
Maradvány
0 ? 'var(--ok)' : 'var(--danger)', whiteSpace: 'nowrap' }}>{window.formatHuf(remaining)}
)}
)}
{/* 3. Munka + anyag VAGY csak beszerzés */}
{taskId && (
3 · Mit rögzítesz?
{/* Típusváltó */}
setDate(e.target.value)} />
{/* Munka csapatok (tiers) */}
{entryType === 'labor' && (
{tiers.length > 0 && (
össz. {totalWorkers} fő · {totalHours} óra
)}
{tiers.map((t, i) => (
{tiers.length > 1 ? `${i + 1}. csapat` : 'Munkaóra'}
{tiers.length > 1 && (
)}
updateTier(i, { workers: v })} min={1} max={20}/>
updateTier(i, { hours: v })} min={1} max={16}/>
{t.workers} × {t.hours}ó × {window.formatHufShort(user.hourlyRate)} Ft
{window.formatHuf((t.workers || 0) * (t.hours || 0) * user.hourlyRate)}
))}
)}
{/* Anyag / beszerzés ár */}
)}
{/* 4. Leírás + fotó */}
{taskId && (
4 · Leírás és fotók
{photos.map(p => (
{p.previewUrl
?

:
}
))}
Több fotó is feltölthető. A jóváhagyó látja majd.
)}
{/* Sticky CTA */}
{taskId && (
Összesen
{window.formatHuf(total)}
)}
);
};
const Stepper = ({ value, onChange, min = 0, max = 999, step = 1 }) => (
{
const v = parseInt(e.target.value);
if (!isNaN(v)) onChange(Math.max(min, Math.min(max, v)));
}}/>
);
const ContractorHistory = ({ user }) => {
const [filter, setFilter] = React.useState('all');
const myLogs = window.WORK_LOGS.filter(l => l.contractorId === user.id).slice().reverse();
const filtered = filter === 'all' ? myLogs : myLogs.filter(l => l.status === filter);
const counts = {
all: myLogs.length,
pending: myLogs.filter(l => l.status === 'pending').length,
approved: myLogs.filter(l => l.status === 'approved').length,
rejected: myLogs.filter(l => l.status === 'rejected').length,
};
return (
Teljesítéseim
{[
{ k: 'all', l: 'Mind' },
{ k: 'pending', l: 'Várakozik' },
{ k: 'approved', l: 'Jóváhagyva' },
{ k: 'rejected', l: 'Elutasítva' },
].map(t => (
))}
{filtered.map(l =>
)}
{filtered.length === 0 && Nincs ilyen teljesítés.
}
);
};
Object.assign(window, { ContractorHome, Logger, ContractorHistory, LogCard, Stepper });