Deploy to production #22
			
				
			
		
		
		
	| 
						 | 
					@ -43,7 +43,7 @@ export const updateWebBid = async (bid: Partial<IWebBid>) => {
 | 
				
			||||||
    origin_url,
 | 
					    origin_url,
 | 
				
			||||||
    active,
 | 
					    active,
 | 
				
			||||||
    arrival_offset_seconds,
 | 
					    arrival_offset_seconds,
 | 
				
			||||||
    // early_login_seconds
 | 
					    early_tracking_seconds
 | 
				
			||||||
  } = removeFalsyValues(bid, ["active"]);
 | 
					  } = removeFalsyValues(bid, ["active"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
| 
						 | 
					@ -58,7 +58,7 @@ export const updateWebBid = async (bid: Partial<IWebBid>) => {
 | 
				
			||||||
        origin_url,
 | 
					        origin_url,
 | 
				
			||||||
        active,
 | 
					        active,
 | 
				
			||||||
        arrival_offset_seconds,
 | 
					        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 { useDisclosure } from "@mantine/hooks";
 | 
				
			||||||
import moment from "moment";
 | 
					import moment from "moment";
 | 
				
			||||||
import { useEffect, useState } from "react";
 | 
					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 { IBid, IWebBid } from "../../system/type";
 | 
				
			||||||
import { cn, stringToColor } from "../../utils";
 | 
					import { cn, stringToColor } from "../../utils";
 | 
				
			||||||
import ShowImageModal from "./show-image-modal";
 | 
					import ShowImageModal from "./show-image-modal";
 | 
				
			||||||
 | 
					import { isTimeReached, subtractSeconds } from "../../lib/table/ultils";
 | 
				
			||||||
export interface IWorkingPageProps {
 | 
					export interface IWorkingPageProps {
 | 
				
			||||||
  data: (IBid | IWebBid) & { type: string };
 | 
					  data: (IBid | IWebBid) & { type: string };
 | 
				
			||||||
  socket: Socket;
 | 
					  socket: Socket;
 | 
				
			||||||
| 
						 | 
					@ -85,12 +86,6 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    const onLoginStatus = (data: { data: IWebBid; login_status: boolean }) => {
 | 
					    const onLoginStatus = (data: { data: IWebBid; login_status: boolean }) => {
 | 
				
			||||||
      setPayloadLoginStatus(data);
 | 
					      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;
 | 
					    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) && (
 | 
					          {isIBid(data) && (
 | 
				
			||||||
            <Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>
 | 
					            <Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>
 | 
				
			||||||
          )}
 | 
					          )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <Text className="text-sm italic opacity-80">
 | 
					          <Text className="text-sm italic opacity-80">
 | 
				
			||||||
            {moment(lastUpdate).format("HH:mm:ss DD/MM/YYYY")}
 | 
					            {moment(lastUpdate).format("HH:mm:ss DD/MM/YYYY")}
 | 
				
			||||||
          </Text>
 | 
					          </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">
 | 
					          <Box className="flex items-center gap-4">
 | 
				
			||||||
            <Button
 | 
					            <Button
 | 
				
			||||||
              size="xs"
 | 
					              size="xs"
 | 
				
			||||||
| 
						 | 
					@ -177,7 +206,9 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
				
			||||||
          </Badge>
 | 
					          </Badge>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <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"
 | 
					            size="xs"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            {isIBid(data) ? data.web_bid.origin_url : data.origin_url}
 | 
					            {isIBid(data) ? data.web_bid.origin_url : data.origin_url}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@ const schema = {
 | 
				
			||||||
    .refine((val) => val >= 60, {
 | 
					    .refine((val) => val >= 60, {
 | 
				
			||||||
      message: "Arrival offset seconds must be at least 60 seconds (1 minute)",
 | 
					      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" })
 | 
					    .number({ message: "Early login seconds is required" })
 | 
				
			||||||
    .refine((val) => val >= 600, {
 | 
					    .refine((val) => val >= 600, {
 | 
				
			||||||
      message: "Early login seconds must be at least 600 seconds (10 minute)",
 | 
					      message: "Early login seconds must be at least 600 seconds (10 minute)",
 | 
				
			||||||
| 
						 | 
					@ -78,14 +78,14 @@ export default function WebBidModal({
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    } else {
 | 
					    } 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);
 | 
					      setLoading(true);
 | 
				
			||||||
      const result = await createWebBid({
 | 
					      const result = await createWebBid({
 | 
				
			||||||
        url,
 | 
					        url,
 | 
				
			||||||
        origin_url,
 | 
					        origin_url,
 | 
				
			||||||
        arrival_offset_seconds,
 | 
					        arrival_offset_seconds,
 | 
				
			||||||
        early_login_seconds
 | 
					        early_tracking_seconds
 | 
				
			||||||
      } as IWebBid);
 | 
					      } as IWebBid);
 | 
				
			||||||
      setLoading(false);
 | 
					      setLoading(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -163,16 +163,16 @@ export default function WebBidModal({
 | 
				
			||||||
          placeholder="msg: 300"
 | 
					          placeholder="msg: 300"
 | 
				
			||||||
          {...form.getInputProps("arrival_offset_seconds")}
 | 
					          {...form.getInputProps("arrival_offset_seconds")}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        {/* <NumberInput
 | 
					        <NumberInput
 | 
				
			||||||
          description="Note: that only integer minutes are accepted."
 | 
					          description="Note: that only integer minutes are accepted."
 | 
				
			||||||
          className="col-span-2"
 | 
					          className="col-span-2"
 | 
				
			||||||
          size="sm"
 | 
					          size="sm"
 | 
				
			||||||
          label={`Early login seconds (${
 | 
					          label={`Early tracking seconds (${
 | 
				
			||||||
            form.getValues()["early_login_seconds"] / 60
 | 
					            form.getValues()["early_tracking_seconds"] / 60
 | 
				
			||||||
          } minutes)`}
 | 
					          } minutes)`}
 | 
				
			||||||
          placeholder="msg: 600"
 | 
					          placeholder="msg: 600"
 | 
				
			||||||
          {...form.getInputProps("early_login_seconds")}
 | 
					          {...form.getInputProps("early_tracking_seconds")}
 | 
				
			||||||
        /> */}
 | 
					        />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <Button
 | 
					        <Button
 | 
				
			||||||
          disabled={_.isEqual(form.getValues(), prevData.current)}
 | 
					          disabled={_.isEqual(form.getValues(), prevData.current)}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,84 +1,144 @@
 | 
				
			||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
					/* 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 defaultPrefixShort = "order_by_";
 | 
				
			||||||
export const defaultPathToData = 'data';
 | 
					export const defaultPathToData = "data";
 | 
				
			||||||
export const flowShort: TShort[] = ['desc', 'asc'];
 | 
					export const flowShort: TShort[] = ["desc", "asc"];
 | 
				
			||||||
export const defaultKeyPerpage = 'per_page';
 | 
					export const defaultKeyPerpage = "per_page";
 | 
				
			||||||
export const defaultKeyPage = 'page';
 | 
					export const defaultKeyPage = "page";
 | 
				
			||||||
export const searchKey = 'search_key';
 | 
					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>[] }) => {
 | 
					export const getParamsData = <
 | 
				
			||||||
    const paramsUrl = new URLSearchParams(window.location.search);
 | 
					  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 } = {};
 | 
					  const paramObject: { [key: string]: string | number } = {};
 | 
				
			||||||
    paramsUrl.forEach((value, key) => {
 | 
					  paramsUrl.forEach((value, key) => {
 | 
				
			||||||
        paramObject[key] = value;
 | 
					    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)
 | 
					  const shortObject = shortParamsData[0]
 | 
				
			||||||
        return {
 | 
					    ? {
 | 
				
			||||||
            shortParamsData: {},
 | 
					        [`${prefixShort}${shortParamsData[0].key}`]: String(
 | 
				
			||||||
            searchParamsData: {},
 | 
					          shortParamsData[0].type
 | 
				
			||||||
            filterParamsData: [],
 | 
					        ),
 | 
				
			||||||
            params: {},
 | 
					      }
 | 
				
			||||||
        };
 | 
					    : {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const shortParamsData = pramsKeys
 | 
					  const pramsData: IDataFilter[] = [
 | 
				
			||||||
        .filter((item) => item.includes(prefixShort) && options.columns.map((col) => col.key).includes(item.replace(prefixShort, '') as IColumn<R>['key']))
 | 
					    ...(filterParamsData ? (filterParamsData as IDataFilter[]) : []),
 | 
				
			||||||
        .map((i) => {
 | 
					  ];
 | 
				
			||||||
            return { key: i.replace(prefixShort, ''), type: flowShort.includes(paramObject[i] as TShort) ? paramObject[i] : 'asc' } as ITableShort<R>;
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const filterParamsData = pramsKeys
 | 
					  let params = convertToParams(
 | 
				
			||||||
        // .filter((item) => options.columns.map((col) => col.key).includes(item as IColumn<R>['key']))
 | 
					    pramsData as unknown as Record<string, string | number>[]
 | 
				
			||||||
        .map((item) => ({ key: item, type: paramObject[item] } as ITableFilter<R>));
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const shortObject = shortParamsData[0] ? { [`${prefixShort}${shortParamsData[0].key}`]: String(shortParamsData[0].type) } : {};
 | 
					  if (shortParamsData.length) {
 | 
				
			||||||
 | 
					    params = {
 | 
				
			||||||
    const pramsData: IDataFilter[] = [...(filterParamsData ? (filterParamsData as IDataFilter[]) : [])];
 | 
					      ...params,
 | 
				
			||||||
 | 
					      ...shortObject,
 | 
				
			||||||
    let params = convertToParams(pramsData as unknown as Record<string, string | number>[]);
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
    if (shortParamsData.length) {
 | 
					  return {
 | 
				
			||||||
        params = {
 | 
					    shortParamsData: shortParamsData[0] || {},
 | 
				
			||||||
            ...params,
 | 
					    filterParamsData,
 | 
				
			||||||
            ...shortObject,
 | 
					    params,
 | 
				
			||||||
        };
 | 
					  };
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return { shortParamsData: shortParamsData[0] || {}, filterParamsData, params };
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const convertToParams = (filter: Record<string, string | number>[]) => {
 | 
					export const convertToParams = (filter: Record<string, string | number>[]) => {
 | 
				
			||||||
    const params = filter.reduce((prev, cur) => {
 | 
					  const params = filter.reduce((prev, cur) => {
 | 
				
			||||||
        prev[cur.key] = cur.type;
 | 
					    prev[cur.key] = cur.type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return prev;
 | 
					    return prev;
 | 
				
			||||||
    }, {} as Record<string, string | number>);
 | 
					  }, {} as Record<string, string | number>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return params;
 | 
					  return params;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const removeFalsy = (data: { [key: string]: string | number }) => {
 | 
					export const removeFalsy = (data: { [key: string]: string | number }) => {
 | 
				
			||||||
    return Object.keys(data).reduce((prev, cur) => {
 | 
					  return Object.keys(data).reduce((prev, cur) => {
 | 
				
			||||||
        if (data[cur]) {
 | 
					    if (data[cur]) {
 | 
				
			||||||
            prev[cur] = data[cur];
 | 
					      prev[cur] = data[cur];
 | 
				
			||||||
        }
 | 
					    }
 | 
				
			||||||
        return prev;
 | 
					    return prev;
 | 
				
			||||||
    }, {} as { [key: string]: string | number });
 | 
					  }, {} as { [key: string]: string | number });
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function extractNumber(str: string) {
 | 
					export function extractNumber(str: string) {
 | 
				
			||||||
    const match = str.match(/\d+(\.\d+)?/);
 | 
					  const match = str.match(/\d+(\.\d+)?/);
 | 
				
			||||||
    return match ? parseFloat(match[0]) : null;
 | 
					  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>
 | 
					          </Box>
 | 
				
			||||||
        </Tooltip>
 | 
					        </Tooltip>
 | 
				
			||||||
      </Box>
 | 
					      </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.length > 0 &&
 | 
				
			||||||
          workingData.map((item, index) => (
 | 
					          workingData.map((item, index) => (
 | 
				
			||||||
            <WorkingPage socket={socket} data={item} key={item.id + index} />
 | 
					            <WorkingPage socket={socket} data={item} key={item.id + index} />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ export interface IWebBid extends ITimestamp {
 | 
				
			||||||
    password: string | null;
 | 
					    password: string | null;
 | 
				
			||||||
    active: boolean;
 | 
					    active: boolean;
 | 
				
			||||||
    arrival_offset_seconds: number;
 | 
					    arrival_offset_seconds: number;
 | 
				
			||||||
    early_login_seconds: number;
 | 
					    early_tracking_seconds: number;
 | 
				
			||||||
    children: IBid[];
 | 
					    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
 | 
					  // Bước 3: Tính toán thời gian login sớm
 | 
				
			||||||
  const closeTime = new Date(closestBid.close_time);
 | 
					  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();
 | 
					  return closeTime.toISOString();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1 @@
 | 
				
			||||||
<<<<<<< HEAD
 | 
					{"createdAt":1746603511532}
 | 
				
			||||||
{"createdAt":1745827424853}
 | 
					 | 
				
			||||||
=======
 | 
					 | 
				
			||||||
{"createdAt":1746413672600}
 | 
					 | 
				
			||||||
>>>>>>> 26b10a7 (pickxel and fix login)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ export class UpdateWebBidDto {
 | 
				
			||||||
  @IsNumber()
 | 
					  @IsNumber()
 | 
				
			||||||
  @Min(600)
 | 
					  @Min(600)
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  early_login_seconds: number;
 | 
					  early_tracking_seconds: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,7 +21,7 @@ export class WebBid extends Timestamp {
 | 
				
			||||||
  arrival_offset_seconds: number;
 | 
					  arrival_offset_seconds: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Column({ default: 600 })
 | 
					  @Column({ default: 600 })
 | 
				
			||||||
  early_login_seconds: number;
 | 
					  early_tracking_seconds: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Column({ default: null, nullable: true })
 | 
					  @Column({ default: null, nullable: true })
 | 
				
			||||||
  @Exclude()
 | 
					  @Exclude()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,15 +8,15 @@ import {
 | 
				
			||||||
  deleteProfile,
 | 
					  deleteProfile,
 | 
				
			||||||
  shouldUpdateProductTab,
 | 
					  shouldUpdateProductTab,
 | 
				
			||||||
} from "./service/app-service.js";
 | 
					} from "./service/app-service.js";
 | 
				
			||||||
 | 
					import { updateLoginStatus } from "./system/apis/bid.js";
 | 
				
			||||||
import browser from "./system/browser.js";
 | 
					import browser from "./system/browser.js";
 | 
				
			||||||
import configs from "./system/config.js";
 | 
					import configs from "./system/config.js";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  delay,
 | 
					  delay,
 | 
				
			||||||
  findEarlyLoginTime,
 | 
					 | 
				
			||||||
  isTimeReached,
 | 
					  isTimeReached,
 | 
				
			||||||
  safeClosePage,
 | 
					  safeClosePage,
 | 
				
			||||||
 | 
					  subtractSeconds,
 | 
				
			||||||
} from "./system/utils.js";
 | 
					} from "./system/utils.js";
 | 
				
			||||||
import { updateLoginStatus } from "./system/apis/bid.js";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
global.IS_CLEANING = true;
 | 
					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
 | 
					          // Kết nối Puppeteer nếu chưa có page_context
 | 
				
			||||||
          if (!productTab.page_context) {
 | 
					          if (!productTab.page_context) {
 | 
				
			||||||
            console.log(
 | 
					            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 () => {
 | 
					const clearLazyTab = async () => {
 | 
				
			||||||
  if (!global.IS_CLEANING) {
 | 
					  if (!global.IS_CLEANING) {
 | 
				
			||||||
    console.log("🚀 Cleaning flag is OFF. Proceeding with operation.");
 | 
					    console.log("🚀 Cleaning flag is OFF. Proceeding with operation.");
 | 
				
			||||||
| 
						 | 
					@ -317,6 +266,9 @@ const clearLazyTab = async () => {
 | 
				
			||||||
      ...item.children.map((child) => child.url),
 | 
					      ...item.children.map((child) => child.url),
 | 
				
			||||||
    ]).filter(Boolean);
 | 
					    ]).filter(Boolean);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // product tabs
 | 
				
			||||||
 | 
					    const productTabs = _.flatMap(MANAGER_BIDS, "children");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const page of pages) {
 | 
					    for (const page of pages) {
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
        if (page.isClosed()) continue; // Trang đã đóng thì bỏ qua
 | 
					        if (page.isClosed()) continue; // Trang đã đóng thì bỏ qua
 | 
				
			||||||
| 
						 | 
					@ -324,8 +276,35 @@ const clearLazyTab = async () => {
 | 
				
			||||||
        const pageUrl = page.url();
 | 
					        const pageUrl = page.url();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!pageUrl || pageUrl === "about:blank") continue;
 | 
					        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();
 | 
					        page.removeAllListeners();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        console.log(`🛑 Unused page detected: ${pageUrl}`);
 | 
					        console.log(`🛑 Unused page detected: ${pageUrl}`);
 | 
				
			||||||
| 
						 | 
					@ -473,6 +452,15 @@ const trackingLoginStatus = async () => {
 | 
				
			||||||
  socket.on("webUpdated", async (data) => {
 | 
					  socket.on("webUpdated", async (data) => {
 | 
				
			||||||
    console.log("📢 Account was updated:", 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);
 | 
					    const isDeleted = deleteProfile(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (isDeleted) {
 | 
					    if (isDeleted) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@ export class ApiBid extends Bid {
 | 
				
			||||||
  updated_at;
 | 
					  updated_at;
 | 
				
			||||||
  origin_url;
 | 
					  origin_url;
 | 
				
			||||||
  active;
 | 
					  active;
 | 
				
			||||||
  browser_context;
 | 
					  // browser_context;
 | 
				
			||||||
  username;
 | 
					  username;
 | 
				
			||||||
  password;
 | 
					  password;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ export class Bid {
 | 
				
			||||||
  url;
 | 
					  url;
 | 
				
			||||||
  action;
 | 
					  action;
 | 
				
			||||||
  page_context;
 | 
					  page_context;
 | 
				
			||||||
 | 
					  browser_context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(type, url, puppeteer_connect) {
 | 
					  constructor(type, url, puppeteer_connect) {
 | 
				
			||||||
    this.type = type;
 | 
					    this.type = type;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -120,6 +120,7 @@ export class ProductBid extends Bid {
 | 
				
			||||||
    const page = await context.newPage();
 | 
					    const page = await context.newPage();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.page_context = page;
 | 
					    this.page_context = page;
 | 
				
			||||||
 | 
					    this.browser_context = context;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async restoreContext(context) {
 | 
					  async restoreContext(context) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -94,13 +94,15 @@ export async function safeClosePage(item) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!page?.isClosed() && page?.close) {
 | 
					    if (!page?.isClosed() && page?.close) {
 | 
				
			||||||
      await safeClosePageReal(page);
 | 
					      await safeClosePageReal(page);
 | 
				
			||||||
      // await page.close();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    item.page_context = undefined;
 | 
					 | 
				
			||||||
    if (item?.page_context) {
 | 
					    if (item?.page_context) {
 | 
				
			||||||
      item.page_context = undefined;
 | 
					      item.page_context = undefined;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (item?.browser_context) {
 | 
				
			||||||
 | 
					      item.browser_context?.close();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    console.log("Can't close item: " + item.id);
 | 
					    console.log("Can't close item: " + item.id);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -258,3 +260,15 @@ export function findEarlyLoginTime(webBid) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return closeTime.toISOString();
 | 
					  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