| | |
| | const COLOR_IDEAL = '#8AE234'; |
| | const COLOR_IDEAL_BG = '#8AE23433'; |
| | const COLOR_ACCEPTABLE = '#FCAF3E'; |
| | const COLOR_ACCEPTABLE_BG = '#FCAF3E33'; |
| | const COLOR_OVERDUE = '#EF2929'; |
| | const COLOR_OVERDUE_BG = '#EF292999'; |
| | const BRAND_GREEN = '#10b981'; |
| | const HIGHLIGHT_COLOR = 'rgba(99, 102, 241, 0.15)'; |
| |
|
| | var autoRefreshIntervalId = null; |
| | let highlightedCrewId = null; |
| |
|
| | let demoDataId = null; |
| | let scheduleId = null; |
| | let loadedSchedule = null; |
| |
|
| | const byCrewPanel = document.getElementById("byCrewPanel"); |
| | const byCrewTimelineOptions = { |
| | timeAxis: {scale: "day"}, |
| | orientation: {axis: "top"}, |
| | stack: false, |
| | xss: {disabled: true}, |
| | zoomMin: 3 * 1000 * 60 * 60 * 24 |
| | }; |
| | var byCrewGroupData = new vis.DataSet(); |
| | var byCrewItemData = new vis.DataSet(); |
| | var byCrewTimeline = new vis.Timeline(byCrewPanel, byCrewItemData, byCrewGroupData, byCrewTimelineOptions); |
| |
|
| | const byJobPanel = document.getElementById("byJobPanel"); |
| | const byJobTimelineOptions = { |
| | timeAxis: {scale: "day"}, |
| | orientation: {axis: "top"}, |
| | xss: {disabled: true}, |
| | zoomMin: 3 * 1000 * 60 * 60 * 24 |
| | }; |
| | var byJobGroupData = new vis.DataSet(); |
| | var byJobItemData = new vis.DataSet(); |
| | var byJobTimeline = new vis.Timeline(byJobPanel, byJobItemData, byJobGroupData, byJobTimelineOptions); |
| |
|
| |
|
| | $(document).ready(function () { |
| | replaceQuickstartSolverForgeAutoHeaderFooter(); |
| |
|
| | $("#solveButton").click(function () { |
| | solve(); |
| | }); |
| | $("#stopSolvingButton").click(function () { |
| | stopSolving(); |
| | }); |
| | $("#analyzeButton").click(function () { |
| | analyze(); |
| | }); |
| | |
| | $("#byCrewTab").on('shown.bs.tab', function (event) { |
| | byCrewTimeline.redraw(); |
| | }) |
| | $("#byJobTab").on('shown.bs.tab', function (event) { |
| | byJobTimeline.redraw(); |
| | }) |
| |
|
| | |
| | byCrewTimeline.on('select', function(properties) { |
| | if (properties.items.length > 0) { |
| | const itemId = properties.items[0]; |
| | |
| | if (!String(itemId).includes('_')) { |
| | showJobDetails(itemId); |
| | } |
| | byCrewTimeline.setSelection([]); |
| | } |
| | }); |
| | byJobTimeline.on('select', function(properties) { |
| | if (properties.items.length > 0) { |
| | const itemId = properties.items[0]; |
| | if (!String(itemId).includes('_')) { |
| | showJobDetails(itemId); |
| | } |
| | byJobTimeline.setSelection([]); |
| | } |
| | }); |
| |
|
| | setupAjax(); |
| | fetchDemoData(); |
| | }); |
| |
|
| | function setupAjax() { |
| | $.ajaxSetup({ |
| | headers: { |
| | 'Content-Type': 'application/json', |
| | 'Accept': 'application/json,text/plain', |
| | } |
| | }); |
| |
|
| | |
| | jQuery.each(["put", "delete"], function (i, method) { |
| | jQuery[method] = function (url, data, callback, type) { |
| | if (jQuery.isFunction(data)) { |
| | type = type || callback; |
| | callback = data; |
| | data = undefined; |
| | } |
| | return jQuery.ajax({ |
| | url: url, |
| | type: method, |
| | dataType: type, |
| | data: data, |
| | success: callback |
| | }); |
| | }; |
| | }); |
| | } |
| |
|
| | function fetchDemoData() { |
| | $.get("/demo-data", function (data) { |
| | data.forEach(item => { |
| | $("#testDataButton").append($('<a id="' + item + 'TestData" class="dropdown-item" href="#">' + item + '</a>')); |
| |
|
| | $("#" + item + "TestData").click(function () { |
| | switchDataDropDownItemActive(item); |
| | scheduleId = null; |
| | demoDataId = item; |
| |
|
| | refreshSchedule(); |
| | }); |
| | }); |
| |
|
| | |
| | demoDataId = data[0]; |
| | switchDataDropDownItemActive(demoDataId); |
| | refreshSchedule(); |
| | }).fail(function (xhr, ajaxOptions, thrownError) { |
| | |
| | let $demo = $("#demo"); |
| | $demo.empty(); |
| | $demo.html("<h1><p align=\"center\">No test data available</p></h1>") |
| | }); |
| | } |
| |
|
| | function switchDataDropDownItemActive(newItem) { |
| | activeCssClass = "active"; |
| | $("#testDataButton > a." + activeCssClass).removeClass(activeCssClass); |
| | $("#" + newItem + "TestData").addClass(activeCssClass); |
| | } |
| |
|
| | function refreshSchedule() { |
| | let path = "/schedules/" + scheduleId; |
| | if (scheduleId === null) { |
| | if (demoDataId === null) { |
| | alert("Please select a test data set."); |
| | return; |
| | } |
| |
|
| | path = "/demo-data/" + demoDataId; |
| | } |
| |
|
| | $.getJSON(path, function (schedule) { |
| | loadedSchedule = schedule; |
| | renderSchedule(schedule); |
| | }) |
| | .fail(function (xhr, ajaxOptions, thrownError) { |
| | showError("Getting the schedule has failed.", xhr); |
| | refreshSolvingButtons(false); |
| | }); |
| | } |
| |
|
| | function renderSchedule(schedule) { |
| | refreshSolvingButtons(schedule.solverStatus != null && schedule.solverStatus !== "NOT_SOLVING"); |
| | $("#score").text("Score: " + (schedule.score == null ? "?" : schedule.score)); |
| |
|
| | const unassignedJobs = $("#unassignedJobs"); |
| | unassignedJobs.children().remove(); |
| | var unassignedJobsCount = 0; |
| | byCrewGroupData.clear(); |
| | byJobGroupData.clear(); |
| | byCrewItemData.clear(); |
| | byJobItemData.clear(); |
| |
|
| | $.each(schedule.crews, (index, crew) => { |
| | byCrewGroupData.add({id: crew.id, content: crew.name}); |
| | }); |
| |
|
| | $.each(schedule.jobs, (index, job) => { |
| | const jobGroupElement = $(`<div/>`) |
| | .append($(`<h5 class="card-title mb-1"/>`).text(job.name)) |
| | .append($(`<p class="card-text ms-2 mb-0"/>`).text(`${job.durationInDays} workdays`)); |
| | byJobGroupData.add({ |
| | id: job.id, |
| | content: jobGroupElement.html() |
| | }); |
| | byJobItemData.add({ |
| | id: job.id + "_readyToIdealEnd", group: job.id, |
| | start: job.minStartDate, end: job.idealEndDate, |
| | type: "background", |
| | style: `background-color: ${COLOR_IDEAL_BG}` |
| | }); |
| | byJobItemData.add({ |
| | id: job.id + "_idealEndToDue", group: job.id, |
| | start: job.idealEndDate, end: job.maxEndDate, |
| | type: "background", |
| | style: `background-color: ${COLOR_ACCEPTABLE_BG}` |
| | }); |
| |
|
| | if (job.crew == null || job.startDate == null) { |
| | unassignedJobsCount++; |
| | const unassignedJobElement = $(`<div class="card-body p-2"/>`) |
| | .append($(`<h5 class="card-title mb-1"/>`).text(job.name)) |
| | .append($(`<p class="card-text ms-2 mb-0"/>`).text(`${job.durationInDays} workdays`)) |
| | .append($(`<p class="card-text ms-2 mb-0"/>`).text(`Start: ${job.minStartDate}`)) |
| | .append($(`<p class="card-text ms-2 mb-0"/>`).text(`End: ${job.maxEndDate}`)); |
| | const byJobJobElement = $(`<div/>`) |
| | .append($(`<h5 class="card-title mb-1"/>`).text(`Unassigned`)); |
| | $.each(job.tags, (index, tag) => { |
| | const color = pickColor(tag); |
| | unassignedJobElement.append($(`<span class="badge me-1" style="background-color: ${color}"/>`).text(tag)); |
| | byJobJobElement.append($(`<span class="badge me-1" style="background-color: ${color}"/>`).text(tag)); |
| | }); |
| | const card = $(`<div class="card" style="cursor: pointer;"/>`).append(unassignedJobElement); |
| | card.click(() => showJobDetails(job.id)); |
| | unassignedJobs.append($(`<div class="col"/>`).append(card)); |
| | byJobItemData.add({ |
| | id: job.id, |
| | group: job.id, |
| | content: byJobJobElement.html(), |
| | start: job.minStartDate, |
| | end: JSJoda.LocalDate.parse(job.minStartDate).plusDays(job.durationInDays).toString(), |
| | style: `background-color: ${COLOR_OVERDUE_BG}` |
| | }); |
| | } else { |
| | const beforeReady = JSJoda.LocalDate.parse(job.startDate).isBefore(JSJoda.LocalDate.parse(job.minStartDate)); |
| | const afterDue = JSJoda.LocalDate.parse(job.endDate).isAfter(JSJoda.LocalDate.parse(job.maxEndDate)); |
| | const byCrewJobElement = $(`<div/>`) |
| | .append($(`<h5 class="card-title mb-1"/>`).text(job.name)) |
| | .append($(`<p class="card-text ms-2 mb-0"/>`).text(`${job.durationInDays} workdays`)); |
| | const byJobJobElement = $(`<div/>`) |
| | .append($(`<h5 class="card-title mb-1"/>`).text(job.crew.name)); |
| | if (beforeReady) { |
| | byCrewJobElement.append($(`<p class="badge badge-danger mb-0"/>`).text(`Before ready (too early)`)); |
| | byJobJobElement.append($(`<p class="badge badge-danger mb-0"/>`).text(`Before ready (too early)`)); |
| | } |
| | if (afterDue) { |
| | byCrewJobElement.append($(`<p class="badge badge-danger mb-0"/>`).text(`After due (too late)`)); |
| | byJobJobElement.append($(`<p class="badge badge-danger mb-0"/>`).text(`After due (too late)`)); |
| | } |
| | $.each(job.tags, (index, tag) => { |
| | const color = pickColor(tag); |
| | byCrewJobElement.append($(`<span class="badge me-1" style="background-color: ${color}"/>`).text(tag)); |
| | byJobJobElement.append($(`<span class="badge me-1" style="background-color: ${color}"/>`).text(tag)); |
| | }); |
| | byCrewItemData.add({ |
| | id: job.id, group: job.crew.id, |
| | content: byCrewJobElement.html(), |
| | start: job.startDate, end: job.endDate |
| | }); |
| | byJobItemData.add({ |
| | id: job.id, group: job.id, |
| | content: byJobJobElement.html(), |
| | start: job.startDate, end: job.endDate, |
| | crewId: job.crew.id |
| | }); |
| | } |
| | }); |
| | if (unassignedJobsCount === 0) { |
| | unassignedJobs.append($(`<p/>`).text(`There are no unassigned jobs.`)); |
| | } |
| | byCrewTimeline.setWindow(schedule.workCalendar.fromDate, schedule.workCalendar.toDate); |
| | byJobTimeline.setWindow(schedule.workCalendar.fromDate, schedule.workCalendar.toDate); |
| |
|
| | |
| | renderCrewTable(schedule); |
| | highlightedCrewId = null; |
| | } |
| |
|
| | function solve() { |
| | $.post("/schedules", JSON.stringify(loadedSchedule), function (data) { |
| | scheduleId = data; |
| | refreshSolvingButtons(true); |
| | }).fail(function (xhr, ajaxOptions, thrownError) { |
| | showError("Start solving failed.", xhr); |
| | refreshSolvingButtons(false); |
| | }, |
| | "text"); |
| | } |
| |
|
| | function analyze() { |
| | analyzeScore(loadedSchedule, "/schedules/analyze"); |
| | } |
| |
|
| | function refreshSolvingButtons(solving) { |
| | if (solving) { |
| | $("#solveButton").hide(); |
| | $("#stopSolvingButton").show(); |
| | $("#solvingSpinner").addClass("active"); |
| | if (autoRefreshIntervalId == null) { |
| | autoRefreshIntervalId = setInterval(refreshSchedule, 2000); |
| | } |
| | } else { |
| | $("#solveButton").show(); |
| | $("#stopSolvingButton").hide(); |
| | $("#solvingSpinner").removeClass("active"); |
| | if (autoRefreshIntervalId != null) { |
| | clearInterval(autoRefreshIntervalId); |
| | autoRefreshIntervalId = null; |
| | } |
| | } |
| | } |
| |
|
| | function stopSolving() { |
| | $.delete("/schedules/" + scheduleId, function () { |
| | refreshSolvingButtons(false); |
| | refreshSchedule(); |
| | }).fail(function (xhr, ajaxOptions, thrownError) { |
| | showError("Stop solving failed.", xhr); |
| | }); |
| | } |
| |
|
| | function copyTextToClipboard(id) { |
| | var text = document.getElementById(id).innerText; |
| | navigator.clipboard.writeText(text); |
| | } |
| |
|
| | function replaceQuickstartSolverForgeAutoHeaderFooter() { |
| | const solverforgeHeader = $("header#solverforge-auto-header"); |
| | if (solverforgeHeader != null) { |
| | solverforgeHeader.css("background-color", "#ffffff"); |
| | solverforgeHeader.append( |
| | $(`<div class="container-fluid"> |
| | <nav class="navbar sticky-top navbar-expand-lg shadow-sm mb-3" style="background-color: #ffffff;"> |
| | <a class="navbar-brand" href="https://www.solverforge.org"> |
| | <img src="/webjars/solverforge/img/solverforge-horizontal.svg" alt="SolverForge logo" width="400"> |
| | </a> |
| | <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> |
| | <span class="navbar-toggler-icon"></span> |
| | </button> |
| | <div class="collapse navbar-collapse" id="navbarNav"> |
| | <ul class="nav nav-pills"> |
| | <li class="nav-item active" id="navUIItem"> |
| | <button class="nav-link active" id="navUI" data-bs-toggle="pill" data-bs-target="#demo" type="button" style="color: #1f2937;">Demo UI</button> |
| | </li> |
| | <li class="nav-item" id="navRestItem"> |
| | <button class="nav-link" id="navRest" data-bs-toggle="pill" data-bs-target="#rest" type="button" style="color: #1f2937;">Guide</button> |
| | </li> |
| | <li class="nav-item" id="navOpenApiItem"> |
| | <button class="nav-link" id="navOpenApi" data-bs-toggle="pill" data-bs-target="#openapi" type="button" style="color: #1f2937;">REST API</button> |
| | </li> |
| | </ul> |
| | </div> |
| | <div class="ms-auto d-flex align-items-center gap-3"> |
| | <div class="dropdown"> |
| | <button class="btn dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" style="background-color: #10b981; color: #ffffff; border-color: #10b981;"> |
| | Data |
| | </button> |
| | <div id="testDataButton" class="dropdown-menu" aria-labelledby="dropdownMenuButton"></div> |
| | </div> |
| | </div> |
| | </nav> |
| | </div>`), |
| | ); |
| | } |
| |
|
| | const solverforgeFooter = $("footer#solverforge-auto-footer"); |
| | if (solverforgeFooter != null) { |
| | solverforgeFooter.append( |
| | $(`<footer class="bg-black text-white-50"> |
| | <div class="container"> |
| | <div class="hstack gap-3 p-4"> |
| | <div class="ms-auto"><a class="text-white" href="https://www.solverforge.org">SolverForge</a></div> |
| | <div class="vr"></div> |
| | <div><a class="text-white" href="https://www.solverforge.org/docs">Documentation</a></div> |
| | <div class="vr"></div> |
| | <div><a class="text-white" href="https://github.com/SolverForge/solverforge-legacy">Code</a></div> |
| | <div class="vr"></div> |
| | <div class="me-auto"><a class="text-white" href="mailto:info@solverforge.org">Support</a></div> |
| | </div> |
| | </div> |
| | </footer>`), |
| | ); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | function showJobDetails(jobId) { |
| | const job = loadedSchedule.jobs.find(j => j.id === jobId); |
| | if (!job) return; |
| |
|
| | $("#jobDetailsName").text(job.name); |
| | $("#jobDetailsCrew").html(job.crew ? |
| | `<i class="fas fa-hard-hat me-1"></i>${job.crew.name}` : |
| | '<span class="text-danger">Unassigned</span>'); |
| | $("#jobDetailsDuration").text(`${job.durationInDays} workdays`); |
| | $("#jobDetailsMinStart").text(job.minStartDate); |
| | $("#jobDetailsMaxEnd").text(job.maxEndDate); |
| | $("#jobDetailsIdealEnd").text(job.idealEndDate); |
| |
|
| | if (job.startDate && job.endDate) { |
| | $("#jobDetailsScheduled").text(`${job.startDate} to ${job.endDate}`); |
| | } else { |
| | $("#jobDetailsScheduled").html('<span class="text-muted">Not scheduled</span>'); |
| | } |
| |
|
| | |
| | const tagsContainer = $("#jobDetailsTags"); |
| | tagsContainer.empty(); |
| | if (job.tags && job.tags.length > 0) { |
| | job.tags.forEach(tag => { |
| | const color = pickColor(tag); |
| | tagsContainer.append($(`<span class="badge me-1" style="background-color: ${color}"/>`).text(tag)); |
| | }); |
| | } else { |
| | tagsContainer.html('<span class="text-muted">None</span>'); |
| | } |
| |
|
| | |
| | const statusDiv = $("#jobDetailsStatus"); |
| | statusDiv.hide().removeClass("alert-danger alert-warning alert-success"); |
| |
|
| | if (!job.crew || !job.startDate) { |
| | statusDiv.addClass("alert-danger").text("This job is not assigned to any crew.").show(); |
| | } else { |
| | const beforeReady = JSJoda.LocalDate.parse(job.startDate).isBefore(JSJoda.LocalDate.parse(job.minStartDate)); |
| | const afterDue = JSJoda.LocalDate.parse(job.endDate).isAfter(JSJoda.LocalDate.parse(job.maxEndDate)); |
| | const afterIdeal = JSJoda.LocalDate.parse(job.endDate).isAfter(JSJoda.LocalDate.parse(job.idealEndDate)); |
| |
|
| | if (beforeReady) { |
| | statusDiv.addClass("alert-warning").text("Scheduled before ready date (too early).").show(); |
| | } else if (afterDue) { |
| | statusDiv.addClass("alert-danger").text("Scheduled after due date (too late).").show(); |
| | } else if (afterIdeal) { |
| | statusDiv.addClass("alert-warning").text("Ends after ideal date.").show(); |
| | } else { |
| | statusDiv.addClass("alert-success").text("Scheduled within ideal window.").show(); |
| | } |
| | } |
| |
|
| | new bootstrap.Modal("#jobDetailsModal").show(); |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | function showCrewDetails(crewId) { |
| | const crew = loadedSchedule.crews.find(c => c.id === crewId); |
| | if (!crew) return; |
| |
|
| | const crewJobs = loadedSchedule.jobs.filter(j => j.crew && j.crew.id === crewId); |
| | const totalWorkdays = crewJobs.reduce((sum, j) => sum + j.durationInDays, 0); |
| |
|
| | $("#crewDetailsName").text(crew.name); |
| | $("#crewDetailsJobCount").text(crewJobs.length); |
| | $("#crewDetailsWorkdays").text(totalWorkdays); |
| |
|
| | |
| | const jobList = $("#crewDetailsJobList"); |
| | jobList.empty(); |
| |
|
| | if (crewJobs.length === 0) { |
| | jobList.append('<div class="list-group-item text-muted">No jobs assigned</div>'); |
| | } else { |
| | crewJobs.forEach(job => { |
| | const afterDue = job.endDate && JSJoda.LocalDate.parse(job.endDate).isAfter(JSJoda.LocalDate.parse(job.maxEndDate)); |
| | const statusIcon = afterDue ? |
| | '<i class="fas fa-exclamation-triangle text-danger me-2"></i>' : |
| | '<i class="fas fa-check-circle text-success me-2"></i>'; |
| |
|
| | const item = $(` |
| | <a href="#" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"> |
| | <div> |
| | ${statusIcon} |
| | <strong>${job.name}</strong> |
| | <small class="text-muted ms-2">${job.durationInDays} days</small> |
| | </div> |
| | <small class="text-muted">${job.startDate || 'Not scheduled'}</small> |
| | </a> |
| | `); |
| | item.click((e) => { |
| | e.preventDefault(); |
| | bootstrap.Modal.getInstance(document.getElementById('crewDetailsModal')).hide(); |
| | setTimeout(() => showJobDetails(job.id), 300); |
| | }); |
| | jobList.append(item); |
| | }); |
| | } |
| |
|
| | new bootstrap.Modal("#crewDetailsModal").show(); |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | function renderCrewTable(schedule) { |
| | const crewTableBody = $("#crewTableBody"); |
| | crewTableBody.empty(); |
| |
|
| | schedule.crews.forEach(crew => { |
| | const crewJobs = schedule.jobs.filter(j => j.crew && j.crew.id === crew.id); |
| | const totalWorkdays = crewJobs.reduce((sum, j) => sum + j.durationInDays, 0); |
| | const color = pickColor("crew" + crew.id); |
| |
|
| | const row = $(` |
| | <tr class="crew-row" data-crew-id="${crew.id}"> |
| | <td> |
| | <div class="crew-color-indicator" style="background-color: ${color};"> |
| | <i class="fas fa-hard-hat"></i> |
| | </div> |
| | </td> |
| | <td><strong>${crew.name}</strong></td> |
| | <td>${crewJobs.length}</td> |
| | <td>${totalWorkdays}</td> |
| | <td> |
| | <button class="btn btn-sm btn-outline-secondary crew-info-btn" title="View details"> |
| | <i class="fas fa-info-circle"></i> |
| | </button> |
| | </td> |
| | </tr> |
| | `); |
| |
|
| | |
| | row.click((e) => { |
| | if (!$(e.target).closest('.crew-info-btn').length) { |
| | toggleCrewHighlight(crew.id); |
| | } |
| | }); |
| |
|
| | |
| | row.find('.crew-info-btn').click((e) => { |
| | e.stopPropagation(); |
| | showCrewDetails(crew.id); |
| | }); |
| |
|
| | crewTableBody.append(row); |
| | }); |
| | } |
| |
|
| | function toggleCrewHighlight(crewId) { |
| | if (highlightedCrewId === crewId) { |
| | clearCrewHighlight(); |
| | } else { |
| | highlightCrew(crewId); |
| | } |
| | } |
| |
|
| | function clearCrewHighlight() { |
| | highlightedCrewId = null; |
| | $(".crew-row").removeClass("table-active"); |
| |
|
| | |
| | byCrewItemData.forEach(item => { |
| | if (item.originalStyle !== undefined) { |
| | byCrewItemData.update({ id: item.id, style: item.originalStyle }); |
| | } |
| | }); |
| | byJobItemData.forEach(item => { |
| | if (item.originalStyle !== undefined) { |
| | byJobItemData.update({ id: item.id, style: item.originalStyle }); |
| | } |
| | }); |
| | } |
| |
|
| | function highlightCrew(crewId) { |
| | highlightedCrewId = crewId; |
| |
|
| | |
| | $(".crew-row").removeClass("table-active"); |
| | $(`[data-crew-id="${crewId}"]`).addClass("table-active"); |
| |
|
| | |
| | byCrewItemData.forEach(item => { |
| | if (item.originalStyle === undefined) { |
| | item.originalStyle = item.style || ""; |
| | } |
| | if (item.group !== crewId) { |
| | byCrewItemData.update({ |
| | id: item.id, |
| | style: item.originalStyle + "; opacity: 0.25;" |
| | }); |
| | } else { |
| | byCrewItemData.update({ |
| | id: item.id, |
| | style: item.originalStyle |
| | }); |
| | } |
| | }); |
| |
|
| | |
| | byJobItemData.forEach(item => { |
| | if (item.originalStyle === undefined) { |
| | item.originalStyle = item.style || ""; |
| | } |
| | |
| | if (item.crewId !== crewId && item.type !== "background") { |
| | byJobItemData.update({ |
| | id: item.id, |
| | style: item.originalStyle + "; opacity: 0.25;" |
| | }); |
| | } else if (item.type !== "background") { |
| | byJobItemData.update({ |
| | id: item.id, |
| | style: item.originalStyle |
| | }); |
| | } |
| | }); |
| | } |
| |
|