Briyarne

Untitled

Jan 4th, 2025
45
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
jQuery 9.74 KB | None | 0 0
  1. (async function() {
  2. const BLOCKS = [
  3.     { blockName: 'MORNING', start: '06:00', end: '08:00' },
  4.     { blockName: 'DAY',     start: '08:00', end: '22:00' },
  5.     { blockName: 'NIGHT',   start: '22:00', end: '00:30' },
  6.   ];
  7.  
  8. const DEFAULT_SHIFT_STATUS = "CONFIRMED";
  9.  
  10. async function softDeleteShift(shiftId, csrfToken) {
  11.     console.log(`(API) Soft-deleting shift #${shiftId}`);
  12.     const resp = await fetch(`/admin/driver_shifts/${shiftId}/soft_delete`, {
  13.       method: 'POST',
  14.       headers: {
  15.         "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
  16.         "X-CSRF-Token": csrfToken,
  17.       },
  18.       body: new URLSearchParams({
  19.         authenticity_token: csrfToken
  20.       }),
  21.     });
  22.     if (resp.ok) {
  23.       console.log(`Shift #${shiftId} soft-deleted successfully`);
  24.       return true;
  25.     } else {
  26.       const text = await resp.text();
  27.       console.error(`Failed to soft-delete shift #${shiftId}: ${resp.status}`, text);
  28.       return false;
  29.     }
  30.   }
  31.  
  32. function timeToMinutes(tStr) {
  33. const [HH, MM] = tStr.split(':').map(Number);
  34.     return HH * 60 + MM;
  35.   }
  36.  
  37.   function minutesToTime(m) {
  38.  
  39.     let dayOffset = 0;
  40.     if (m >= 1440) dayOffset = 1440;
  41.     const adj = m - dayOffset;
  42.     const hh = Math.floor(adj / 60);
  43.     const mm = adj % 60;
  44.     return String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0');
  45.   }
  46.  
  47.   function parseShiftRange(startT, endT) {
  48.     let s = timeToMinutes(startT);
  49.     let e = timeToMinutes(endT);
  50.     if (e < s) e += 1440;
  51.     return [s, e];
  52.   }
  53.  
  54.   async function updateShift(shiftId, dateStr, startTime, endTime, csrfToken) {
  55.     console.log(`(API) Update shift #${shiftId} => ${startTime}–${endTime} on ${dateStr}`);
  56.     let startDT = `${dateStr}T${startTime}:00`;
  57.     let endDT   = `${dateStr}T${endTime}:00`;
  58.  
  59.     // crossing midnight?
  60.     if (timeToMinutes(endTime) < timeToMinutes(startTime)) {
  61.       const d = new Date(dateStr);
  62.       d.setDate(d.getDate() + 1);
  63.       const nextDayStr = d.toISOString().split('T')[0];
  64.       endDT = `${nextDayStr}T${endTime}:00`;
  65.     }
  66.  
  67.     const resp = await fetch(`/admin/driver_shifts/${shiftId}`, {
  68.       method: "POST",
  69.       headers: {
  70.         "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
  71.         "X-CSRF-Token": csrfToken
  72.       },
  73.       body: new URLSearchParams({
  74.         '_method': 'patch',
  75.         'driver_shift[active_period_start_date_time]': startDT,
  76.         'driver_shift[active_period_end_date_time]': endDT,
  77.         'authenticity_token': csrfToken,
  78.       })
  79.     });
  80.  
  81.     if (resp.ok) {
  82.       console.log(`Shift #${shiftId} updated => ${startTime}–${endTime}`);
  83.       return true;
  84.     } else {
  85.       const txt = await resp.text();
  86.       console.error(`Failed to update shift #${shiftId}: ${resp.status}`, txt);
  87.       return false;
  88.     }
  89.   }
  90.  
  91.   async function createShift(driverId, dateStr, startTime, endTime, csrfToken) {
  92.     console.log(`(API) Create NEW shift for driver #${driverId}: ${startTime}–${endTime} (date=${dateStr})`);
  93.     let startDT = `${dateStr}T${startTime}:00`;
  94.     let endDT   = `${dateStr}T${endTime}:00`;
  95.  
  96.     if (timeToMinutes(endTime) < timeToMinutes(startTime)) {
  97.       const d = new Date(dateStr);
  98.       d.setDate(d.getDate() + 1);
  99.       const nextDayStr = d.toISOString().split('T')[0];
  100.       endDT = `${nextDayStr}T${endTime}:00`;
  101.     }
  102.  
  103.     const resp = await fetch(`/admin/driver_shifts`, {
  104.       method: "POST",
  105.       headers: {
  106.         "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
  107.         "X-CSRF-Token": csrfToken
  108.       },
  109.       body: new URLSearchParams({
  110.         'driver_shift[date]': dateStr,
  111.         'driver_shift[driver_id]': driverId,
  112.         'driver_shift[active_period_start_date_time]': startDT,
  113.         'driver_shift[active_period_end_date_time]': endDT,
  114.         'driver_shift[status]': DEFAULT_SHIFT_STATUS,
  115.         'authenticity_token': csrfToken,
  116.       })
  117.     });
  118.  
  119.     if (resp.ok) {
  120.       console.log("New shift created successfully!");
  121.       return true;
  122.     } else {
  123.       const txt = await resp.text();
  124.       console.error(`Failed to create new shift: ${resp.status}`, txt);
  125.       return false;
  126.     }
  127.   }
  128.  
  129.   function buildCoverageMap(shifts) {
  130.  
  131.     shifts.sort((a,b) => a.startM - b.startM);
  132.  
  133.     const coverageByBlock = {
  134.       MORNING: [],
  135.       DAY: [],
  136.       NIGHT: []
  137.     };
  138.  
  139.     for (const s of shifts) {
  140.       for (const block of BLOCKS) {
  141.         const [blockStart, blockEnd] = parseShiftRange(block.start, block.end);
  142.         const overlapStart = Math.max(s.startM, blockStart);
  143.         const overlapEnd   = Math.min(s.endM, blockEnd);
  144.         if (overlapEnd > overlapStart) {
  145.           coverageByBlock[block.blockName].push([overlapStart, overlapEnd]);
  146.         }
  147.       }
  148.     }
  149.  
  150.     for (const bn of Object.keys(coverageByBlock)) {
  151.       coverageByBlock[bn].sort((a,b) => a[0]-b[0]);
  152.       const merged = [];
  153.       let current = null;
  154.       for (const interval of coverageByBlock[bn]) {
  155.         if (!current) {
  156.           current = interval;
  157.           continue;
  158.         }
  159.         if (interval[0] <= current[1]) {
  160.           current[1] = Math.max(current[1], interval[1]);
  161.         } else {
  162.           merged.push(current);
  163.           current = interval;
  164.         }
  165.       }
  166.       if (current) merged.push(current);
  167.       coverageByBlock[bn] = merged;
  168.     }
  169.  
  170.     return coverageByBlock;
  171.   }
  172.  
  173.   async function applyBlockCoverage(coverageMap, originalShifts, driverId, shiftDate, csrfToken) {
  174.     const finalIntervals = [];
  175.     for (const block of BLOCKS) {
  176.       const intervals = coverageMap[block.blockName];
  177.       for (const iv of intervals) {
  178.         finalIntervals.push({
  179.           blockName: block.blockName,
  180.           startM: iv[0],
  181.           endM: iv[1]
  182.         });
  183.       }
  184.     }
  185.  
  186.     finalIntervals.sort((a,b) => a.startM - b.startM);
  187.  
  188.     const usedShiftIds = [];
  189.     let iCoverage = 0;
  190.     let iShift    = 0;
  191.     while (iCoverage < finalIntervals.length && iShift < originalShifts.length) {
  192.       const cov = finalIntervals[iCoverage];
  193.       const shiftRec = originalShifts[iShift];
  194.       const newStart = minutesToTime(cov.startM);
  195.       const newEnd   = minutesToTime(cov.endM);
  196.  
  197.       await updateShift(shiftRec.id, shiftDate, newStart, newEnd, csrfToken);
  198.       usedShiftIds.push(shiftRec.id);
  199.  
  200.       iCoverage++;
  201.       iShift++;
  202.       await new Promise(r => setTimeout(r, 2000));
  203.     }
  204.     while (iCoverage < finalIntervals.length) {
  205.       const cov = finalIntervals[iCoverage];
  206.       const newStart = minutesToTime(cov.startM);
  207.       const newEnd   = minutesToTime(cov.endM);
  208.  
  209.       await createShift(driverId, shiftDate, newStart, newEnd, csrfToken);
  210.       iCoverage++;
  211.       await new Promise(r => setTimeout(r, 2000));
  212.     }
  213.  
  214.     while (iShift < originalShifts.length) {
  215.       const leftoverShift = originalShifts[iShift];
  216.       if (!usedShiftIds.includes(leftoverShift.id)) {
  217.         await softDeleteShift(leftoverShift.id, csrfToken);
  218.       }
  219.       iShift++;
  220.       await new Promise(r => setTimeout(r, 2000));
  221.     }
  222.   }
  223.  
  224.   async function processAppleReview() {
  225.     const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
  226.     if (!csrfToken) {
  227.       console.error("No CSRF token found. Aborting.");
  228.       return;
  229.     }
  230.  
  231.     const driverRows = document.querySelectorAll('[data-shifts-target="rosterRow"]');
  232.     const appleRow = Array.from(driverRows).find(row => {
  233.       const nameEl = row.querySelector('a[href*="/admin/drivers/"]');
  234.       return nameEl && /Apple Review/i.test(nameEl.innerText);
  235.     });
  236.     if (!appleRow) {
  237.       console.warn("Could not find Apple Review row.");
  238.       return;
  239.     }
  240.  
  241.     const linkEl = appleRow.querySelector('a[href*="/admin/drivers/"]');
  242.     const idMatch = linkEl.href.match(/\/drivers\/(\d+)\//);
  243.     const driverId = idMatch ? idMatch[1] : null;
  244.     if (!driverId) {
  245.       console.warn("No driver ID found for Apple Review!");
  246.       return;
  247.     }
  248.     console.log(`Processing Apple Review (driver #${driverId})`);
  249.  
  250.     const dayContainers = appleRow.querySelectorAll('.shifts');
  251.  
  252.     for (const dayDiv of dayContainers) {
  253.       const addShiftLink = dayDiv.querySelector('a.add-shift');
  254.       if (!addShiftLink) {
  255.         console.log("Skipping day with no add-shift link");
  256.         continue;
  257.       }
  258.       const href = addShiftLink.getAttribute("href") || "";
  259.       const dateMatch = href.match(/date%5D=(\d{4}-\d{2}-\d{2})/);
  260.       if (!dateMatch) {
  261.         console.log("Could not parse date from .add-shift link:", href);
  262.         continue;
  263.       }
  264.       const shiftDate = dateMatch[1];
  265.  
  266.       const confirmedEls = dayDiv.querySelectorAll('.existing-shift.confirmed-driver-shift');
  267.       if (!confirmedEls.length) {
  268.         console.log(`No confirmed shifts on ${shiftDate}. Skipping...`);
  269.         continue;
  270.       }
  271.  
  272.       console.log(`\n=== Processing date ${shiftDate} with ${confirmedEls.length} shifts ===`);
  273.  
  274.       const dayShifts = Array.from(confirmedEls).map(el => {
  275.         const aEl = el.querySelector('a[href*="/admin/driver_shifts/"]');
  276.         const timeText = aEl.innerText.trim();
  277.         const [sT, eT] = timeText.split("-").map(x => x.trim());
  278.         const shiftIdMatch = aEl.href.match(/\/driver_shifts\/(\d+)\//);
  279.         const shiftId = shiftIdMatch ? shiftIdMatch[1] : null;
  280.  
  281.         const [startM, endM] = parseShiftRange(sT, eT);
  282.  
  283.         return {
  284.           id: shiftId,
  285.           startTime: sT,
  286.           endTime: eT,
  287.           startM,
  288.           endM
  289.         };
  290.       });
  291.  
  292.       const coverageMap = buildCoverageMap(dayShifts);
  293.  
  294.       await applyBlockCoverage(coverageMap, dayShifts, driverId, shiftDate, csrfToken);
  295.     }
  296.  
  297.     console.log("All days processed for Apple Review. Done!");
  298.   }
  299.  
  300.   await processAppleReview();
  301.  
  302. })();
  303.  
Advertisement
Add Comment
Please, Sign In to add comment