// project-timesheet-tab.jsx — таймшіт для ГО проектів
function ProjectTimesheetTab({ project, role }) {
const [timesheets, setTimesheets] = React.useState([]);
const [loading, setLoading] = React.useState(true);
const [editing, setEditing] = React.useState(null);
const [filterDate, setFilterDate] = React.useState("2026-06");
const m = window.formatMoney;
React.useEffect(() => {
loadTimesheets();
}, [project.id]);
const loadTimesheets = async () => {
setLoading(true);
try {
const data = await window.loadProjectTimesheets(project.id);
setTimesheets(data || []);
} catch (e) {
console.error("[ERP] Load error:", e);
} finally {
setLoading(false);
}
};
const handleSave = async (data) => {
try {
await window.saveProjectTimesheet({
...data,
project: project.id,
dailyHours: parseFloat(data.dailyHours),
hourlyRate: Math.round(parseFloat(data.hourlyRate) * 100)
});
await loadTimesheets();
setEditing(null);
} catch (e) {
alert("Помилка: " + e.message);
}
};
const handleDelete = async (id) => {
if (!confirm("Видалити запис?")) return;
try {
await window.deleteProjectTimesheet(id, project.id);
await loadTimesheets();
} catch (e) {
alert("Помилка: " + e.message);
}
};
const canEdit = ["director", "accountant", "hr_team"].includes(role);
const filtered = timesheets.filter(ts => !filterDate || ts.date.startsWith(filterDate));
const totalHours = filtered.reduce((s, ts) => s + (ts.dailyHours || 0), 0);
const totalAmount = filtered.reduce((s, ts) => s + ((ts.dailyHours || 0) * (ts.hourlyRate || 0) / 100), 0);
return (
Днів роботи
{filtered.length}
Всього годин
{totalHours.toFixed(1)}
До сплати
{m(Math.round(totalAmount * 100))} ₴
setFilterDate(e.target.value)}
className="cp-input"
style={{ flex: "0 0 160px" }}
/>
{canEdit && (
)}
{loading ? (
Завантаження...
) : filtered.length === 0 ? (
Немає записів за цей період
) : (
| Дата |
Години |
Ставка/год |
Сума |
Примітка |
Скріншот |
{canEdit && Дії | }
{filtered.map(ts => (
setEditing(ts)}
onSave={handleSave}
onDelete={() => handleDelete(ts.id)}
canEdit={canEdit}
m={m}
/>
))}
)}
{editing && (
setEditing(null)}
/>
)}
);
}
function TimesheetRow({ ts, isEditing, onEdit, onSave, onDelete, canEdit, m }) {
const hours = ts.dailyHours || 0;
const rate = ts.hourlyRate || 0;
const amount = hours * rate / 100;
if (isEditing) {
return (
|
|
);
}
return (
| {ts.date} |
{hours.toFixed(1)} |
{m(rate)} ₴ |
{m(Math.round(amount * 100))} ₴ |
{ts.note || "—"} |
{ts.screenshot ? "✓" : "—"} |
{canEdit && (
|
)}
);
}
function TimesheetModal({ ts, onSave, onClose }) {
const getSysDate = () => new Date().toISOString().split("T")[0];
const [form, setForm] = React.useState({
date: ts.date || getSysDate(),
employee: ts.employee || "",
dailyHours: ts.dailyHours || "",
note: ts.note || ""
});
const [hourlyRate, setHourlyRate] = React.useState(ts.hourlyRate ? ts.hourlyRate / 100 : "");
const [screenshotFile, setScreenshotFile] = React.useState(null);
React.useEffect(() => {
// Завантажити ID користувача при відкритті
const loadUser = async () => {
if (!form.employee && window.getCurrentUserId) {
try {
const userId = await window.getCurrentUserId();
if (userId) {
setForm(prev => ({ ...prev, employee: userId }));
}
} catch (e) {
console.error("[ERP] Failed to get user:", e);
}
}
};
loadUser();
}, []);
const handleSave = () => {
if (!form.date) { alert("Виберіть дату"); return; }
if (!form.dailyHours || parseFloat(form.dailyHours) <= 0) { alert("Введіть години > 0"); return; }
if (!hourlyRate || parseFloat(hourlyRate) <= 0) { alert("Введіть ставку > 0"); return; }
if (!form.employee) { alert("Завантажується користувач..."); return; }
if (!screenshotFile && !form.screenshot) { alert("Завантажте скріншот"); return; }
onSave({
...form,
hourlyRate: Math.round(parseFloat(hourlyRate) * 100),
screenshot: screenshotFile || form.screenshot
});
};
return (
setForm({ ...form, date: e.target.value })}
/>
setForm({ ...form, dailyHours: e.target.value })}
step="0.5"
min="0"
placeholder="напр. 8"
/>
setHourlyRate(e.target.value)}
step="0.01"
min="0"
placeholder="напр. 300"
/>
document.getElementById("screenshot-input").click()}
>
{screenshotFile ? (
✓ {screenshotFile.name}
) : form.screenshot ? (
✓ Скріншот завантажено
) : (
📷 Клікни для завантаження скріншота
)}
setScreenshotFile(e.target.files?.[0] || null)}
style={{ display: "none" }}
/>
);
}
window.ProjectTimesheetTab = ProjectTimesheetTab;