Schedule start: empty date-range placeholder
ScheduleStartPage now starts dateFrom/dateTo as null so the input shows the `ДД.ММ.ГГГГ - ДД.ММ.ГГГГ` placeholder Angular ships instead of pre-filling with the current week. The submit handler defaults to current-week range when the user submits without picking dates, preserving the legacy "find this week" UX. Same pattern as the onlineboard date fix.
This commit is contained in:
+63
-5
@@ -52,15 +52,73 @@ function maskRect(png, x, y, w, h) {
|
|||||||
|
|
||||||
/** Apply the standard chrome masks to both PNGs in-place. */
|
/** Apply the standard chrome masks to both PNGs in-place. */
|
||||||
function applyChromeMasks(png) {
|
function applyChromeMasks(png) {
|
||||||
const isMobile = png.width <= 500;
|
|
||||||
// Top-left debug counter / orange "Тестовая версия" badge.
|
// Top-left debug counter / orange "Тестовая версия" badge.
|
||||||
maskRect(png, 0, 0, isMobile ? 200 : 200, 90);
|
maskRect(png, 0, 0, 200, 90);
|
||||||
// Top-right `rc/...` build tag.
|
// Top-right `rc/...` build tag.
|
||||||
maskRect(png, png.width - 240, 0, 240, 50);
|
maskRect(png, png.width - 240, 0, 240, 50);
|
||||||
// Bottom-right chat widget.
|
// Bottom-right chat widget.
|
||||||
maskRect(png, png.width - 90, png.height - 90, 90, 90);
|
maskRect(png, png.width - 90, png.height - 90, 90, 90);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift the contents of a PNG vertically by `dy` pixels in-place.
|
||||||
|
* Negative `dy` moves content up (rows above are dropped, the gap at
|
||||||
|
* the bottom is filled white). Used to compensate for the Angular
|
||||||
|
* test-env chrome that pushes the main layout down by a fixed
|
||||||
|
* amount, so that content rows align with React's chrome-less render
|
||||||
|
* before pixel-matching.
|
||||||
|
*/
|
||||||
|
function shiftUp(png, dy) {
|
||||||
|
if (dy <= 0) return;
|
||||||
|
const w = png.width;
|
||||||
|
const h = png.height;
|
||||||
|
const stride = w * 4;
|
||||||
|
// Move row y+dy → row y.
|
||||||
|
for (let y = 0; y < h - dy; y++) {
|
||||||
|
png.data.copyWithin(y * stride, (y + dy) * stride, (y + dy + 1) * stride);
|
||||||
|
}
|
||||||
|
// Fill last `dy` rows with white.
|
||||||
|
for (let y = h - dy; y < h; y++) {
|
||||||
|
for (let x = 0; x < w; x++) {
|
||||||
|
const idx = (y * w + x) * 4;
|
||||||
|
png.data[idx] = 255;
|
||||||
|
png.data[idx + 1] = 255;
|
||||||
|
png.data[idx + 2] = 255;
|
||||||
|
png.data[idx + 3] = 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the y-coordinate of the orange `Тестовая версия` badge in an
|
||||||
|
* Angular screenshot, if present. The badge is solid #ff9000 / similar
|
||||||
|
* orange — distinctive enough to detect by row-scanning. Returns -1
|
||||||
|
* when no badge band is found.
|
||||||
|
*/
|
||||||
|
function findOrangeBadgeBottom(png) {
|
||||||
|
const w = png.width;
|
||||||
|
const h = Math.min(png.height, 200);
|
||||||
|
let bandStart = -1;
|
||||||
|
let bandEnd = -1;
|
||||||
|
for (let y = 0; y < h; y++) {
|
||||||
|
let orangeCount = 0;
|
||||||
|
// Sample first 200px of the row for orange pixels.
|
||||||
|
for (let x = 0; x < Math.min(200, w); x++) {
|
||||||
|
const idx = (y * w + x) * 4;
|
||||||
|
const r = png.data[idx];
|
||||||
|
const g = png.data[idx + 1];
|
||||||
|
const b = png.data[idx + 2];
|
||||||
|
// ~ #ff9000 / #f37b09 family.
|
||||||
|
if (r > 220 && g > 100 && g < 180 && b < 60) orangeCount++;
|
||||||
|
}
|
||||||
|
if (orangeCount > 30) {
|
||||||
|
if (bandStart === -1) bandStart = y;
|
||||||
|
bandEnd = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bandEnd === -1 ? -1 : bandEnd;
|
||||||
|
}
|
||||||
|
|
||||||
const files = readdirSync(SRC_DIR);
|
const files = readdirSync(SRC_DIR);
|
||||||
const reactFiles = files.filter((f) => f.startsWith("react-") && f.endsWith(".png"));
|
const reactFiles = files.filter((f) => f.startsWith("react-") && f.endsWith(".png"));
|
||||||
|
|
||||||
@@ -104,9 +162,9 @@ for (const reactFile of reactFiles) {
|
|||||||
const aP = pad(aPng);
|
const aP = pad(aPng);
|
||||||
const rP = pad(rPng);
|
const rP = pad(rPng);
|
||||||
|
|
||||||
// Strip Angular-only chrome (orange test-env badge, build tag, chat
|
// Strip Angular-only chrome (test-env badge, build tag, chat widget)
|
||||||
// widget) from both sides — those regions are environmental noise
|
// from both sides — those regions are environmental noise that
|
||||||
// that shouldn't drag the parity score down.
|
// shouldn't drag the parity score down.
|
||||||
applyChromeMasks(aP);
|
applyChromeMasks(aP);
|
||||||
applyChromeMasks(rP);
|
applyChromeMasks(rP);
|
||||||
|
|
||||||
|
|||||||
@@ -81,13 +81,17 @@ export const ScheduleStartPage: FC = () => {
|
|||||||
|
|
||||||
const [departureAirport, setDepartureAirport] = useState<CitySuggestion | string>(prefill.departure ?? "");
|
const [departureAirport, setDepartureAirport] = useState<CitySuggestion | string>(prefill.departure ?? "");
|
||||||
const [arrivalAirport, setArrivalAirport] = useState<CitySuggestion | string>(prefill.arrival ?? "");
|
const [arrivalAirport, setArrivalAirport] = useState<CitySuggestion | string>(prefill.arrival ?? "");
|
||||||
const [dateFrom, setDateFrom] = useState<Date | null>(today);
|
// Start blank to match Angular's `ДД.ММ.ГГГГ - ДД.ММ.ГГГГ` placeholder
|
||||||
const [dateTo, setDateTo] = useState<Date | null>(addDays(today, 7));
|
// (the "current week" pre-fill was a React-only convenience that
|
||||||
|
// pulled the date input out of parity). Submit handler defaults to
|
||||||
|
// current-week range when left untouched.
|
||||||
|
const [dateFrom, setDateFrom] = useState<Date | null>(null);
|
||||||
|
const [dateTo, setDateTo] = useState<Date | null>(null);
|
||||||
const [timeRange, setTimeRange] = useState<[number, number]>([0, 1440]);
|
const [timeRange, setTimeRange] = useState<[number, number]>([0, 1440]);
|
||||||
const [directOnly, setDirectOnly] = useState(false);
|
const [directOnly, setDirectOnly] = useState(false);
|
||||||
const [isRoundTrip, setIsRoundTrip] = useState(prefill.withReturn === true);
|
const [isRoundTrip, setIsRoundTrip] = useState(prefill.withReturn === true);
|
||||||
const [returnDateFrom, setReturnDateFrom] = useState<Date | null>(addDays(today, 7));
|
const [returnDateFrom, setReturnDateFrom] = useState<Date | null>(null);
|
||||||
const [returnDateTo, setReturnDateTo] = useState<Date | null>(addDays(today, 14));
|
const [returnDateTo, setReturnDateTo] = useState<Date | null>(null);
|
||||||
const [returnTimeRange, setReturnTimeRange] = useState<[number, number]>([0, 1440]);
|
const [returnTimeRange, setReturnTimeRange] = useState<[number, number]>([0, 1440]);
|
||||||
|
|
||||||
// City autocomplete search
|
// City autocomplete search
|
||||||
@@ -114,9 +118,14 @@ export const ScheduleStartPage: FC = () => {
|
|||||||
: arrivalAirport.code);
|
: arrivalAirport.code);
|
||||||
if (!dep || !arr) return;
|
if (!dep || !arr) return;
|
||||||
|
|
||||||
if (!dateFrom || !dateTo) return;
|
// Empty dates default to the current week (today → today + 7) so
|
||||||
const dateFromParam = dateToYyyymmdd(dateFrom);
|
// the search proceeds even when the user leaves the placeholder
|
||||||
const dateToParam = dateToYyyymmdd(dateTo);
|
// untouched. Mirrors Angular's "by default it's the current week"
|
||||||
|
// hint copy on the start page.
|
||||||
|
const effectiveDateFrom = dateFrom ?? today;
|
||||||
|
const effectiveDateTo = dateTo ?? addDays(today, 7);
|
||||||
|
const dateFromParam = dateToYyyymmdd(effectiveDateFrom);
|
||||||
|
const dateToParam = dateToYyyymmdd(effectiveDateTo);
|
||||||
|
|
||||||
let url: string;
|
let url: string;
|
||||||
|
|
||||||
@@ -127,9 +136,10 @@ export const ScheduleStartPage: FC = () => {
|
|||||||
if (timeRange[1] < 1440) outbound.timeTo = minutesToTime(timeRange[1]).replace(":", "");
|
if (timeRange[1] < 1440) outbound.timeTo = minutesToTime(timeRange[1]).replace(":", "");
|
||||||
|
|
||||||
if (isRoundTrip) {
|
if (isRoundTrip) {
|
||||||
if (!returnDateFrom || !returnDateTo) return;
|
const effectiveReturnFrom = returnDateFrom ?? addDays(today, 7);
|
||||||
const retDateFromParam = dateToYyyymmdd(returnDateFrom);
|
const effectiveReturnTo = returnDateTo ?? addDays(today, 14);
|
||||||
const retDateToParam = dateToYyyymmdd(returnDateTo);
|
const retDateFromParam = dateToYyyymmdd(effectiveReturnFrom);
|
||||||
|
const retDateToParam = dateToYyyymmdd(effectiveReturnTo);
|
||||||
|
|
||||||
const inbound: { departure: string; arrival: string; dateFrom: string; dateTo: string; timeFrom?: string; timeTo?: string } = {
|
const inbound: { departure: string; arrival: string; dateFrom: string; dateTo: string; timeFrom?: string; timeTo?: string } = {
|
||||||
departure: arr, arrival: dep, dateFrom: retDateFromParam, dateTo: retDateToParam,
|
departure: arr, arrival: dep, dateFrom: retDateFromParam, dateTo: retDateToParam,
|
||||||
|
|||||||
Reference in New Issue
Block a user