Deploy to production #58
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -17,11 +17,13 @@ export default function DeleteRowAction({
 | 
			
		|||
    setConfirm({
 | 
			
		||||
      handleOk: async () => {
 | 
			
		||||
        const result = await deletesBid(chooses);
 | 
			
		||||
 | 
			
		||||
        console.log({ result });
 | 
			
		||||
        if (!result) return;
 | 
			
		||||
        onDeleted?.();
 | 
			
		||||
      },
 | 
			
		||||
      title: 'Delete',
 | 
			
		||||
      message: `This action will remove ${chooses.length} products.`
 | 
			
		||||
      title: "Delete",
 | 
			
		||||
      message: `This action will remove ${chooses.length} products.`,
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,2 @@
 | 
			
		|||
export { default as ShowHistoriesModal } from './show-histories-modal';
 | 
			
		||||
export { default as ShowHistoriesBidGraysApiModal } from './show-histories-bid-grays-api-modal';
 | 
			
		||||
export { default as ShowHistoriesBidPicklesApiModal } from './show-histories-bid-pickles-api-modal';
 | 
			
		||||
export { default as BidModal } from './bid-modal';
 | 
			
		||||
export { default as ShowHistoriesModal } from "./show-histories-modal";
 | 
			
		||||
export { default as BidModal } from "./bid-modal";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
			
		||||
 | 
			
		||||
import Table from "../../../lib/table/table";
 | 
			
		||||
import { IColumn } from "../../../lib/table/type";
 | 
			
		||||
import { formatTime } from "../../../utils";
 | 
			
		||||
export interface IGraysHistoriesViewProps {
 | 
			
		||||
  histories: Record<string, string>[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function AllbidsHistoriesView({
 | 
			
		||||
  histories,
 | 
			
		||||
}: IGraysHistoriesViewProps) {
 | 
			
		||||
  type BidHistoryEntry = {
 | 
			
		||||
    row_id: number;
 | 
			
		||||
    date: string; // ISO datetime string
 | 
			
		||||
    amount: number; // Số tiền đặt giá
 | 
			
		||||
    proxyamount: number; // Giá proxy tối đa
 | 
			
		||||
    bidQty: number; // Số lượng
 | 
			
		||||
    flashBuy: boolean; // Mua ngay
 | 
			
		||||
    proxyBid: boolean; // Có phải đấu giá tự động
 | 
			
		||||
    instantBid: boolean; // Đặt giá ngay
 | 
			
		||||
    userName: string; // Tên người dùng
 | 
			
		||||
    bidBidderID: number; // ID người đặt giá
 | 
			
		||||
    $$hashKey?: string; // Khóa nội bộ Angular (không cần thiết, có thể bỏ hoặc để optional)
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const columns: IColumn<BidHistoryEntry>[] = [
 | 
			
		||||
    {
 | 
			
		||||
      title: "Username",
 | 
			
		||||
      key: "userName",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Amount",
 | 
			
		||||
      key: "amount",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Proxy amount",
 | 
			
		||||
      key: "proxyamount",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Bid Qty",
 | 
			
		||||
      key: "bidQty",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Bid at",
 | 
			
		||||
      key: "date",
 | 
			
		||||
      renderRow(row) {
 | 
			
		||||
        return <span>{formatTime(row.date)}</span>;
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table
 | 
			
		||||
      striped
 | 
			
		||||
      highlightOnHover
 | 
			
		||||
      withTableBorder
 | 
			
		||||
      withColumnBorders
 | 
			
		||||
      styleDefaultHead={{
 | 
			
		||||
        justifyContent: "flex-start",
 | 
			
		||||
        width: "fit-content",
 | 
			
		||||
      }}
 | 
			
		||||
      showFilter={false}
 | 
			
		||||
      showActions={false}
 | 
			
		||||
      showChooses={false}
 | 
			
		||||
      columns={columns}
 | 
			
		||||
      rowKey="row_id"
 | 
			
		||||
      rows={histories as unknown as BidHistoryEntry[]}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,80 @@
 | 
			
		|||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
import { extractNumber, formatTime } from "../../../utils";
 | 
			
		||||
import Table from "../../../lib/table/table";
 | 
			
		||||
import { IColumn } from "../../../lib/table/type";
 | 
			
		||||
 | 
			
		||||
export interface IGraysHistoriesViewProps {
 | 
			
		||||
  histories: Record<string, string>[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function GraysHistoriesView({
 | 
			
		||||
  histories,
 | 
			
		||||
}: IGraysHistoriesViewProps) {
 | 
			
		||||
  type BidHistoryEntry = {
 | 
			
		||||
    row_id: number;
 | 
			
		||||
    Price: string;
 | 
			
		||||
    Quantity: number;
 | 
			
		||||
    WinningQuantity: number;
 | 
			
		||||
    UserShortAddress: string;
 | 
			
		||||
    UserInitials: string;
 | 
			
		||||
    OriginalDate: string;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const columns: IColumn<BidHistoryEntry>[] = [
 | 
			
		||||
    {
 | 
			
		||||
      title: "Bidding Details",
 | 
			
		||||
      key: "UserInitials",
 | 
			
		||||
      renderRow(row) {
 | 
			
		||||
        return (
 | 
			
		||||
          <span>{`${row["UserInitials"]} - ${row["UserShortAddress"]}`}</span>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Bid Time",
 | 
			
		||||
      key: "OriginalDate",
 | 
			
		||||
      renderRow(row) {
 | 
			
		||||
        return (
 | 
			
		||||
          <span>
 | 
			
		||||
            {formatTime(
 | 
			
		||||
              new Date(extractNumber(row["OriginalDate"]) || 0).toUTCString(),
 | 
			
		||||
              "HH:mm:ss DD/MM/YYYY"
 | 
			
		||||
            )}
 | 
			
		||||
          </span>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Bid Price",
 | 
			
		||||
      key: "Price",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Bid Qty",
 | 
			
		||||
      key: "Quantity",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Win Qty",
 | 
			
		||||
      key: "WinningQuantity",
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table
 | 
			
		||||
      striped
 | 
			
		||||
      highlightOnHover
 | 
			
		||||
      withTableBorder
 | 
			
		||||
      withColumnBorders
 | 
			
		||||
      styleDefaultHead={{
 | 
			
		||||
        justifyContent: "flex-start",
 | 
			
		||||
        width: "fit-content",
 | 
			
		||||
      }}
 | 
			
		||||
      showFilter={false}
 | 
			
		||||
      showActions={false}
 | 
			
		||||
      showChooses={false}
 | 
			
		||||
      columns={columns}
 | 
			
		||||
      rowKey="row_id"
 | 
			
		||||
      rows={histories as unknown as BidHistoryEntry[]}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,79 @@
 | 
			
		|||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
			
		||||
import { useMemo } from "react";
 | 
			
		||||
import { formatTime } from "../../../utils";
 | 
			
		||||
import { IColumn } from "../../../lib/table/type";
 | 
			
		||||
import Table from "../../../lib/table/table";
 | 
			
		||||
 | 
			
		||||
export interface IGraysHistoriesViewProps {
 | 
			
		||||
  histories: Record<string, string>[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function PicklesHistoriesView({
 | 
			
		||||
  histories,
 | 
			
		||||
}: IGraysHistoriesViewProps) {
 | 
			
		||||
  // const rows = useMemo(() => {
 | 
			
		||||
  //   return histories.map((element, index) => (
 | 
			
		||||
  //     <Table.Tr key={index}>
 | 
			
		||||
  //       <Table.Td>{element["bidderAnonName"]}</Table.Td>
 | 
			
		||||
  //       <Table.Td>{element["actualBid"]}</Table.Td>
 | 
			
		||||
  //       <Table.Td>
 | 
			
		||||
  //         {formatTime(
 | 
			
		||||
  //           new Date(element["bidTimeInMilliSeconds"]).toUTCString(),
 | 
			
		||||
  //           "HH:mm:ss DD/MM/YYYY"
 | 
			
		||||
  //         )}
 | 
			
		||||
  //       </Table.Td>
 | 
			
		||||
  //     </Table.Tr>
 | 
			
		||||
  //   ));
 | 
			
		||||
  // }, [histories]);
 | 
			
		||||
 | 
			
		||||
  type BidHistoryEntry = {
 | 
			
		||||
    row_id: number;
 | 
			
		||||
    bidderAnonName: string;
 | 
			
		||||
    actualBid: number;
 | 
			
		||||
    bidTimeInMilliSeconds: number;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const columns: IColumn<BidHistoryEntry>[] = [
 | 
			
		||||
    {
 | 
			
		||||
      title: "Bidder name",
 | 
			
		||||
      key: "bidderAnonName",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Actual bid",
 | 
			
		||||
      key: "actualBid",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      title: "Time",
 | 
			
		||||
      key: "bidTimeInMilliSeconds",
 | 
			
		||||
      renderRow(row) {
 | 
			
		||||
        return (
 | 
			
		||||
          <span>
 | 
			
		||||
            {formatTime(
 | 
			
		||||
              new Date(row["bidTimeInMilliSeconds"]).toUTCString(),
 | 
			
		||||
              "HH:mm:ss DD/MM/YYYY"
 | 
			
		||||
            )}
 | 
			
		||||
          </span>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Table
 | 
			
		||||
      striped
 | 
			
		||||
      highlightOnHover
 | 
			
		||||
      withTableBorder
 | 
			
		||||
      withColumnBorders
 | 
			
		||||
      styleDefaultHead={{
 | 
			
		||||
        justifyContent: "flex-start",
 | 
			
		||||
        width: "fit-content",
 | 
			
		||||
      }}
 | 
			
		||||
      showFilter={false}
 | 
			
		||||
      showActions={false}
 | 
			
		||||
      showChooses={false}
 | 
			
		||||
      columns={columns}
 | 
			
		||||
      rowKey="row_id"
 | 
			
		||||
      rows={histories as unknown as BidHistoryEntry[]}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,81 @@
 | 
			
		|||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
			
		||||
import { LoadingOverlay, Modal, ModalProps } from "@mantine/core";
 | 
			
		||||
import { useCallback, useEffect, useMemo, useState } from "react";
 | 
			
		||||
import { getDetailBidHistories } from "../../../apis/bid-histories";
 | 
			
		||||
import { IBid } from "../../../system/type";
 | 
			
		||||
import GraysHistoriesView from "./grays-histories-view";
 | 
			
		||||
import constants from "../../../constant";
 | 
			
		||||
import PicklesHistoriesView from "./pickles-histories-view";
 | 
			
		||||
import AllbidsHistoriesView from "./allbids-histories-view";
 | 
			
		||||
 | 
			
		||||
export interface IShowHistoriesApiModalProps extends ModalProps {
 | 
			
		||||
  data: IBid | null;
 | 
			
		||||
  onUpdated?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function ShowHistoriesApiModal({
 | 
			
		||||
  data,
 | 
			
		||||
  onUpdated,
 | 
			
		||||
  ...props
 | 
			
		||||
}: IShowHistoriesApiModalProps) {
 | 
			
		||||
  const [histories, setHistories] = useState<Record<string, string>[]>([]);
 | 
			
		||||
 | 
			
		||||
  const [loading, setLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
  const handleCallApi = useCallback(async () => {
 | 
			
		||||
    if (!data?.lot_id) {
 | 
			
		||||
      setHistories([]);
 | 
			
		||||
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setLoading(true);
 | 
			
		||||
    const response = await getDetailBidHistories(data?.lot_id);
 | 
			
		||||
    setLoading(false);
 | 
			
		||||
 | 
			
		||||
    if (response.data && response.data) {
 | 
			
		||||
      const values = (response.data as Record<string, string>[]).map(
 | 
			
		||||
        (item, index) => {
 | 
			
		||||
          return {
 | 
			
		||||
            ...item,
 | 
			
		||||
            row_id: String(index),
 | 
			
		||||
          };
 | 
			
		||||
        }
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      setHistories(values);
 | 
			
		||||
    }
 | 
			
		||||
  }, [data]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    handleCallApi();
 | 
			
		||||
  }, [handleCallApi]);
 | 
			
		||||
 | 
			
		||||
  const generateView = useMemo(() => {
 | 
			
		||||
    switch (data?.web_bid.origin_url) {
 | 
			
		||||
      case constants.grays:
 | 
			
		||||
        return <GraysHistoriesView histories={histories} />;
 | 
			
		||||
      case constants.pickles:
 | 
			
		||||
        return <PicklesHistoriesView histories={histories} />;
 | 
			
		||||
      case constants.allbids:
 | 
			
		||||
        return <AllbidsHistoriesView histories={histories} />;
 | 
			
		||||
    }
 | 
			
		||||
  }, [data?.web_bid.origin_url, histories]);
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Modal
 | 
			
		||||
      className="relative"
 | 
			
		||||
      {...props}
 | 
			
		||||
      size="xl"
 | 
			
		||||
      title={<span className="text-xl font-bold">BIDDING HISTORY</span>}
 | 
			
		||||
      centered
 | 
			
		||||
    >
 | 
			
		||||
      {generateView}
 | 
			
		||||
      <LoadingOverlay
 | 
			
		||||
        visible={loading}
 | 
			
		||||
        zIndex={1000}
 | 
			
		||||
        overlayProps={{ blur: 2 }}
 | 
			
		||||
      />
 | 
			
		||||
    </Modal>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,78 +0,0 @@
 | 
			
		|||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
			
		||||
import { LoadingOverlay, Modal, ModalProps, Table } from '@mantine/core';
 | 
			
		||||
import { useCallback, useEffect, useMemo, useState } from 'react';
 | 
			
		||||
import { getDetailBidHistories } from '../../apis/bid-histories';
 | 
			
		||||
import { IBid } from '../../system/type';
 | 
			
		||||
import { extractNumber, formatTime } from '../../utils';
 | 
			
		||||
 | 
			
		||||
export interface IShowHistoriesBidGraysApiModalProps extends ModalProps {
 | 
			
		||||
    data: IBid | null;
 | 
			
		||||
    onUpdated?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function ShowHistoriesBidGraysApiModal({ data, onUpdated, ...props }: IShowHistoriesBidGraysApiModalProps) {
 | 
			
		||||
    const [histories, setHistories] = useState<Record<string, string>[]>([]);
 | 
			
		||||
 | 
			
		||||
    const [loading, setLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const rows = useMemo(() => {
 | 
			
		||||
        return histories.map((element, index) => (
 | 
			
		||||
            <Table.Tr key={index}>
 | 
			
		||||
                <Table.Td>{`${element['UserInitials']} - ${element['UserShortAddress']}`}</Table.Td>
 | 
			
		||||
                <Table.Td>{formatTime(new Date(extractNumber(element['OriginalDate']) || 0).toUTCString(), 'HH:mm:ss DD/MM/YYYY')}</Table.Td>
 | 
			
		||||
                <Table.Td>{`AU $${element['Price']}`}</Table.Td>
 | 
			
		||||
                <Table.Td>{`${element['Quantity']}`}</Table.Td>
 | 
			
		||||
                <Table.Td>{`${element['WinningQuantity']}`}</Table.Td>
 | 
			
		||||
            </Table.Tr>
 | 
			
		||||
        ));
 | 
			
		||||
    }, [histories]);
 | 
			
		||||
 | 
			
		||||
    const handleCallApi = useCallback(async () => {
 | 
			
		||||
        if (!data?.lot_id) {
 | 
			
		||||
            setHistories([]);
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setLoading(true);
 | 
			
		||||
        const response = await getDetailBidHistories(data?.lot_id);
 | 
			
		||||
        setLoading(false);
 | 
			
		||||
 | 
			
		||||
        if (response.data && response.data) {
 | 
			
		||||
            setHistories(response.data);
 | 
			
		||||
        }
 | 
			
		||||
    }, [data]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        handleCallApi();
 | 
			
		||||
    }, [handleCallApi]);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal className="relative" {...props} size="xl" title={<span className="text-xl font-bold">BIDDING HISTORY</span>} centered>
 | 
			
		||||
            <Table striped highlightOnHover withTableBorder withColumnBorders>
 | 
			
		||||
                <Table.Thead>
 | 
			
		||||
                    <Table.Tr>
 | 
			
		||||
                        <Table.Th>Bidding Details</Table.Th>
 | 
			
		||||
                        <Table.Th>Bid Time</Table.Th>
 | 
			
		||||
                        <Table.Th>Bid Price</Table.Th>
 | 
			
		||||
                        <Table.Th>Bid Qty</Table.Th>
 | 
			
		||||
                        <Table.Th>Win Qty</Table.Th>
 | 
			
		||||
                    </Table.Tr>
 | 
			
		||||
                </Table.Thead>
 | 
			
		||||
                <Table.Tbody>
 | 
			
		||||
                    {histories.length <= 0 ? (
 | 
			
		||||
                        <Table.Tr>
 | 
			
		||||
                            <Table.Td colSpan={5} className="text-center">
 | 
			
		||||
                                None
 | 
			
		||||
                            </Table.Td>
 | 
			
		||||
                        </Table.Tr>
 | 
			
		||||
                    ) : (
 | 
			
		||||
                        rows
 | 
			
		||||
                    )}
 | 
			
		||||
                </Table.Tbody>
 | 
			
		||||
            </Table>
 | 
			
		||||
 | 
			
		||||
            <LoadingOverlay visible={loading} zIndex={1000} overlayProps={{ blur: 2 }} />
 | 
			
		||||
        </Modal>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,13 @@
 | 
			
		|||
 const constants = {
 | 
			
		||||
    grays: 'https://www.grays.com',
 | 
			
		||||
    pickles:'https://www.pickles.com.au'
 | 
			
		||||
}
 | 
			
		||||
const constants = {
 | 
			
		||||
  grays: "https://www.grays.com",
 | 
			
		||||
  pickles: "https://www.pickles.com.au",
 | 
			
		||||
  allbids: "https://www.allbids.com.au",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const haveHistories = [constants.grays, constants.pickles]
 | 
			
		||||
export const haveHistories = [
 | 
			
		||||
  constants.grays,
 | 
			
		||||
  constants.pickles,
 | 
			
		||||
  constants.allbids,
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export default constants
 | 
			
		||||
export default constants;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,22 +21,18 @@ import {
 | 
			
		|||
} from "@tabler/icons-react";
 | 
			
		||||
import _ from "lodash";
 | 
			
		||||
import { useMemo, useRef, useState } from "react";
 | 
			
		||||
import { deleteBid, deletesBid, getBids, toggleBid } from "../apis/bid";
 | 
			
		||||
import {
 | 
			
		||||
  BidModal,
 | 
			
		||||
  ShowHistoriesBidGraysApiModal,
 | 
			
		||||
  ShowHistoriesBidPicklesApiModal,
 | 
			
		||||
  ShowHistoriesModal,
 | 
			
		||||
} from "../components/bid";
 | 
			
		||||
import constants, { haveHistories } from "../constant";
 | 
			
		||||
import { deleteBid, getBids, toggleBid } from "../apis/bid";
 | 
			
		||||
import { BidModal, ShowHistoriesModal } from "../components/bid";
 | 
			
		||||
import DeleteRowAction from "../components/bid/delete-row-action";
 | 
			
		||||
import { haveHistories } from "../constant";
 | 
			
		||||
import Table from "../lib/table/table";
 | 
			
		||||
import { IColumn, TRefTableFn } from "../lib/table/type";
 | 
			
		||||
import { useChoosesStore } from "../lib/zustand/use-chooses-store";
 | 
			
		||||
import { useConfirmStore } from "../lib/zustand/use-confirm";
 | 
			
		||||
import { mappingStatusColors } from "../system/constants";
 | 
			
		||||
import { IBid } from "../system/type";
 | 
			
		||||
import { extractDomainSmart, formatTime } from "../utils";
 | 
			
		||||
import DeleteRowAction from "../components/bid/delete-row-action";
 | 
			
		||||
import { useChoosesStore } from "../lib/zustand/use-chooses-store";
 | 
			
		||||
import ShowHistoriesApiModal from "../components/bid/show-histories-api/show-histories-api-modal";
 | 
			
		||||
 | 
			
		||||
export default function Bids() {
 | 
			
		||||
  const refTableFn: TRefTableFn<IBid> = useRef({});
 | 
			
		||||
| 
						 | 
				
			
			@ -47,12 +43,9 @@ export default function Bids() {
 | 
			
		|||
 | 
			
		||||
  const { setChooses } = useChoosesStore();
 | 
			
		||||
 | 
			
		||||
  const [openedHistories, historiesModel] = useDisclosure(false);
 | 
			
		||||
  const [openedHistoriesGraysApi, historiesGraysApiModel] =
 | 
			
		||||
    useDisclosure(false);
 | 
			
		||||
  const [openedHistories, historiesModal] = useDisclosure(false);
 | 
			
		||||
 | 
			
		||||
  const [openedHistoriesPicklesApi, historiesPicklesApiModel] =
 | 
			
		||||
    useDisclosure(false);
 | 
			
		||||
  const [openedHistoriesView, openedHistoriesViewModal] = useDisclosure(false);
 | 
			
		||||
  const [openedBid, bidModal] = useDisclosure(false);
 | 
			
		||||
 | 
			
		||||
  const columns: IColumn<IBid>[] = [
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +77,9 @@ export default function Bids() {
 | 
			
		|||
      title: "Web",
 | 
			
		||||
      typeFilter: "none",
 | 
			
		||||
      renderRow(row) {
 | 
			
		||||
        return <span>{extractDomainSmart(row.web_bid.origin_url)}</span>;
 | 
			
		||||
        return (
 | 
			
		||||
          <span>{extractDomainSmart(row.web_bid?.origin_url) || "None"}</span>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -238,28 +233,6 @@ export default function Bids() {
 | 
			
		|||
        }}
 | 
			
		||||
        actionsOptions={{
 | 
			
		||||
          showMainAction: false,
 | 
			
		||||
          actions: [
 | 
			
		||||
            {
 | 
			
		||||
              key: "delete",
 | 
			
		||||
              title: "Delete",
 | 
			
		||||
              callback: (data) => {
 | 
			
		||||
                if (!data.length) return;
 | 
			
		||||
                setConfirm({
 | 
			
		||||
                  title: "Delete",
 | 
			
		||||
                  message: `${data.length} will be delete`,
 | 
			
		||||
                  handleOk: async () => {
 | 
			
		||||
                    const result = await deletesBid(data);
 | 
			
		||||
 | 
			
		||||
                    if (!result) return;
 | 
			
		||||
                    if (refTableFn.current.fetchData) {
 | 
			
		||||
                      refTableFn.current.fetchData();
 | 
			
		||||
                    }
 | 
			
		||||
                  },
 | 
			
		||||
                });
 | 
			
		||||
              },
 | 
			
		||||
              disabled: (data) => data.length <= 0,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          leftActionSession: (
 | 
			
		||||
            <Box className="flex items-end gap-2">
 | 
			
		||||
              <Button
 | 
			
		||||
| 
						 | 
				
			
			@ -270,7 +243,11 @@ export default function Bids() {
 | 
			
		|||
                Add
 | 
			
		||||
              </Button>
 | 
			
		||||
 | 
			
		||||
              <DeleteRowAction onDeleted={refTableFn.current?.fetchData} />
 | 
			
		||||
              <DeleteRowAction
 | 
			
		||||
                onDeleted={() => {
 | 
			
		||||
                  refTableFn.current?.fetchData?.();
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
            </Box>
 | 
			
		||||
          ),
 | 
			
		||||
        }}
 | 
			
		||||
| 
						 | 
				
			
			@ -329,29 +306,17 @@ export default function Bids() {
 | 
			
		|||
                    <Menu.Item
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        setClickData(row);
 | 
			
		||||
                        historiesModel.open();
 | 
			
		||||
                        historiesModal.open();
 | 
			
		||||
                      }}
 | 
			
		||||
                      leftSection={<IconHistory size={14} />}
 | 
			
		||||
                    >
 | 
			
		||||
                      Histories
 | 
			
		||||
                    </Menu.Item>
 | 
			
		||||
                    {haveHistories.includes(row?.web_bid.origin_url) && (
 | 
			
		||||
                    {haveHistories.includes(row?.web_bid?.origin_url) && (
 | 
			
		||||
                      <Menu.Item
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                          setClickData(row);
 | 
			
		||||
                          switch (row.web_bid.origin_url) {
 | 
			
		||||
                            case constants.grays: {
 | 
			
		||||
                              historiesGraysApiModel.open();
 | 
			
		||||
                              break;
 | 
			
		||||
                            }
 | 
			
		||||
                            case constants.pickles: {
 | 
			
		||||
                              historiesPicklesApiModel.open();
 | 
			
		||||
                              break;
 | 
			
		||||
                            }
 | 
			
		||||
                            default: {
 | 
			
		||||
                              historiesGraysApiModel.open();
 | 
			
		||||
                            }
 | 
			
		||||
                          }
 | 
			
		||||
                          openedHistoriesViewModal.open();
 | 
			
		||||
                        }}
 | 
			
		||||
                        leftSection={<IconHammer size={14} />}
 | 
			
		||||
                      >
 | 
			
		||||
| 
						 | 
				
			
			@ -405,7 +370,7 @@ export default function Bids() {
 | 
			
		|||
      <ShowHistoriesModal
 | 
			
		||||
        opened={openedHistories}
 | 
			
		||||
        onClose={() => {
 | 
			
		||||
          historiesModel.close();
 | 
			
		||||
          historiesModal.close();
 | 
			
		||||
          setClickData(null);
 | 
			
		||||
        }}
 | 
			
		||||
        data={clickData}
 | 
			
		||||
| 
						 | 
				
			
			@ -426,28 +391,9 @@ export default function Bids() {
 | 
			
		|||
        }}
 | 
			
		||||
        data={clickData}
 | 
			
		||||
      />
 | 
			
		||||
      {/* Grays */}
 | 
			
		||||
      {openedHistoriesGraysApi && (
 | 
			
		||||
        <ShowHistoriesBidGraysApiModal
 | 
			
		||||
          onUpdated={() => {
 | 
			
		||||
            if (refTableFn.current?.fetchData) {
 | 
			
		||||
              refTableFn.current.fetchData();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            setClickData(null);
 | 
			
		||||
          }}
 | 
			
		||||
          opened={openedHistoriesGraysApi}
 | 
			
		||||
          onClose={() => {
 | 
			
		||||
            historiesGraysApiModel.close();
 | 
			
		||||
 | 
			
		||||
            setClickData(null);
 | 
			
		||||
          }}
 | 
			
		||||
          data={clickData}
 | 
			
		||||
        />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {openedHistoriesPicklesApi && (
 | 
			
		||||
        <ShowHistoriesBidPicklesApiModal
 | 
			
		||||
      {openedHistoriesView && (
 | 
			
		||||
        <ShowHistoriesApiModal
 | 
			
		||||
          onUpdated={() => {
 | 
			
		||||
            if (refTableFn.current?.fetchData) {
 | 
			
		||||
              refTableFn.current.fetchData();
 | 
			
		||||
| 
						 | 
				
			
			@ -457,7 +403,7 @@ export default function Bids() {
 | 
			
		|||
          }}
 | 
			
		||||
          opened={true}
 | 
			
		||||
          onClose={() => {
 | 
			
		||||
            historiesPicklesApiModel.close();
 | 
			
		||||
            openedHistoriesViewModal.close();
 | 
			
		||||
            setClickData(null);
 | 
			
		||||
          }}
 | 
			
		||||
          data={clickData}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
{"createdAt":1747812172479}
 | 
			
		||||
{"createdAt":1747970028717}
 | 
			
		||||
| 
						 | 
				
			
			@ -26,7 +26,6 @@
 | 
			
		|||
        "axios": "^1.8.3",
 | 
			
		||||
        "bcrypt": "^5.1.1",
 | 
			
		||||
        "bull": "^4.16.5",
 | 
			
		||||
        "cheerio": "^1.0.0",
 | 
			
		||||
        "class-transformer": "^0.5.1",
 | 
			
		||||
        "class-validator": "^0.14.1",
 | 
			
		||||
        "cookie": "^1.0.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -4661,7 +4660,8 @@
 | 
			
		|||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
 | 
			
		||||
      "license": "ISC"
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/brace-expansion": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
| 
						 | 
				
			
			@ -4968,36 +4968,12 @@
 | 
			
		|||
      "dev": true,
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cheerio": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "cheerio-select": "^2.1.0",
 | 
			
		||||
        "dom-serializer": "^2.0.0",
 | 
			
		||||
        "domhandler": "^5.0.3",
 | 
			
		||||
        "domutils": "^3.1.0",
 | 
			
		||||
        "encoding-sniffer": "^0.2.0",
 | 
			
		||||
        "htmlparser2": "^9.1.0",
 | 
			
		||||
        "parse5": "^7.1.2",
 | 
			
		||||
        "parse5-htmlparser2-tree-adapter": "^7.0.0",
 | 
			
		||||
        "parse5-parser-stream": "^7.1.2",
 | 
			
		||||
        "undici": "^6.19.5",
 | 
			
		||||
        "whatwg-mimetype": "^4.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18.17"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/cheerio-select": {
 | 
			
		||||
      "version": "2.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "boolbase": "^1.0.0",
 | 
			
		||||
        "css-select": "^5.1.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -5582,6 +5558,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "boolbase": "^1.0.0",
 | 
			
		||||
        "css-what": "^6.1.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -5598,6 +5575,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 6"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -5873,6 +5851,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "domelementtype": "^2.3.0",
 | 
			
		||||
        "domhandler": "^5.0.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -5892,13 +5871,15 @@
 | 
			
		|||
          "url": "https://github.com/sponsors/fb55"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "license": "BSD-2-Clause"
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/domhandler": {
 | 
			
		||||
      "version": "5.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "domelementtype": "^2.3.0"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -5914,6 +5895,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
 | 
			
		||||
      "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "dom-serializer": "^2.0.0",
 | 
			
		||||
        "domelementtype": "^2.3.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -6100,31 +6082,6 @@
 | 
			
		|||
        "node": ">=8.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/encoding-sniffer": {
 | 
			
		||||
      "version": "0.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "iconv-lite": "^0.6.3",
 | 
			
		||||
        "whatwg-encoding": "^3.1.1"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/encoding-sniffer/node_modules/iconv-lite": {
 | 
			
		||||
      "version": "0.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/encoding/node_modules/iconv-lite": {
 | 
			
		||||
      "version": "0.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -6211,6 +6168,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
 | 
			
		||||
      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.12"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -7844,6 +7802,7 @@
 | 
			
		|||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "domelementtype": "^2.3.0",
 | 
			
		||||
        "domhandler": "^5.0.3",
 | 
			
		||||
| 
						 | 
				
			
			@ -10921,6 +10880,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "boolbase": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -11224,6 +11184,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
 | 
			
		||||
      "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "entities": "^6.0.0"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -11236,6 +11197,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "domhandler": "^5.0.3",
 | 
			
		||||
        "parse5": "^7.0.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -11244,23 +11206,12 @@
 | 
			
		|||
        "url": "https://github.com/inikulin/parse5?sponsor=1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/parse5-parser-stream": {
 | 
			
		||||
      "version": "7.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "parse5": "^7.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "url": "https://github.com/inikulin/parse5?sponsor=1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/parse5/node_modules/entities": {
 | 
			
		||||
      "version": "6.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
 | 
			
		||||
      "license": "BSD-2-Clause",
 | 
			
		||||
      "optional": true,
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.12"
 | 
			
		||||
      },
 | 
			
		||||
| 
						 | 
				
			
			@ -14245,15 +14196,6 @@
 | 
			
		|||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/undici": {
 | 
			
		||||
      "version": "6.21.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
 | 
			
		||||
      "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18.17"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/undici-types": {
 | 
			
		||||
      "version": "6.19.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -14726,39 +14668,6 @@
 | 
			
		|||
        "url": "https://opencollective.com/webpack"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/whatwg-encoding": {
 | 
			
		||||
      "version": "3.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "iconv-lite": "0.6.3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/whatwg-encoding/node_modules/iconv-lite": {
 | 
			
		||||
      "version": "0.6.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
 | 
			
		||||
      "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "safer-buffer": ">= 2.1.2 < 3.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=0.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/whatwg-mimetype": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=18"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/whatwg-url": {
 | 
			
		||||
      "version": "5.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,6 @@
 | 
			
		|||
    "axios": "^1.8.3",
 | 
			
		||||
    "bcrypt": "^5.1.1",
 | 
			
		||||
    "bull": "^4.16.5",
 | 
			
		||||
    "cheerio": "^1.0.0",
 | 
			
		||||
    "class-transformer": "^0.5.1",
 | 
			
		||||
    "class-validator": "^0.14.1",
 | 
			
		||||
    "cookie": "^1.0.2",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,22 +5,17 @@ import { Bid } from '../entities/bid.entity';
 | 
			
		|||
import { BidsService } from '../services/bids.service';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class GraysApi {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  constructor(private readonly bidsService: BidsService){}
 | 
			
		||||
 | 
			
		||||
export class AllBidsApi {
 | 
			
		||||
  constructor(private readonly bidsService: BidsService) {}
 | 
			
		||||
 | 
			
		||||
  async getHistoriesBid(lot_id: Bid['lot_id']) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const bid= await this.bidsService.bidsRepo.findOne({where: {lot_id, }, relations: {web_bid: true}})
 | 
			
		||||
 | 
			
		||||
    const bid = await this.bidsService.bidsRepo.findOne({
 | 
			
		||||
      where: { lot_id },
 | 
			
		||||
      relations: { web_bid: true },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
 | 
			
		||||
      switch(bid.web_bid.origin_url){
 | 
			
		||||
 | 
			
		||||
      switch (bid.web_bid.origin_url) {
 | 
			
		||||
        // GRAYS
 | 
			
		||||
        case 'https://www.grays.com': {
 | 
			
		||||
          const response = await axios({
 | 
			
		||||
| 
						 | 
				
			
			@ -31,12 +26,11 @@ export class GraysApi {
 | 
			
		|||
            return AppResponse.toResponse(response.data.Bids);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return AppResponse.toResponse([])
 | 
			
		||||
          return AppResponse.toResponse([]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // PICKLES
 | 
			
		||||
        case 'https://www.pickles.com.au': {
 | 
			
		||||
          
 | 
			
		||||
          const response = await axios({
 | 
			
		||||
            url: `https://www.pickles.com.au/PWR-Web/services/api/bidHistoryService/bidHistory?item=${lot_id}`,
 | 
			
		||||
          });
 | 
			
		||||
| 
						 | 
				
			
			@ -45,13 +39,11 @@ export class GraysApi {
 | 
			
		|||
            return AppResponse.toResponse(response.data.Bids);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return AppResponse.toResponse([])
 | 
			
		||||
          return AppResponse.toResponse([]);
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
          return AppResponse.toResponse([])
 | 
			
		||||
          return AppResponse.toResponse([]);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      return AppResponse.toResponse([]);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
import AppResponse from 'src/response/app-response';
 | 
			
		||||
import { Bid } from '../entities/bid.entity';
 | 
			
		||||
import { BidsService } from '../services/bids.service';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class AuctionHistoresApi {
 | 
			
		||||
  constructor(private readonly bidsService: BidsService) {}
 | 
			
		||||
 | 
			
		||||
  async getHistoriesBid(lot_id: Bid['lot_id']) {
 | 
			
		||||
    const bid = await this.bidsService.bidsRepo.findOne({
 | 
			
		||||
      where: { lot_id },
 | 
			
		||||
      relations: { web_bid: true, metadata: true },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      switch (bid.web_bid.origin_url) {
 | 
			
		||||
        // GRAYS
 | 
			
		||||
        case 'https://www.grays.com': {
 | 
			
		||||
          const response = await axios({
 | 
			
		||||
            url: `https://www.grays.com/api/LotInfo/GetBiddingHistory?lotId=${lot_id}¤cyCode=AUD`,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          if (response.data && response.data?.Bids) {
 | 
			
		||||
            return AppResponse.toResponse(response.data.Bids);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return AppResponse.toResponse([]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // PICKLES
 | 
			
		||||
        case 'https://www.pickles.com.au': {
 | 
			
		||||
          const response = await axios({
 | 
			
		||||
            url: `https://www.pickles.com.au/PWR-Web/services/api/bidHistoryService/bidHistory?item=${lot_id}`,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          if (response.data) {
 | 
			
		||||
            return AppResponse.toResponse(response.data.Bids);
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return AppResponse.toResponse([]);
 | 
			
		||||
        }
 | 
			
		||||
        // ALLBIDS
 | 
			
		||||
        case 'https://www.allbids.com.au': {
 | 
			
		||||
          const data = bid.metadata.find(
 | 
			
		||||
            (meta) => meta.key_name === 'competor_histories',
 | 
			
		||||
          )?.value;
 | 
			
		||||
 | 
			
		||||
          const sorted = _.orderBy(data, ['amount'], ['desc']);
 | 
			
		||||
 | 
			
		||||
          return AppResponse.toResponse(sorted || []);
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
          return AppResponse.toResponse([]);
 | 
			
		||||
      }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      return AppResponse.toResponse([]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -3,7 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
 | 
			
		|||
import { AdminsModule } from '../admins/admins.module';
 | 
			
		||||
import { NotificationModule } from '../notification/notification.module';
 | 
			
		||||
import { BotTelegramApi } from './apis/bot-telegram.api';
 | 
			
		||||
import { GraysApi } from './apis/grays.api';
 | 
			
		||||
import { AuctionHistoresApi } from './apis/auction-histories.api';
 | 
			
		||||
import { AdminBidHistoriesController } from './controllers/admin/admin-bid-histories.controller';
 | 
			
		||||
import { AdminBidsController } from './controllers/admin/admin-bids.controller';
 | 
			
		||||
import { AdminOutBidLogsController } from './controllers/admin/admin-out-bid-logs.controller';
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +31,8 @@ import { TasksService } from './services/tasks.servise';
 | 
			
		|||
import { ConfigsService } from './services/configs.service';
 | 
			
		||||
import { Config } from './entities/configs.entity';
 | 
			
		||||
import { AdminConfigsController } from './controllers/admin/admin-configs.controller';
 | 
			
		||||
import { BidMetadatasService } from './services/bid-metadatas.service';
 | 
			
		||||
import { BidMetadata } from './entities/bid-metadata.entity';
 | 
			
		||||
 | 
			
		||||
@Module({
 | 
			
		||||
  imports: [
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +43,7 @@ import { AdminConfigsController } from './controllers/admin/admin-configs.contro
 | 
			
		|||
      WebBid,
 | 
			
		||||
      SendMessageHistory,
 | 
			
		||||
      Config,
 | 
			
		||||
      BidMetadata,
 | 
			
		||||
    ]),
 | 
			
		||||
    // AuthModule,
 | 
			
		||||
    AdminsModule,
 | 
			
		||||
| 
						 | 
				
			
			@ -66,12 +69,13 @@ import { AdminConfigsController } from './controllers/admin/admin-configs.contro
 | 
			
		|||
    OutBidLogsService,
 | 
			
		||||
    WebBidsService,
 | 
			
		||||
    BotTelegramApi,
 | 
			
		||||
    GraysApi,
 | 
			
		||||
    AuctionHistoresApi,
 | 
			
		||||
    SendMessageHistoriesService,
 | 
			
		||||
    ImapService,
 | 
			
		||||
    DashboardService,
 | 
			
		||||
    TasksService,
 | 
			
		||||
    ConfigsService,
 | 
			
		||||
    BidMetadatasService,
 | 
			
		||||
  ],
 | 
			
		||||
  exports: [
 | 
			
		||||
    BotTelegramApi,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,13 +4,13 @@ import { CreateBidDto } from '../../dto/bid/create-bid.dto';
 | 
			
		|||
import { BidHistoriesService } from '../../services/bid-histories.service';
 | 
			
		||||
import { CreateBidHistoryDto } from '../../dto/bid-history/create-bid-history.dto';
 | 
			
		||||
import { Bid } from '../../entities/bid.entity';
 | 
			
		||||
import { GraysApi } from '../../apis/grays.api';
 | 
			
		||||
import { AuctionHistoresApi } from '../../apis/auction-histories.api';
 | 
			
		||||
 | 
			
		||||
@Controller('admin/bid-histories')
 | 
			
		||||
export class AdminBidHistoriesController {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly bidHistoriesService: BidHistoriesService,
 | 
			
		||||
    private readonly graysApi: GraysApi,
 | 
			
		||||
    private readonly auctionHistoresApi: AuctionHistoresApi,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  @Post()
 | 
			
		||||
| 
						 | 
				
			
			@ -20,6 +20,6 @@ export class AdminBidHistoriesController {
 | 
			
		|||
 | 
			
		||||
  @Get('detail/:lot_id')
 | 
			
		||||
  async getBidHistories(@Param('lot_id') lot_id: Bid['lot_id']) {
 | 
			
		||||
    return await this.graysApi.getHistoriesBid(lot_id);
 | 
			
		||||
    return await this.auctionHistoresApi.getHistoriesBid(lot_id);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
import { IsNumber, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
import { IsNumber, IsObject, IsOptional, IsString } from 'class-validator';
 | 
			
		||||
 | 
			
		||||
export class ClientUpdateBidDto {
 | 
			
		||||
  @IsString()
 | 
			
		||||
| 
						 | 
				
			
			@ -24,4 +24,8 @@ export class ClientUpdateBidDto {
 | 
			
		|||
  @IsNumber()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  reserve_price: number;
 | 
			
		||||
 | 
			
		||||
  @IsObject()
 | 
			
		||||
  @IsOptional()
 | 
			
		||||
  metadata: Record<string, any>;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
import {
 | 
			
		||||
  Column,
 | 
			
		||||
  Entity,
 | 
			
		||||
  ManyToOne,
 | 
			
		||||
  PrimaryGeneratedColumn,
 | 
			
		||||
  Unique,
 | 
			
		||||
} from 'typeorm';
 | 
			
		||||
import { Bid } from './bid.entity';
 | 
			
		||||
import { Timestamp } from './timestamp';
 | 
			
		||||
 | 
			
		||||
@Entity('bid_metadata')
 | 
			
		||||
@Unique(['key_name', 'bid'])
 | 
			
		||||
export class BidMetadata extends Timestamp {
 | 
			
		||||
  @PrimaryGeneratedColumn('increment')
 | 
			
		||||
  id: number;
 | 
			
		||||
 | 
			
		||||
  @Column()
 | 
			
		||||
  key_name: string;
 | 
			
		||||
 | 
			
		||||
  @Column({ type: 'json' })
 | 
			
		||||
  value: string;
 | 
			
		||||
 | 
			
		||||
  @ManyToOne(() => Bid, (bid) => bid.metadata, { onDelete: 'CASCADE' })
 | 
			
		||||
  bid: Bid;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ import { Timestamp } from './timestamp';
 | 
			
		|||
import { BidHistory } from './bid-history.entity';
 | 
			
		||||
import { WebBid } from './wed-bid.entity';
 | 
			
		||||
import { SendMessageHistory } from './send-message-histories.entity';
 | 
			
		||||
import { BidMetadata } from './bid-metadata.entity';
 | 
			
		||||
 | 
			
		||||
@Entity('bids')
 | 
			
		||||
export class Bid extends Timestamp {
 | 
			
		||||
| 
						 | 
				
			
			@ -69,4 +70,7 @@ export class Bid extends Timestamp {
 | 
			
		|||
 | 
			
		||||
  @ManyToOne(() => WebBid, (web) => web.children, { onDelete: 'CASCADE' })
 | 
			
		||||
  web_bid: WebBid;
 | 
			
		||||
 | 
			
		||||
  @OneToMany(() => BidMetadata, (metadata) => metadata.bid)
 | 
			
		||||
  metadata: BidMetadata[];
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
import { Injectable } from '@nestjs/common';
 | 
			
		||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
			
		||||
import { Repository } from 'typeorm';
 | 
			
		||||
import { BidMetadata } from '../entities/bid-metadata.entity';
 | 
			
		||||
import { Bid } from '../entities/bid.entity';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class BidMetadatasService {
 | 
			
		||||
  constructor(
 | 
			
		||||
    @InjectRepository(BidMetadata)
 | 
			
		||||
    readonly bidMetadataRepo: Repository<BidMetadata>,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  async upsert(data: Record<string, any>, bid: Bid) {
 | 
			
		||||
    const existingMetadata = await this.bidMetadataRepo.find({
 | 
			
		||||
      where: { bid: { id: bid.id } },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const existingMap = new Map(
 | 
			
		||||
      existingMetadata.map((item) => [item.key_name, item]),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const toSave: BidMetadata[] = [];
 | 
			
		||||
 | 
			
		||||
    for (const [key, value] of Object.entries(data)) {
 | 
			
		||||
      const existing = existingMap.get(key);
 | 
			
		||||
      if (existing) {
 | 
			
		||||
        existing.value = value;
 | 
			
		||||
        toSave.push(existing);
 | 
			
		||||
      } else {
 | 
			
		||||
        toSave.push(
 | 
			
		||||
          this.bidMetadataRepo.create({
 | 
			
		||||
            key_name: key,
 | 
			
		||||
            value,
 | 
			
		||||
            bid: { id: bid.id },
 | 
			
		||||
          }),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await this.bidMetadataRepo.save(toSave);
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -32,6 +32,7 @@ import { ImageCompressionPipe } from '../pipes/image-compression-pipe';
 | 
			
		|||
import { Constant } from '../utils/constant';
 | 
			
		||||
import { Event } from '../utils/events';
 | 
			
		||||
import { WebBidsService } from './web-bids.service';
 | 
			
		||||
import { BidMetadatasService } from './bid-metadatas.service';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
export class BidsService {
 | 
			
		||||
| 
						 | 
				
			
			@ -41,8 +42,9 @@ export class BidsService {
 | 
			
		|||
    @InjectRepository(BidHistory)
 | 
			
		||||
    readonly bidHistoriesRepo: Repository<BidHistory>,
 | 
			
		||||
    private readonly webBidsService: WebBidsService,
 | 
			
		||||
    private eventEmitter: EventEmitter2,
 | 
			
		||||
    private notificationService: NotificationService,
 | 
			
		||||
    private readonly eventEmitter: EventEmitter2,
 | 
			
		||||
    private readonly notificationService: NotificationService,
 | 
			
		||||
    private readonly bidMetadatasService: BidMetadatasService,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
  async index(query: PaginateQuery) {
 | 
			
		||||
| 
						 | 
				
			
			@ -208,12 +210,12 @@ export class BidsService {
 | 
			
		|||
 | 
			
		||||
  async clientUpdate(
 | 
			
		||||
    id: Bid['id'],
 | 
			
		||||
    { close_time, model, ...data }: ClientUpdateBidDto, // Nhận dữ liệu cập nhật
 | 
			
		||||
    { close_time, model, metadata, ...data }: ClientUpdateBidDto, // Nhận dữ liệu cập nhật
 | 
			
		||||
  ) {
 | 
			
		||||
    // Tìm kiếm phiên đấu giá trong database theo id
 | 
			
		||||
    const bid = await this.bidsRepo.findOne({
 | 
			
		||||
      where: { id },
 | 
			
		||||
      relations: { histories: true, web_bid: true },
 | 
			
		||||
      relations: { histories: true, web_bid: true, metadata: true },
 | 
			
		||||
      order: {
 | 
			
		||||
        histories: {
 | 
			
		||||
          price: 'DESC',
 | 
			
		||||
| 
						 | 
				
			
			@ -288,6 +290,10 @@ export class BidsService {
 | 
			
		|||
      updated_at: new Date(), // Cập nhật timestamp
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (metadata) {
 | 
			
		||||
      await this.bidMetadatasService.upsert(metadata, bid);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Phát sự kiện cập nhật toàn bộ danh sách đấu giá
 | 
			
		||||
    this.emitAllBidEvent();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,7 @@ import { Queue } from 'bull';
 | 
			
		|||
export class MailsService {
 | 
			
		||||
  constructor(
 | 
			
		||||
    private readonly mailerService: MailerService,
 | 
			
		||||
 | 
			
		||||
    @InjectQueue('mail-queue') private mailQueue: Queue,
 | 
			
		||||
  ) {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +53,8 @@ export class MailsService {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  generateProductTableHTML(products: ScrapItem[]): string {
 | 
			
		||||
    const from = process.env.MAIL_USER || 'no-reply@example.com';
 | 
			
		||||
 | 
			
		||||
    if (!products.length) {
 | 
			
		||||
      return `
 | 
			
		||||
    <!DOCTYPE html>
 | 
			
		||||
| 
						 | 
				
			
			@ -64,6 +67,7 @@ export class MailsService {
 | 
			
		|||
    <body style="font-family: sans-serif; background: #f8f9fa; padding: 20px;">
 | 
			
		||||
      <h2 style="text-align: center; color: #333;">Product Listing</h2>
 | 
			
		||||
      <p style="text-align: center; color: #666;">No matching products found for your keywords today.</p>
 | 
			
		||||
      <p style="text-align: center; color: #999; font-size: 12px; margin-top: 40px;">From: ${from}</p>
 | 
			
		||||
    </body>
 | 
			
		||||
    </html>
 | 
			
		||||
    `;
 | 
			
		||||
| 
						 | 
				
			
			@ -109,6 +113,7 @@ export class MailsService {
 | 
			
		|||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
    <p style="text-align: center; color: #999; font-size: 12px; margin-top: 40px;">From: ${from}</p>
 | 
			
		||||
  </body>
 | 
			
		||||
  </html>
 | 
			
		||||
  `;
 | 
			
		||||
| 
						 | 
				
			
			@ -122,6 +127,7 @@ export class MailsService {
 | 
			
		|||
    const max = `$${bid.max_price}`;
 | 
			
		||||
    const submitted = `$${bid.max_price}`;
 | 
			
		||||
    const nextBid = bid.max_price + bid.plus_price;
 | 
			
		||||
    const from = process.env.MAIL_USER || 'no-reply@example.com';
 | 
			
		||||
 | 
			
		||||
    const cardStyle = `
 | 
			
		||||
    max-width: 600px; 
 | 
			
		||||
| 
						 | 
				
			
			@ -146,15 +152,16 @@ export class MailsService {
 | 
			
		|||
    switch (bid.status) {
 | 
			
		||||
      case 'biding':
 | 
			
		||||
        return `
 | 
			
		||||
        <div style="${cardStyle}">
 | 
			
		||||
          <h2 style="${headerStyle('#2c7a7b')}">✅ Auto Bid Started</h2>
 | 
			
		||||
          ${renderRow('Title', title)}
 | 
			
		||||
          ${renderRow('Max', max)}
 | 
			
		||||
          ${renderRow('End time', endTime)}
 | 
			
		||||
          ${renderRow('Competitor', competitor)}
 | 
			
		||||
          ${renderRow('Bid submitted', submitted)}
 | 
			
		||||
        </div>
 | 
			
		||||
      `;
 | 
			
		||||
      <div style="${cardStyle}">
 | 
			
		||||
        <h2 style="${headerStyle('#2c7a7b')}">✅ Auto Bid Started</h2>
 | 
			
		||||
        ${renderRow('Title', title)}
 | 
			
		||||
        ${renderRow('Max', max)}
 | 
			
		||||
        ${renderRow('End time', endTime)}
 | 
			
		||||
        ${renderRow('Competitor', competitor)}
 | 
			
		||||
        ${renderRow('Bid submitted', submitted)}
 | 
			
		||||
        ${renderRow('From', from)}
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
      case 'out-bid': {
 | 
			
		||||
        const overLimit = bid.current_price >= nextBid;
 | 
			
		||||
| 
						 | 
				
			
			@ -163,59 +170,64 @@ export class MailsService {
 | 
			
		|||
 | 
			
		||||
        if (isTimeReached(bid.close_time)) {
 | 
			
		||||
          return `
 | 
			
		||||
          <div style="${cardStyle}">
 | 
			
		||||
            <h2 style="${headerStyle('#718096')}">⏳ Auction Ended</h2>
 | 
			
		||||
            ${renderRow('Title', title)}
 | 
			
		||||
            ${renderRow('End time', endTime)}
 | 
			
		||||
            ${renderRow('Final price', competitor)}
 | 
			
		||||
          </div>
 | 
			
		||||
        `;
 | 
			
		||||
        <div style="${cardStyle}">
 | 
			
		||||
          <h2 style="${headerStyle('#718096')}">⏳ Auction Ended</h2>
 | 
			
		||||
          ${renderRow('Title', title)}
 | 
			
		||||
          ${renderRow('End time', endTime)}
 | 
			
		||||
          ${renderRow('Final price', competitor)}
 | 
			
		||||
          ${renderRow('From', from)}
 | 
			
		||||
        </div>
 | 
			
		||||
      `;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (overLimit || belowReserve) {
 | 
			
		||||
          return `
 | 
			
		||||
          <div style="${cardStyle}">
 | 
			
		||||
            <h2 style="${headerStyle('#dd6b20')}">⚠️ Outbid (${timeExtended})</h2>
 | 
			
		||||
            ${renderRow('Title', title)}
 | 
			
		||||
            ${renderRow('Competitor', competitor)}
 | 
			
		||||
            ${renderRow('Max', max)}
 | 
			
		||||
            ${renderRow('Next bid at', `$${nextBid}`)}
 | 
			
		||||
            ${renderRow('End time', endTime)}
 | 
			
		||||
            <p style="color:#c05621; font-weight: 600;">⚠️ Current bid exceeds your max bid.</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        `;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return `
 | 
			
		||||
        <div style="${cardStyle}">
 | 
			
		||||
          <h2 style="${headerStyle('#e53e3e')}">🛑 Auction Canceled (${timeExtended})</h2>
 | 
			
		||||
          <h2 style="${headerStyle('#dd6b20')}">⚠️ Outbid (${timeExtended})</h2>
 | 
			
		||||
          ${renderRow('Title', title)}
 | 
			
		||||
          ${renderRow('Competitor', competitor)}
 | 
			
		||||
          ${renderRow('Max', max)}
 | 
			
		||||
          ${renderRow('Next bid at', `$${nextBid}`)}
 | 
			
		||||
          ${renderRow('End time', endTime)}
 | 
			
		||||
          <p style="color:#9b2c2c; font-weight: 600;">🛑 Auction has been canceled.</p>
 | 
			
		||||
          ${renderRow('From', from)}
 | 
			
		||||
          <p style="color:#c05621; font-weight: 600;">⚠️ Current bid exceeds your max bid.</p>
 | 
			
		||||
        </div>
 | 
			
		||||
      `;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return `
 | 
			
		||||
      <div style="${cardStyle}">
 | 
			
		||||
        <h2 style="${headerStyle('#e53e3e')}">🛑 Auction Canceled (${timeExtended})</h2>
 | 
			
		||||
        ${renderRow('Title', title)}
 | 
			
		||||
        ${renderRow('Competitor', competitor)}
 | 
			
		||||
        ${renderRow('Max', max)}
 | 
			
		||||
        ${renderRow('Next bid at', `$${nextBid}`)}
 | 
			
		||||
        ${renderRow('End time', endTime)}
 | 
			
		||||
        ${renderRow('From', from)}
 | 
			
		||||
        <p style="color:#9b2c2c; font-weight: 600;">🛑 Auction has been canceled.</p>
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      case 'win-bid':
 | 
			
		||||
        return `
 | 
			
		||||
        <div style="${cardStyle}">
 | 
			
		||||
          <h2 style="${headerStyle('#2b6cb0')}">🎉 You Won!</h2>
 | 
			
		||||
          ${renderRow('Title', title)}
 | 
			
		||||
          ${renderRow('Price won', `$${bid.current_price}`)}
 | 
			
		||||
          ${renderRow('Max', max)}
 | 
			
		||||
        </div>
 | 
			
		||||
      `;
 | 
			
		||||
      <div style="${cardStyle}">
 | 
			
		||||
        <h2 style="${headerStyle('#2b6cb0')}">🎉 You Won!</h2>
 | 
			
		||||
        ${renderRow('Title', title)}
 | 
			
		||||
        ${renderRow('Price won', `$${bid.current_price}`)}
 | 
			
		||||
        ${renderRow('Max', max)}
 | 
			
		||||
        ${renderRow('From', from)}
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        return `
 | 
			
		||||
        <div style="${cardStyle}">
 | 
			
		||||
          <h2 style="${headerStyle('#718096')}">❓ Unknown Status</h2>
 | 
			
		||||
          ${renderRow('Title', title)}
 | 
			
		||||
        </div>
 | 
			
		||||
      `;
 | 
			
		||||
      <div style="${cardStyle}">
 | 
			
		||||
        <h2 style="${headerStyle('#718096')}">❓ Unknown Status</h2>
 | 
			
		||||
        ${renderRow('Title', title)}
 | 
			
		||||
        ${renderRow('From', from)}
 | 
			
		||||
      </div>
 | 
			
		||||
    `;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -227,6 +239,7 @@ export class MailsService {
 | 
			
		|||
    const max = `$${bid.max_price}`;
 | 
			
		||||
    const submitted = `$${bid.max_price}`;
 | 
			
		||||
    const maxReached = bid.max_price <= bid.max_price;
 | 
			
		||||
    const from = process.env.MAIL_USER || 'no-reply@example.com';
 | 
			
		||||
 | 
			
		||||
    return `
 | 
			
		||||
  <!DOCTYPE html>
 | 
			
		||||
| 
						 | 
				
			
			@ -300,6 +313,10 @@ export class MailsService {
 | 
			
		|||
            <th>End Time</th>
 | 
			
		||||
            <td>${endTime}</td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <th>From</th>
 | 
			
		||||
            <td>${from}</td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,7 +6,6 @@ import { Injectable, Logger } from '@nestjs/common';
 | 
			
		|||
import { Cron } from '@nestjs/schedule';
 | 
			
		||||
import * as moment from 'moment';
 | 
			
		||||
import { Between } from 'typeorm';
 | 
			
		||||
import { ScrapConfigsService } from './scrap-config.service';
 | 
			
		||||
import { ScrapItemsService } from './scrap-item-config.service';
 | 
			
		||||
 | 
			
		||||
@Injectable()
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +86,7 @@ export class TasksService {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Cron('0 2 * * *')
 | 
			
		||||
  @Cron('58 5 * * *')
 | 
			
		||||
  async handleScraps() {
 | 
			
		||||
    const processName = 'scrape-data-keyword';
 | 
			
		||||
    await this.runProcessAndSendReport(processName);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,6 +59,48 @@ export class AllbidsProductBid extends ProductBid {
 | 
			
		|||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async getHistoriesData() {
 | 
			
		||||
    if (!this.page_context) return;
 | 
			
		||||
    try {
 | 
			
		||||
      // Chờ cho Angular load (có thể tùy chỉnh thời gian nếu cần)
 | 
			
		||||
      await this.page_context.waitForFunction(
 | 
			
		||||
        () => window.angular !== undefined
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      const historiesData = await this.page_context.evaluate(() => {
 | 
			
		||||
        let data = null;
 | 
			
		||||
        const elements = document.querySelectorAll(".ng-scope");
 | 
			
		||||
 | 
			
		||||
        for (let i = 0; i < elements.length; i++) {
 | 
			
		||||
          try {
 | 
			
		||||
            const scope = angular.element(elements[i]).scope();
 | 
			
		||||
            if (scope?.bidHistory) {
 | 
			
		||||
              data = scope.bidHistory;
 | 
			
		||||
              break;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Thử lấy từ $parent nếu không thấy
 | 
			
		||||
            if (scope?.$parent?.bidHistory) {
 | 
			
		||||
              data = scope.$parent.bidHistory;
 | 
			
		||||
              break;
 | 
			
		||||
            }
 | 
			
		||||
          } catch (e) {
 | 
			
		||||
            // Angular element có thể lỗi nếu phần tử không hợp lệ
 | 
			
		||||
            continue;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return data;
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      return historiesData;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      console.log(
 | 
			
		||||
        `[${this.id}] Error in waitForApiResponse: ${error?.message}`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async handleUpdateBid({
 | 
			
		||||
    lot_id,
 | 
			
		||||
    close_time,
 | 
			
		||||
| 
						 | 
				
			
			@ -66,6 +108,7 @@ export class AllbidsProductBid extends ProductBid {
 | 
			
		|||
    current_price,
 | 
			
		||||
    reserve_price,
 | 
			
		||||
    model,
 | 
			
		||||
    metadata,
 | 
			
		||||
  }) {
 | 
			
		||||
    const response = await updateBid(this.id, {
 | 
			
		||||
      lot_id,
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +117,7 @@ export class AllbidsProductBid extends ProductBid {
 | 
			
		|||
      current_price,
 | 
			
		||||
      reserve_price: Number(reserve_price) || 0,
 | 
			
		||||
      model,
 | 
			
		||||
      metadata,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    if (response) {
 | 
			
		||||
| 
						 | 
				
			
			@ -122,6 +166,8 @@ export class AllbidsProductBid extends ProductBid {
 | 
			
		|||
    // 📌 Chờ phản hồi API từ trang, tối đa 10 giây
 | 
			
		||||
    const result = await this.waitForApiResponse();
 | 
			
		||||
 | 
			
		||||
    const historiesData = await this.getHistoriesData();
 | 
			
		||||
 | 
			
		||||
    // 📌 Nếu không có dữ liệu trả về thì dừng
 | 
			
		||||
    if (!result) {
 | 
			
		||||
      console.log(`⚠️ [${this.id}] No valid data received, skipping update.`);
 | 
			
		||||
| 
						 | 
				
			
			@ -140,6 +186,9 @@ export class AllbidsProductBid extends ProductBid {
 | 
			
		|||
          : null,
 | 
			
		||||
        // close_time: close_time && !this.close_time ? String(close_time) : null, // test
 | 
			
		||||
        name: result?.aucTitle || null,
 | 
			
		||||
        metadata: {
 | 
			
		||||
          competor_histories: historiesData,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      ["close_time"]
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -233,12 +282,10 @@ export class AllbidsProductBid extends ProductBid {
 | 
			
		|||
 | 
			
		||||
      const data = await this.submitBid();
 | 
			
		||||
 | 
			
		||||
      console.log({ data });
 | 
			
		||||
 | 
			
		||||
      await this.page_context.reload({ waitUntil: "networkidle0" });
 | 
			
		||||
 | 
			
		||||
      const { aucUserMaxBid } = await this.waitForApiResponse();
 | 
			
		||||
      console.log(`📡 [${this.id}] API Response received:`, lotData);
 | 
			
		||||
      console.log(`📡 [${this.id}] API Response received:`, { aucUserMaxBid });
 | 
			
		||||
 | 
			
		||||
      // 📌 Kiểm tra trạng thái đấu giá từ API
 | 
			
		||||
      if (aucUserMaxBid == this.max_price) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue