Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (async function() {
- const BLOCKS = [
- { blockName: 'MORNING', start: '06:00', end: '08:00' },
- { blockName: 'DAY', start: '08:00', end: '22:00' },
- { blockName: 'NIGHT', start: '22:00', end: '00:30' },
- ];
- const DEFAULT_SHIFT_STATUS = "CONFIRMED";
- async function softDeleteShift(shiftId, csrfToken) {
- console.log(`(API) Soft-deleting shift #${shiftId}`);
- const resp = await fetch(`/admin/driver_shifts/${shiftId}/soft_delete`, {
- method: 'POST',
- headers: {
- "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
- "X-CSRF-Token": csrfToken,
- },
- body: new URLSearchParams({
- authenticity_token: csrfToken
- }),
- });
- if (resp.ok) {
- console.log(`Shift #${shiftId} soft-deleted successfully`);
- return true;
- } else {
- const text = await resp.text();
- console.error(`Failed to soft-delete shift #${shiftId}: ${resp.status}`, text);
- return false;
- }
- }
- function timeToMinutes(tStr) {
- const [HH, MM] = tStr.split(':').map(Number);
- return HH * 60 + MM;
- }
- function minutesToTime(m) {
- let dayOffset = 0;
- if (m >= 1440) dayOffset = 1440;
- const adj = m - dayOffset;
- const hh = Math.floor(adj / 60);
- const mm = adj % 60;
- return String(hh).padStart(2, '0') + ':' + String(mm).padStart(2, '0');
- }
- function parseShiftRange(startT, endT) {
- let s = timeToMinutes(startT);
- let e = timeToMinutes(endT);
- if (e < s) e += 1440;
- return [s, e];
- }
- async function updateShift(shiftId, dateStr, startTime, endTime, csrfToken) {
- console.log(`(API) Update shift #${shiftId} => ${startTime}β${endTime} on ${dateStr}`);
- let startDT = `${dateStr}T${startTime}:00`;
- let endDT = `${dateStr}T${endTime}:00`;
- // crossing midnight?
- if (timeToMinutes(endTime) < timeToMinutes(startTime)) {
- const d = new Date(dateStr);
- d.setDate(d.getDate() + 1);
- const nextDayStr = d.toISOString().split('T')[0];
- endDT = `${nextDayStr}T${endTime}:00`;
- }
- const resp = await fetch(`/admin/driver_shifts/${shiftId}`, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
- "X-CSRF-Token": csrfToken
- },
- body: new URLSearchParams({
- '_method': 'patch',
- 'driver_shift[active_period_start_date_time]': startDT,
- 'driver_shift[active_period_end_date_time]': endDT,
- 'authenticity_token': csrfToken,
- })
- });
- if (resp.ok) {
- console.log(`Shift #${shiftId} updated => ${startTime}β${endTime}`);
- return true;
- } else {
- const txt = await resp.text();
- console.error(`Failed to update shift #${shiftId}: ${resp.status}`, txt);
- return false;
- }
- }
- async function createShift(driverId, dateStr, startTime, endTime, csrfToken) {
- console.log(`(API) Create NEW shift for driver #${driverId}: ${startTime}β${endTime} (date=${dateStr})`);
- let startDT = `${dateStr}T${startTime}:00`;
- let endDT = `${dateStr}T${endTime}:00`;
- if (timeToMinutes(endTime) < timeToMinutes(startTime)) {
- const d = new Date(dateStr);
- d.setDate(d.getDate() + 1);
- const nextDayStr = d.toISOString().split('T')[0];
- endDT = `${nextDayStr}T${endTime}:00`;
- }
- const resp = await fetch(`/admin/driver_shifts`, {
- method: "POST",
- headers: {
- "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
- "X-CSRF-Token": csrfToken
- },
- body: new URLSearchParams({
- 'driver_shift[date]': dateStr,
- 'driver_shift[driver_id]': driverId,
- 'driver_shift[active_period_start_date_time]': startDT,
- 'driver_shift[active_period_end_date_time]': endDT,
- 'driver_shift[status]': DEFAULT_SHIFT_STATUS,
- 'authenticity_token': csrfToken,
- })
- });
- if (resp.ok) {
- console.log("New shift created successfully!");
- return true;
- } else {
- const txt = await resp.text();
- console.error(`Failed to create new shift: ${resp.status}`, txt);
- return false;
- }
- }
- function buildCoverageMap(shifts) {
- shifts.sort((a,b) => a.startM - b.startM);
- const coverageByBlock = {
- MORNING: [],
- DAY: [],
- NIGHT: []
- };
- for (const s of shifts) {
- for (const block of BLOCKS) {
- const [blockStart, blockEnd] = parseShiftRange(block.start, block.end);
- const overlapStart = Math.max(s.startM, blockStart);
- const overlapEnd = Math.min(s.endM, blockEnd);
- if (overlapEnd > overlapStart) {
- coverageByBlock[block.blockName].push([overlapStart, overlapEnd]);
- }
- }
- }
- for (const bn of Object.keys(coverageByBlock)) {
- coverageByBlock[bn].sort((a,b) => a[0]-b[0]);
- const merged = [];
- let current = null;
- for (const interval of coverageByBlock[bn]) {
- if (!current) {
- current = interval;
- continue;
- }
- if (interval[0] <= current[1]) {
- current[1] = Math.max(current[1], interval[1]);
- } else {
- merged.push(current);
- current = interval;
- }
- }
- if (current) merged.push(current);
- coverageByBlock[bn] = merged;
- }
- return coverageByBlock;
- }
- async function applyBlockCoverage(coverageMap, originalShifts, driverId, shiftDate, csrfToken) {
- const finalIntervals = [];
- for (const block of BLOCKS) {
- const intervals = coverageMap[block.blockName];
- for (const iv of intervals) {
- finalIntervals.push({
- blockName: block.blockName,
- startM: iv[0],
- endM: iv[1]
- });
- }
- }
- finalIntervals.sort((a,b) => a.startM - b.startM);
- const usedShiftIds = [];
- let iCoverage = 0;
- let iShift = 0;
- while (iCoverage < finalIntervals.length && iShift < originalShifts.length) {
- const cov = finalIntervals[iCoverage];
- const shiftRec = originalShifts[iShift];
- const newStart = minutesToTime(cov.startM);
- const newEnd = minutesToTime(cov.endM);
- await updateShift(shiftRec.id, shiftDate, newStart, newEnd, csrfToken);
- usedShiftIds.push(shiftRec.id);
- iCoverage++;
- iShift++;
- await new Promise(r => setTimeout(r, 2000));
- }
- while (iCoverage < finalIntervals.length) {
- const cov = finalIntervals[iCoverage];
- const newStart = minutesToTime(cov.startM);
- const newEnd = minutesToTime(cov.endM);
- await createShift(driverId, shiftDate, newStart, newEnd, csrfToken);
- iCoverage++;
- await new Promise(r => setTimeout(r, 2000));
- }
- while (iShift < originalShifts.length) {
- const leftoverShift = originalShifts[iShift];
- if (!usedShiftIds.includes(leftoverShift.id)) {
- await softDeleteShift(leftoverShift.id, csrfToken);
- }
- iShift++;
- await new Promise(r => setTimeout(r, 2000));
- }
- }
- async function processAppleReview() {
- const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
- if (!csrfToken) {
- console.error("No CSRF token found. Aborting.");
- return;
- }
- const driverRows = document.querySelectorAll('[data-shifts-target="rosterRow"]');
- const appleRow = Array.from(driverRows).find(row => {
- const nameEl = row.querySelector('a[href*="/admin/drivers/"]');
- return nameEl && /Apple Review/i.test(nameEl.innerText);
- });
- if (!appleRow) {
- console.warn("Could not find Apple Review row.");
- return;
- }
- const linkEl = appleRow.querySelector('a[href*="/admin/drivers/"]');
- const idMatch = linkEl.href.match(/\/drivers\/(\d+)\//);
- const driverId = idMatch ? idMatch[1] : null;
- if (!driverId) {
- console.warn("No driver ID found for Apple Review!");
- return;
- }
- console.log(`Processing Apple Review (driver #${driverId})`);
- const dayContainers = appleRow.querySelectorAll('.shifts');
- for (const dayDiv of dayContainers) {
- const addShiftLink = dayDiv.querySelector('a.add-shift');
- if (!addShiftLink) {
- console.log("Skipping day with no add-shift link");
- continue;
- }
- const href = addShiftLink.getAttribute("href") || "";
- const dateMatch = href.match(/date%5D=(\d{4}-\d{2}-\d{2})/);
- if (!dateMatch) {
- console.log("Could not parse date from .add-shift link:", href);
- continue;
- }
- const shiftDate = dateMatch[1];
- const confirmedEls = dayDiv.querySelectorAll('.existing-shift.confirmed-driver-shift');
- if (!confirmedEls.length) {
- console.log(`No confirmed shifts on ${shiftDate}. Skipping...`);
- continue;
- }
- console.log(`\n=== Processing date ${shiftDate} with ${confirmedEls.length} shifts ===`);
- const dayShifts = Array.from(confirmedEls).map(el => {
- const aEl = el.querySelector('a[href*="/admin/driver_shifts/"]');
- const timeText = aEl.innerText.trim();
- const [sT, eT] = timeText.split("-").map(x => x.trim());
- const shiftIdMatch = aEl.href.match(/\/driver_shifts\/(\d+)\//);
- const shiftId = shiftIdMatch ? shiftIdMatch[1] : null;
- const [startM, endM] = parseShiftRange(sT, eT);
- return {
- id: shiftId,
- startTime: sT,
- endTime: eT,
- startM,
- endM
- };
- });
- const coverageMap = buildCoverageMap(dayShifts);
- await applyBlockCoverage(coverageMap, dayShifts, driverId, shiftDate, csrfToken);
- }
- console.log("All days processed for Apple Review. Done!");
- }
- await processAppleReview();
- })();
Advertisement
Add Comment
Please, Sign In to add comment