Web UI Ok
This commit is contained in:
@@ -108,7 +108,7 @@
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="byLocationTab" data-bs-toggle="tab"
|
||||
data-bs-target="#byLocationPanel" type="button" role="tab">
|
||||
By location
|
||||
By collecte
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
@@ -163,14 +163,14 @@
|
||||
|
||||
// Timefold server URL - modify this to match your server
|
||||
// Option 1: Direct (nécessite CORS activé sur le serveur)
|
||||
// const TIMEFOLD_SERVER = 'http://10.0.100.13:8080';
|
||||
const TIMEFOLD_SERVER = 'http://10.0.100.13:8080';
|
||||
|
||||
// Option 2: Via proxy local (démarrez le serveur proxy sur port 3000)
|
||||
// Option 2: Si vous servez depuis le serveur Timefold même
|
||||
// const TIMEFOLD_SERVER = window.location.origin;
|
||||
|
||||
// Option 3: Via proxy local (si vous utilisez un proxy)
|
||||
// const TIMEFOLD_SERVER = 'http://localhost:3000/api';
|
||||
|
||||
// Option 3: Si vous servez depuis le serveur Timefold
|
||||
const TIMEFOLD_SERVER = window.location.origin;
|
||||
|
||||
// Timeline configuration
|
||||
const timelineOptions = {
|
||||
timeAxis: {scale: "hour", step: 6},
|
||||
@@ -270,7 +270,16 @@
|
||||
const reader = new FileReader();
|
||||
reader.onload = function(e) {
|
||||
try {
|
||||
const jsonData = JSON.parse(e.target.result);
|
||||
const rawText = e.target.result;
|
||||
|
||||
// Debug: montrer les premiers caractères
|
||||
console.log('Premiers caractères du fichier:', rawText.substring(0, 50));
|
||||
console.log('Code du premier caractère:', rawText.charCodeAt(0));
|
||||
|
||||
// Nettoyer le texte (supprimer BOM et espaces invisibles)
|
||||
const cleanText = rawText.trim().replace(/^\uFEFF/, '');
|
||||
|
||||
const jsonData = JSON.parse(cleanText);
|
||||
loadedSchedule = jsonData;
|
||||
|
||||
// Show file info
|
||||
@@ -285,10 +294,26 @@
|
||||
|
||||
showNotification('File loaded successfully!', 'success');
|
||||
} catch (error) {
|
||||
showNotification('Error parsing JSON file: ' + error.message, 'danger');
|
||||
console.error('Erreur détaillée:', error);
|
||||
console.error('Contenu du fichier:', e.target.result.substring(0, 200));
|
||||
showNotification(`Error parsing JSON file: ${error.message}`, 'danger');
|
||||
|
||||
// Afficher une aide pour résoudre le problème
|
||||
const helpMessage = `
|
||||
<div class="alert alert-warning mt-3">
|
||||
<h6>Conseils pour résoudre le problème:</h6>
|
||||
<ul class="mb-0">
|
||||
<li>Vérifiez que le fichier commence bien par { et se termine par }</li>
|
||||
<li>Sauvegardez le fichier en UTF-8 sans BOM</li>
|
||||
<li>Vérifiez qu'il n'y a pas de virgules en trop</li>
|
||||
<li>Testez votre JSON sur <a href="https://jsonlint.com" target="_blank">JSONLint</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
$('#fileInfo').html(helpMessage).show();
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
}
|
||||
|
||||
function loadDemoData() {
|
||||
@@ -325,15 +350,49 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$.post(TIMEFOLD_SERVER + '/schedules', JSON.stringify(loadedSchedule))
|
||||
.done(function(data) {
|
||||
scheduleId = data;
|
||||
console.log('Sending data to solve:', loadedSchedule);
|
||||
|
||||
$.post({
|
||||
url: TIMEFOLD_SERVER + '/schedules',
|
||||
data: JSON.stringify(loadedSchedule),
|
||||
contentType: 'application/json',
|
||||
dataType: 'text' // Attendre du texte (l'ID du job)
|
||||
})
|
||||
.done(function(data, textStatus, xhr) {
|
||||
console.log('Solve response:', data);
|
||||
console.log('Response status:', xhr.status);
|
||||
console.log('Response headers:', xhr.getAllResponseHeaders());
|
||||
|
||||
if (data && data.trim()) {
|
||||
scheduleId = data.trim();
|
||||
refreshSolvingButtons(true);
|
||||
showNotification('Solving started...', 'info');
|
||||
})
|
||||
.fail(function(xhr) {
|
||||
showNotification('Failed to start solving: ' + xhr.responseText, 'danger');
|
||||
} else {
|
||||
showNotification('Server returned empty response', 'danger');
|
||||
}
|
||||
})
|
||||
.fail(function(xhr, textStatus, errorThrown) {
|
||||
console.error('Solve failed:', {
|
||||
status: xhr.status,
|
||||
statusText: xhr.statusText,
|
||||
responseText: xhr.responseText,
|
||||
textStatus: textStatus,
|
||||
errorThrown: errorThrown
|
||||
});
|
||||
|
||||
let errorMsg = 'Failed to start solving: ';
|
||||
if (xhr.status === 0) {
|
||||
errorMsg += 'Cannot connect to server. Check CORS configuration.';
|
||||
} else if (xhr.status === 404) {
|
||||
errorMsg += 'Endpoint not found. Check server URL.';
|
||||
} else if (xhr.responseText) {
|
||||
errorMsg += xhr.responseText;
|
||||
} else {
|
||||
errorMsg += `${xhr.status} ${xhr.statusText}`;
|
||||
}
|
||||
|
||||
showNotification(errorMsg, 'danger');
|
||||
});
|
||||
}
|
||||
|
||||
function stopSolving() {
|
||||
@@ -437,14 +496,32 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Render locations and shifts
|
||||
const locations = [...new Set(schedule.shifts.map(shift => shift.location))];
|
||||
locations.forEach(location => {
|
||||
byLocationGroupDataSet.add({
|
||||
id: location,
|
||||
content: location
|
||||
// Render collectes/shifts as groups in location view
|
||||
if (schedule.collectes && schedule.shifts) {
|
||||
// Create individual groups for each shift within collectes
|
||||
schedule.shifts.forEach((shift, index) => {
|
||||
const collecte = schedule.collectes.find(c => c.id === shift.collecte?.id);
|
||||
const collecteInfo = collecte ?
|
||||
`Collecte: ${collecte.id} - Requis: ${Object.entries(collecte.requiredSkills || {}).map(([skill, count]) => `${count} ${skill}`).join(', ')}` :
|
||||
'Collecte inconnue';
|
||||
|
||||
byLocationGroupDataSet.add({
|
||||
id: `shift-group-${index}`,
|
||||
content: `<div><strong>${shift.location}</strong><br/>
|
||||
<small>${collecteInfo}</small><br/>
|
||||
<small class="text-primary">Shift ${shift.requiredSkill}</small></div>`
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if (schedule.shifts) {
|
||||
// Fallback for old format without collectes
|
||||
const locations = [...new Set(schedule.shifts.map(shift => shift.location))];
|
||||
locations.forEach(location => {
|
||||
byLocationGroupDataSet.add({
|
||||
id: location,
|
||||
content: location
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Render shifts
|
||||
schedule.shifts.forEach((shift, index) => {
|
||||
@@ -461,17 +538,17 @@
|
||||
byEmployeeItemDataSet.add({
|
||||
id: `shift-${index}-emp`,
|
||||
group: shift.employee.name,
|
||||
content: `<div><strong>${shift.location}</strong><br/>
|
||||
content: `<div><strong>${shift.collecte?.id || shift.location}</strong><br/>
|
||||
<span class="badge" style="background-color:${skillColor}">${shift.requiredSkill}</span></div>`,
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
style: `background-color: ${shiftColor}`
|
||||
});
|
||||
|
||||
// Add to location timeline
|
||||
// Add to shift-specific timeline (each shift has its own group)
|
||||
byLocationItemDataSet.add({
|
||||
id: `shift-${index}-loc`,
|
||||
group: shift.location,
|
||||
group: `shift-group-${index}`,
|
||||
content: `<div><strong>${shift.employee.name}</strong><br/>
|
||||
<span class="badge" style="background-color:${skillColor}">${shift.requiredSkill}</span></div>`,
|
||||
start: startTime,
|
||||
@@ -482,8 +559,8 @@
|
||||
// Unassigned shift
|
||||
byLocationItemDataSet.add({
|
||||
id: `shift-${index}-unassigned`,
|
||||
group: shift.location,
|
||||
content: `<div><strong>Unassigned</strong><br/>
|
||||
group: `shift-group-${index}`,
|
||||
content: `<div><strong>Non assigné</strong><br/>
|
||||
<span class="badge bg-secondary">${shift.requiredSkill}</span></div>`,
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
@@ -492,6 +569,51 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Fallback for old format without collectes
|
||||
if (!schedule.collectes && schedule.shifts) {
|
||||
// Override groups for old format - use locations instead
|
||||
byLocationGroupDataSet.clear();
|
||||
const locations = [...new Set(schedule.shifts.map(shift => shift.location))];
|
||||
locations.forEach(location => {
|
||||
byLocationGroupDataSet.add({
|
||||
id: location,
|
||||
content: location
|
||||
});
|
||||
});
|
||||
|
||||
// Re-render shifts with location groups for old format
|
||||
schedule.shifts.forEach((shift, index) => {
|
||||
const startTime = new Date(shift.start);
|
||||
const endTime = new Date(shift.end);
|
||||
|
||||
if (shift.employee) {
|
||||
const hasRequiredSkill = shift.employee.skills.includes(shift.requiredSkill);
|
||||
const skillColor = hasRequiredSkill ? COLORS.SKILL_MATCH : COLORS.SKILL_MISMATCH;
|
||||
const shiftColor = getShiftColor(shift, shift.employee);
|
||||
|
||||
byLocationItemDataSet.add({
|
||||
id: `shift-${index}-loc-old`,
|
||||
group: shift.location,
|
||||
content: `<div><strong>${shift.employee.name}</strong><br/>
|
||||
<span class="badge" style="background-color:${skillColor}">${shift.requiredSkill}</span></div>`,
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
style: `background-color: ${shiftColor}`
|
||||
});
|
||||
} else {
|
||||
byLocationItemDataSet.add({
|
||||
id: `shift-${index}-unassigned-old`,
|
||||
group: shift.location,
|
||||
content: `<div><strong>Non assigné</strong><br/>
|
||||
<span class="badge bg-secondary">${shift.requiredSkill}</span></div>`,
|
||||
start: startTime,
|
||||
end: endTime,
|
||||
style: `background-color: ${COLORS.UNASSIGNED}`
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set timeline window
|
||||
if (schedule.shifts.length > 0) {
|
||||
const dates = schedule.shifts.map(shift => new Date(shift.start));
|
||||
@@ -609,4 +731,4 @@
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user