Deploy to staging #21
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -43,7 +43,7 @@ export const updateWebBid = async (bid: Partial<IWebBid>) => {
 | 
			
		|||
    origin_url,
 | 
			
		||||
    active,
 | 
			
		||||
    arrival_offset_seconds,
 | 
			
		||||
    // early_login_seconds
 | 
			
		||||
    early_tracking_seconds
 | 
			
		||||
  } = removeFalsyValues(bid, ["active"]);
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
| 
						 | 
				
			
			@ -58,7 +58,7 @@ export const updateWebBid = async (bid: Partial<IWebBid>) => {
 | 
			
		|||
        origin_url,
 | 
			
		||||
        active,
 | 
			
		||||
        arrival_offset_seconds,
 | 
			
		||||
        // early_login_seconds
 | 
			
		||||
        early_tracking_seconds
 | 
			
		||||
      },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { Badge, Box, Button, Image, Text } from "@mantine/core";
 | 
			
		||||
import { Badge, Box, Button, Image, Text, Tooltip } from "@mantine/core";
 | 
			
		||||
import { useDisclosure } from "@mantine/hooks";
 | 
			
		||||
import moment from "moment";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,7 @@ import { useStatusToolStore } from "../../lib/zustand/use-status-tool-store";
 | 
			
		|||
import { IBid, IWebBid } from "../../system/type";
 | 
			
		||||
import { cn, stringToColor } from "../../utils";
 | 
			
		||||
import ShowImageModal from "./show-image-modal";
 | 
			
		||||
import { isTimeReached, subtractSeconds } from "../../lib/table/ultils";
 | 
			
		||||
export interface IWorkingPageProps {
 | 
			
		||||
  data: (IBid | IWebBid) & { type: string };
 | 
			
		||||
  socket: Socket;
 | 
			
		||||
| 
						 | 
				
			
			@ -85,12 +86,6 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
  useEffect(() => {
 | 
			
		||||
    const onLoginStatus = (data: { data: IWebBid; login_status: boolean }) => {
 | 
			
		||||
      setPayloadLoginStatus(data);
 | 
			
		||||
 | 
			
		||||
      console.log(
 | 
			
		||||
        "%csrc/components/dashboard/working-page.tsx:60 data",
 | 
			
		||||
        "color: #007acc;",
 | 
			
		||||
        data
 | 
			
		||||
      );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const origin_url = isIBid(data) ? data.web_bid.origin_url : data.origin_url;
 | 
			
		||||
| 
						 | 
				
			
			@ -144,9 +139,43 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
          {isIBid(data) && (
 | 
			
		||||
            <Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          <Text className="text-sm italic opacity-80">
 | 
			
		||||
            {moment(lastUpdate).format("HH:mm:ss DD/MM/YYYY")}
 | 
			
		||||
          </Text>
 | 
			
		||||
 | 
			
		||||
          <Box className="flex items-center gap-3">
 | 
			
		||||
            {isIBid(data) && (
 | 
			
		||||
              <Tooltip label={'Close time'}>
 | 
			
		||||
                <Text
 | 
			
		||||
                  style={{ fontSize: "12px" }}
 | 
			
		||||
                  className="tracking-wide"
 | 
			
		||||
                >{`CLT: ${moment(data.close_time).format(
 | 
			
		||||
                  "HH:mm:ss DD/MM/YYYY"
 | 
			
		||||
                )}`}</Text>
 | 
			
		||||
              </Tooltip>
 | 
			
		||||
            )}
 | 
			
		||||
            {isIBid(data) &&
 | 
			
		||||
              data.close_time &&
 | 
			
		||||
              !isTimeReached(
 | 
			
		||||
                subtractSeconds(
 | 
			
		||||
                  data.close_time,
 | 
			
		||||
                  data.web_bid?.early_tracking_seconds || 0
 | 
			
		||||
                )
 | 
			
		||||
              ) && (
 | 
			
		||||
                <Tooltip label={'Time to tracking'}>
 | 
			
		||||
                  <Text
 | 
			
		||||
                    style={{ fontSize: "12px" }}
 | 
			
		||||
                    className="tracking-wide"
 | 
			
		||||
                  >{`TT: ${moment(
 | 
			
		||||
                    subtractSeconds(
 | 
			
		||||
                      data.close_time,
 | 
			
		||||
                      data.web_bid?.early_tracking_seconds || 0
 | 
			
		||||
                    )
 | 
			
		||||
                  ).format("HH:mm:ss DD/MM/YYYY")}`}</Text>
 | 
			
		||||
                </Tooltip>
 | 
			
		||||
              )}
 | 
			
		||||
          </Box>
 | 
			
		||||
          <Box className="flex items-center gap-4">
 | 
			
		||||
            <Button
 | 
			
		||||
              size="xs"
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +206,9 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
          </Badge>
 | 
			
		||||
 | 
			
		||||
          <Badge
 | 
			
		||||
            color={stringToColor(isIBid(data) ? data.web_bid.origin_url : data.origin_url)}
 | 
			
		||||
            color={stringToColor(
 | 
			
		||||
              isIBid(data) ? data.web_bid.origin_url : data.origin_url
 | 
			
		||||
            )}
 | 
			
		||||
            size="xs"
 | 
			
		||||
          >
 | 
			
		||||
            {isIBid(data) ? data.web_bid.origin_url : data.origin_url}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,7 +27,7 @@ const schema = {
 | 
			
		|||
    .refine((val) => val >= 60, {
 | 
			
		||||
      message: "Arrival offset seconds must be at least 60 seconds (1 minute)",
 | 
			
		||||
    }),
 | 
			
		||||
    early_login_seconds: z
 | 
			
		||||
    early_tracking_seconds: z
 | 
			
		||||
    .number({ message: "Early login seconds is required" })
 | 
			
		||||
    .refine((val) => val >= 600, {
 | 
			
		||||
      message: "Early login seconds must be at least 600 seconds (10 minute)",
 | 
			
		||||
| 
						 | 
				
			
			@ -78,14 +78,14 @@ export default function WebBidModal({
 | 
			
		|||
        },
 | 
			
		||||
      });
 | 
			
		||||
    } else {
 | 
			
		||||
      const { url, origin_url, arrival_offset_seconds, early_login_seconds } = values;
 | 
			
		||||
      const { url, origin_url, arrival_offset_seconds, early_tracking_seconds } = values;
 | 
			
		||||
 | 
			
		||||
      setLoading(true);
 | 
			
		||||
      const result = await createWebBid({
 | 
			
		||||
        url,
 | 
			
		||||
        origin_url,
 | 
			
		||||
        arrival_offset_seconds,
 | 
			
		||||
        early_login_seconds
 | 
			
		||||
        early_tracking_seconds
 | 
			
		||||
      } as IWebBid);
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -163,16 +163,16 @@ export default function WebBidModal({
 | 
			
		|||
          placeholder="msg: 300"
 | 
			
		||||
          {...form.getInputProps("arrival_offset_seconds")}
 | 
			
		||||
        />
 | 
			
		||||
        {/* <NumberInput
 | 
			
		||||
        <NumberInput
 | 
			
		||||
          description="Note: that only integer minutes are accepted."
 | 
			
		||||
          className="col-span-2"
 | 
			
		||||
          size="sm"
 | 
			
		||||
          label={`Early login seconds (${
 | 
			
		||||
            form.getValues()["early_login_seconds"] / 60
 | 
			
		||||
          label={`Early tracking seconds (${
 | 
			
		||||
            form.getValues()["early_tracking_seconds"] / 60
 | 
			
		||||
          } minutes)`}
 | 
			
		||||
          placeholder="msg: 600"
 | 
			
		||||
          {...form.getInputProps("early_login_seconds")}
 | 
			
		||||
        /> */}
 | 
			
		||||
          {...form.getInputProps("early_tracking_seconds")}
 | 
			
		||||
        />
 | 
			
		||||
 | 
			
		||||
        <Button
 | 
			
		||||
          disabled={_.isEqual(form.getValues(), prevData.current)}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,84 +1,144 @@
 | 
			
		|||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
			
		||||
import { IColumn, IDataFilter, ITableFilter, ITableShort, TShort } from '../type';
 | 
			
		||||
import {
 | 
			
		||||
  IColumn,
 | 
			
		||||
  IDataFilter,
 | 
			
		||||
  ITableFilter,
 | 
			
		||||
  ITableShort,
 | 
			
		||||
  TShort,
 | 
			
		||||
} from "../type";
 | 
			
		||||
 | 
			
		||||
export const defaultPrefixShort = 'order_by_';
 | 
			
		||||
export const defaultPathToData = 'data';
 | 
			
		||||
export const flowShort: TShort[] = ['desc', 'asc'];
 | 
			
		||||
export const defaultKeyPerpage = 'per_page';
 | 
			
		||||
export const defaultKeyPage = 'page';
 | 
			
		||||
export const searchKey = 'search_key';
 | 
			
		||||
export const defaultPrefixShort = "order_by_";
 | 
			
		||||
export const defaultPathToData = "data";
 | 
			
		||||
export const flowShort: TShort[] = ["desc", "asc"];
 | 
			
		||||
export const defaultKeyPerpage = "per_page";
 | 
			
		||||
export const defaultKeyPage = "page";
 | 
			
		||||
export const searchKey = "search_key";
 | 
			
		||||
 | 
			
		||||
export const defaultStyleHightlight = { color: 'red', backgroundColor: 'yellow' } as React.CSSProperties;
 | 
			
		||||
export const defaultStyleHightlight = {
 | 
			
		||||
  color: "red",
 | 
			
		||||
  backgroundColor: "yellow",
 | 
			
		||||
} as React.CSSProperties;
 | 
			
		||||
 | 
			
		||||
export const defaultPerpageValue = '10';
 | 
			
		||||
export const defaultPerpageValue = "10";
 | 
			
		||||
 | 
			
		||||
export const getParamsData = <R extends Record<string, string | number>>(options: { prefixShort?: string; columns: IColumn<R>[] }) => {
 | 
			
		||||
    const paramsUrl = new URLSearchParams(window.location.search);
 | 
			
		||||
export const getParamsData = <
 | 
			
		||||
  R extends Record<string, string | number>
 | 
			
		||||
>(options: {
 | 
			
		||||
  prefixShort?: string;
 | 
			
		||||
  columns: IColumn<R>[];
 | 
			
		||||
}) => {
 | 
			
		||||
  const paramsUrl = new URLSearchParams(window.location.search);
 | 
			
		||||
 | 
			
		||||
    if (!paramsUrl.size) return {};
 | 
			
		||||
  if (!paramsUrl.size) return {};
 | 
			
		||||
 | 
			
		||||
    const prefixShort = options?.prefixShort || defaultPrefixShort;
 | 
			
		||||
  const prefixShort = options?.prefixShort || defaultPrefixShort;
 | 
			
		||||
 | 
			
		||||
    const paramObject: { [key: string]: string | number } = {};
 | 
			
		||||
    paramsUrl.forEach((value, key) => {
 | 
			
		||||
        paramObject[key] = value;
 | 
			
		||||
  const paramObject: { [key: string]: string | number } = {};
 | 
			
		||||
  paramsUrl.forEach((value, key) => {
 | 
			
		||||
    paramObject[key] = value;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const pramsKeys = Object.keys(paramObject);
 | 
			
		||||
 | 
			
		||||
  if (pramsKeys.length <= 0)
 | 
			
		||||
    return {
 | 
			
		||||
      shortParamsData: {},
 | 
			
		||||
      searchParamsData: {},
 | 
			
		||||
      filterParamsData: [],
 | 
			
		||||
      params: {},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
  const shortParamsData = pramsKeys
 | 
			
		||||
    .filter(
 | 
			
		||||
      (item) =>
 | 
			
		||||
        item.includes(prefixShort) &&
 | 
			
		||||
        options.columns
 | 
			
		||||
          .map((col) => col.key)
 | 
			
		||||
          .includes(item.replace(prefixShort, "") as IColumn<R>["key"])
 | 
			
		||||
    )
 | 
			
		||||
    .map((i) => {
 | 
			
		||||
      return {
 | 
			
		||||
        key: i.replace(prefixShort, ""),
 | 
			
		||||
        type: flowShort.includes(paramObject[i] as TShort)
 | 
			
		||||
          ? paramObject[i]
 | 
			
		||||
          : "asc",
 | 
			
		||||
      } as ITableShort<R>;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const pramsKeys = Object.keys(paramObject);
 | 
			
		||||
  const filterParamsData = pramsKeys
 | 
			
		||||
    // .filter((item) => options.columns.map((col) => col.key).includes(item as IColumn<R>['key']))
 | 
			
		||||
    .map((item) => ({ key: item, type: paramObject[item] } as ITableFilter<R>));
 | 
			
		||||
 | 
			
		||||
    if (pramsKeys.length <= 0)
 | 
			
		||||
        return {
 | 
			
		||||
            shortParamsData: {},
 | 
			
		||||
            searchParamsData: {},
 | 
			
		||||
            filterParamsData: [],
 | 
			
		||||
            params: {},
 | 
			
		||||
        };
 | 
			
		||||
  const shortObject = shortParamsData[0]
 | 
			
		||||
    ? {
 | 
			
		||||
        [`${prefixShort}${shortParamsData[0].key}`]: String(
 | 
			
		||||
          shortParamsData[0].type
 | 
			
		||||
        ),
 | 
			
		||||
      }
 | 
			
		||||
    : {};
 | 
			
		||||
 | 
			
		||||
    const shortParamsData = pramsKeys
 | 
			
		||||
        .filter((item) => item.includes(prefixShort) && options.columns.map((col) => col.key).includes(item.replace(prefixShort, '') as IColumn<R>['key']))
 | 
			
		||||
        .map((i) => {
 | 
			
		||||
            return { key: i.replace(prefixShort, ''), type: flowShort.includes(paramObject[i] as TShort) ? paramObject[i] : 'asc' } as ITableShort<R>;
 | 
			
		||||
        });
 | 
			
		||||
  const pramsData: IDataFilter[] = [
 | 
			
		||||
    ...(filterParamsData ? (filterParamsData as IDataFilter[]) : []),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
    const filterParamsData = pramsKeys
 | 
			
		||||
        // .filter((item) => options.columns.map((col) => col.key).includes(item as IColumn<R>['key']))
 | 
			
		||||
        .map((item) => ({ key: item, type: paramObject[item] } as ITableFilter<R>));
 | 
			
		||||
  let params = convertToParams(
 | 
			
		||||
    pramsData as unknown as Record<string, string | number>[]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    const shortObject = shortParamsData[0] ? { [`${prefixShort}${shortParamsData[0].key}`]: String(shortParamsData[0].type) } : {};
 | 
			
		||||
 | 
			
		||||
    const pramsData: IDataFilter[] = [...(filterParamsData ? (filterParamsData as IDataFilter[]) : [])];
 | 
			
		||||
 | 
			
		||||
    let params = convertToParams(pramsData as unknown as Record<string, string | number>[]);
 | 
			
		||||
 | 
			
		||||
    if (shortParamsData.length) {
 | 
			
		||||
        params = {
 | 
			
		||||
            ...params,
 | 
			
		||||
            ...shortObject,
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    return { shortParamsData: shortParamsData[0] || {}, filterParamsData, params };
 | 
			
		||||
  if (shortParamsData.length) {
 | 
			
		||||
    params = {
 | 
			
		||||
      ...params,
 | 
			
		||||
      ...shortObject,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  return {
 | 
			
		||||
    shortParamsData: shortParamsData[0] || {},
 | 
			
		||||
    filterParamsData,
 | 
			
		||||
    params,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const convertToParams = (filter: Record<string, string | number>[]) => {
 | 
			
		||||
    const params = filter.reduce((prev, cur) => {
 | 
			
		||||
        prev[cur.key] = cur.type;
 | 
			
		||||
  const params = filter.reduce((prev, cur) => {
 | 
			
		||||
    prev[cur.key] = cur.type;
 | 
			
		||||
 | 
			
		||||
        return prev;
 | 
			
		||||
    }, {} as Record<string, string | number>);
 | 
			
		||||
    return prev;
 | 
			
		||||
  }, {} as Record<string, string | number>);
 | 
			
		||||
 | 
			
		||||
    return params;
 | 
			
		||||
  return params;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeFalsy = (data: { [key: string]: string | number }) => {
 | 
			
		||||
    return Object.keys(data).reduce((prev, cur) => {
 | 
			
		||||
        if (data[cur]) {
 | 
			
		||||
            prev[cur] = data[cur];
 | 
			
		||||
        }
 | 
			
		||||
        return prev;
 | 
			
		||||
    }, {} as { [key: string]: string | number });
 | 
			
		||||
  return Object.keys(data).reduce((prev, cur) => {
 | 
			
		||||
    if (data[cur]) {
 | 
			
		||||
      prev[cur] = data[cur];
 | 
			
		||||
    }
 | 
			
		||||
    return prev;
 | 
			
		||||
  }, {} as { [key: string]: string | number });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function extractNumber(str: string) {
 | 
			
		||||
    const match = str.match(/\d+(\.\d+)?/);
 | 
			
		||||
    return match ? parseFloat(match[0]) : null;
 | 
			
		||||
  const match = str.match(/\d+(\.\d+)?/);
 | 
			
		||||
  return match ? parseFloat(match[0]) : null;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function subtractMinutes(time: string, minutes: number) {
 | 
			
		||||
  const date = new Date(time);
 | 
			
		||||
  date.setMinutes(date.getMinutes() - minutes);
 | 
			
		||||
  return date.toUTCString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function subtractSeconds(time: string, seconds: number) {
 | 
			
		||||
  const date = new Date(time);
 | 
			
		||||
  date.setSeconds(date.getSeconds() - seconds);
 | 
			
		||||
  return date.toUTCString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isTimeReached(targetTime: string) {
 | 
			
		||||
  if (!targetTime) return false;
 | 
			
		||||
 | 
			
		||||
  const targetDate = new Date(targetTime);
 | 
			
		||||
  const now = new Date();
 | 
			
		||||
 | 
			
		||||
  return now >= targetDate;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -167,7 +167,7 @@ export default function DashBoard() {
 | 
			
		|||
          </Box>
 | 
			
		||||
        </Tooltip>
 | 
			
		||||
      </Box>
 | 
			
		||||
      <Box className="grid grid-cols-4 gap-4 mt-5">
 | 
			
		||||
      <Box className="grid lg:grid-cols-3 2xl:grid-cols-4 gap-4 mt-5">
 | 
			
		||||
        {workingData.length > 0 &&
 | 
			
		||||
          workingData.map((item, index) => (
 | 
			
		||||
            <WorkingPage socket={socket} data={item} key={item.id + index} />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ export interface IWebBid extends ITimestamp {
 | 
			
		|||
    password: string | null;
 | 
			
		||||
    active: boolean;
 | 
			
		||||
    arrival_offset_seconds: number;
 | 
			
		||||
    early_login_seconds: number;
 | 
			
		||||
    early_tracking_seconds: number;
 | 
			
		||||
    children: IBid[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -172,7 +172,7 @@ export function findEarlyLoginTime(webBid: IWebBid): string | null {
 | 
			
		|||
 | 
			
		||||
  // Bước 3: Tính toán thời gian login sớm
 | 
			
		||||
  const closeTime = new Date(closestBid.close_time);
 | 
			
		||||
  closeTime.setSeconds(closeTime.getSeconds() - (webBid.early_login_seconds || 0));
 | 
			
		||||
  closeTime.setSeconds(closeTime.getSeconds() - (webBid.early_tracking_seconds || 0));
 | 
			
		||||
 | 
			
		||||
  return closeTime.toISOString();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1 @@
 | 
			
		|||
<<<<<<< HEAD
 | 
			
		||||
{"createdAt":1745827424853}
 | 
			
		||||
=======
 | 
			
		||||
{"createdAt":1746413672600}
 | 
			
		||||
>>>>>>> 26b10a7 (pickxel and fix login)
 | 
			
		||||
{"createdAt":1746603511532}
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ export class UpdateWebBidDto {
 | 
			
		|||
  @IsNumber()
 | 
			
		||||
  @Min(600)
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  early_login_seconds: number;
 | 
			
		||||
  early_tracking_seconds: number;
 | 
			
		||||
 | 
			
		||||
  @IsString()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ export class WebBid extends Timestamp {
 | 
			
		|||
  arrival_offset_seconds: number;
 | 
			
		||||
 | 
			
		||||
  @Column({ default: 600 })
 | 
			
		||||
  early_login_seconds: number;
 | 
			
		||||
  early_tracking_seconds: number;
 | 
			
		||||
 | 
			
		||||
  @Column({ default: null, nullable: true })
 | 
			
		||||
  @Exclude()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,15 +8,15 @@ import {
 | 
			
		|||
  deleteProfile,
 | 
			
		||||
  shouldUpdateProductTab,
 | 
			
		||||
} from "./service/app-service.js";
 | 
			
		||||
import { updateLoginStatus } from "./system/apis/bid.js";
 | 
			
		||||
import browser from "./system/browser.js";
 | 
			
		||||
import configs from "./system/config.js";
 | 
			
		||||
import {
 | 
			
		||||
  delay,
 | 
			
		||||
  findEarlyLoginTime,
 | 
			
		||||
  isTimeReached,
 | 
			
		||||
  safeClosePage,
 | 
			
		||||
  subtractSeconds,
 | 
			
		||||
} from "./system/utils.js";
 | 
			
		||||
import { updateLoginStatus } from "./system/apis/bid.js";
 | 
			
		||||
 | 
			
		||||
global.IS_CLEANING = true;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -156,6 +156,21 @@ const tracking = async () => {
 | 
			
		|||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Thời điểm tracking liên tục
 | 
			
		||||
          const earlyTrackingTime = subtractSeconds(
 | 
			
		||||
            productTab.close_time,
 | 
			
		||||
            productTab?.web_bid?.early_tracking_seconds || 0
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          // Check không mở tab nếu chưa đến giờ
 | 
			
		||||
          if (productTab.close_time && !isTimeReached(earlyTrackingTime)) {
 | 
			
		||||
            console.log(
 | 
			
		||||
              `⏳ [${productTab.id}] Early tracking time not reached yet. ` +
 | 
			
		||||
                `Waiting until ${earlyTrackingTime} (current time: ${new Date().toISOString()})`
 | 
			
		||||
            );
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          // Kết nối Puppeteer nếu chưa có page_context
 | 
			
		||||
          if (!productTab.page_context) {
 | 
			
		||||
            console.log(
 | 
			
		||||
| 
						 | 
				
			
			@ -231,72 +246,6 @@ const tracking = async () => {
 | 
			
		|||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// const clearLazyTab = async () => {
 | 
			
		||||
//   if (!global.IS_CLEANING) {
 | 
			
		||||
//     console.log("🚀 Cleaning flag is OFF. Proceeding with operation.");
 | 
			
		||||
//     return;
 | 
			
		||||
//   }
 | 
			
		||||
 | 
			
		||||
//   if (!browser) {
 | 
			
		||||
//     console.warn("⚠️ Browser is not available or disconnected.");
 | 
			
		||||
//     return;
 | 
			
		||||
//   }
 | 
			
		||||
 | 
			
		||||
//   try {
 | 
			
		||||
//     const pages = await browser.pages();
 | 
			
		||||
 | 
			
		||||
//     // Lấy danh sách URL từ flattenedArray
 | 
			
		||||
//     const activeUrls = _.flatMap(MANAGER_BIDS, (item) => [
 | 
			
		||||
//       item.url,
 | 
			
		||||
//       ...item.children.map((child) => child.url),
 | 
			
		||||
//     ]).filter(Boolean); // Lọc bỏ null hoặc undefined
 | 
			
		||||
 | 
			
		||||
//     console.log(
 | 
			
		||||
//       "🔍 Page URLs:",
 | 
			
		||||
//       pages.map((page) => page.url())
 | 
			
		||||
//     );
 | 
			
		||||
 | 
			
		||||
//     for (const page of pages) {
 | 
			
		||||
//       const pageUrl = page.url();
 | 
			
		||||
 | 
			
		||||
//       if (!pageUrl || pageUrl === "about:blank") continue;
 | 
			
		||||
 | 
			
		||||
//       if (!activeUrls.includes(pageUrl)) {
 | 
			
		||||
//         if (!page.isClosed() && browser.isConnected()) {
 | 
			
		||||
//           try {
 | 
			
		||||
//             const bidData = MANAGER_BIDS.filter((item) => item.page_context)
 | 
			
		||||
//               .map((i) => ({
 | 
			
		||||
//                 current_url: i.page_context.url(),
 | 
			
		||||
//                 data: i,
 | 
			
		||||
//               }))
 | 
			
		||||
//               .find((j) => j.current_url === pageUrl);
 | 
			
		||||
 | 
			
		||||
//             console.log(bidData);
 | 
			
		||||
 | 
			
		||||
//             if (bidData && bidData.data) {
 | 
			
		||||
//               await safeClosePage(bidData.data);
 | 
			
		||||
//             } else {
 | 
			
		||||
//               // 👇 Wrap close with timeout + error catch
 | 
			
		||||
//               await Promise.race([
 | 
			
		||||
//                 page.close(),
 | 
			
		||||
//                 new Promise((_, reject) =>
 | 
			
		||||
//                   setTimeout(() => reject(new Error("Close timeout")), 3000)
 | 
			
		||||
//                 ),
 | 
			
		||||
//               ]);
 | 
			
		||||
//             }
 | 
			
		||||
 | 
			
		||||
//             console.log(`🛑 Closing unused tab: ${pageUrl}`);
 | 
			
		||||
//           } catch (err) {
 | 
			
		||||
//             console.warn(`⚠️ Error closing tab ${pageUrl}: ${err.message}`);
 | 
			
		||||
//           }
 | 
			
		||||
//         }
 | 
			
		||||
//       }
 | 
			
		||||
//     }
 | 
			
		||||
//   } catch (err) {
 | 
			
		||||
//     console.error("❌ Error in clearLazyTab:", err.message);
 | 
			
		||||
//   }
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
const clearLazyTab = async () => {
 | 
			
		||||
  if (!global.IS_CLEANING) {
 | 
			
		||||
    console.log("🚀 Cleaning flag is OFF. Proceeding with operation.");
 | 
			
		||||
| 
						 | 
				
			
			@ -317,6 +266,9 @@ const clearLazyTab = async () => {
 | 
			
		|||
      ...item.children.map((child) => child.url),
 | 
			
		||||
    ]).filter(Boolean);
 | 
			
		||||
 | 
			
		||||
    // product tabs
 | 
			
		||||
    const productTabs = _.flatMap(MANAGER_BIDS, "children");
 | 
			
		||||
 | 
			
		||||
    for (const page of pages) {
 | 
			
		||||
      try {
 | 
			
		||||
        if (page.isClosed()) continue; // Trang đã đóng thì bỏ qua
 | 
			
		||||
| 
						 | 
				
			
			@ -324,8 +276,35 @@ const clearLazyTab = async () => {
 | 
			
		|||
        const pageUrl = page.url();
 | 
			
		||||
 | 
			
		||||
        if (!pageUrl || pageUrl === "about:blank") continue;
 | 
			
		||||
        if (activeUrls.includes(pageUrl)) continue;
 | 
			
		||||
 | 
			
		||||
        if (activeUrls.includes(pageUrl)) {
 | 
			
		||||
          const productTab = productTabs.find((item) => item.url === pageUrl);
 | 
			
		||||
 | 
			
		||||
          if (!productTab || !productTab?.close_time) continue;
 | 
			
		||||
 | 
			
		||||
          const earlyTrackingTime = subtractSeconds(
 | 
			
		||||
            productTab.close_time,
 | 
			
		||||
            productTab?.web_bid?.early_tracking_seconds || 0
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          console.log("%cindex.js:291 {object}", "color: #007acc;", {
 | 
			
		||||
            earlyTrackingTime,
 | 
			
		||||
            it_time: isTimeReached(earlyTrackingTime),
 | 
			
		||||
            close_time: productTab.close_time,
 | 
			
		||||
            tracking_time: productTab?.web_bid?.early_tracking_seconds,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          if (!isTimeReached(earlyTrackingTime)) {
 | 
			
		||||
            await safeClosePage(productTab);
 | 
			
		||||
            console.log(`🛑 Unused page detected: ${pageUrl}`);
 | 
			
		||||
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // remove all listents
 | 
			
		||||
        page.removeAllListeners();
 | 
			
		||||
 | 
			
		||||
        console.log(`🛑 Unused page detected: ${pageUrl}`);
 | 
			
		||||
| 
						 | 
				
			
			@ -473,6 +452,15 @@ const trackingLoginStatus = async () => {
 | 
			
		|||
  socket.on("webUpdated", async (data) => {
 | 
			
		||||
    console.log("📢 Account was updated:", data);
 | 
			
		||||
 | 
			
		||||
    const webBid = MANAGER_BIDS.find((item) => item.id === data.id);
 | 
			
		||||
 | 
			
		||||
    if (
 | 
			
		||||
      webBid &&
 | 
			
		||||
      webBid.username === data.username &&
 | 
			
		||||
      webBid.password === data.password
 | 
			
		||||
    )
 | 
			
		||||
      return;
 | 
			
		||||
 | 
			
		||||
    const isDeleted = deleteProfile(data);
 | 
			
		||||
 | 
			
		||||
    if (isDeleted) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ export class ApiBid extends Bid {
 | 
			
		|||
  updated_at;
 | 
			
		||||
  origin_url;
 | 
			
		||||
  active;
 | 
			
		||||
  browser_context;
 | 
			
		||||
  // browser_context;
 | 
			
		||||
  username;
 | 
			
		||||
  password;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ export class Bid {
 | 
			
		|||
  url;
 | 
			
		||||
  action;
 | 
			
		||||
  page_context;
 | 
			
		||||
  browser_context;
 | 
			
		||||
 | 
			
		||||
  constructor(type, url, puppeteer_connect) {
 | 
			
		||||
    this.type = type;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -120,6 +120,7 @@ export class ProductBid extends Bid {
 | 
			
		|||
    const page = await context.newPage();
 | 
			
		||||
 | 
			
		||||
    this.page_context = page;
 | 
			
		||||
    this.browser_context = context;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  async restoreContext(context) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -94,13 +94,15 @@ export async function safeClosePage(item) {
 | 
			
		|||
 | 
			
		||||
    if (!page?.isClosed() && page?.close) {
 | 
			
		||||
      await safeClosePageReal(page);
 | 
			
		||||
      // await page.close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    item.page_context = undefined;
 | 
			
		||||
    if (item?.page_context) {
 | 
			
		||||
      item.page_context = undefined;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (item?.browser_context) {
 | 
			
		||||
      item.browser_context?.close();
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.log("Can't close item: " + item.id);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -258,3 +260,15 @@ export function findEarlyLoginTime(webBid) {
 | 
			
		|||
 | 
			
		||||
  return closeTime.toISOString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function subtractMinutes(time, minutes) {
 | 
			
		||||
  const date = new Date(time);
 | 
			
		||||
  date.setMinutes(date.getMinutes() - minutes);
 | 
			
		||||
  return date.toUTCString();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function subtractSeconds(time, seconds) {
 | 
			
		||||
  const date = new Date(time);
 | 
			
		||||
  date.setSeconds(date.getSeconds() - seconds);
 | 
			
		||||
  return date.toUTCString();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue