Deploy to staging #36
			
				
			
		
		
		
	| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
import { ActionIcon } from "@mantine/core";
 | 
			
		||||
import { IconTrash } from "@tabler/icons-react";
 | 
			
		||||
import { useChoosesStore } from "../../lib/zustand/use-chooses-store";
 | 
			
		||||
import { IBid } from "../../system/type";
 | 
			
		||||
 | 
			
		||||
export interface IDeleteRowActionProps {
 | 
			
		||||
  onClick?: () => void;
 | 
			
		||||
  data: IBid
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function DeleteRowAction({ data,onClick }: IDeleteRowActionProps) {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    const {chooses} = useChoosesStore()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <ActionIcon disabled={!chooses.some(item => item.id === data.id)} onClick={onClick} size={"sm"} color="red">
 | 
			
		||||
      <IconTrash size={14} />
 | 
			
		||||
    </ActionIcon>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,8 +6,16 @@ import { Socket } from "socket.io-client";
 | 
			
		|||
import { getImagesWorking } from "../../apis/bid";
 | 
			
		||||
import { useStatusToolStore } from "../../lib/zustand/use-status-tool-store";
 | 
			
		||||
import { IBid, IWebBid } from "../../system/type";
 | 
			
		||||
import { cn, extractDomainSmart, findNearestClosingChild, isTimeReached, stringToColor, subtractSeconds } from "../../utils";
 | 
			
		||||
import {
 | 
			
		||||
  cn,
 | 
			
		||||
  extractDomainSmart,
 | 
			
		||||
  findNearestClosingChild,
 | 
			
		||||
  isTimeReached,
 | 
			
		||||
  stringToColor,
 | 
			
		||||
  subtractSeconds,
 | 
			
		||||
} from "../../utils";
 | 
			
		||||
import ShowImageModal from "./show-image-modal";
 | 
			
		||||
import { IconExternalLink, IconImageInPicture } from "@tabler/icons-react";
 | 
			
		||||
export interface IWorkingPageProps {
 | 
			
		||||
  data: (IBid | IWebBid) & { type: string };
 | 
			
		||||
  socket: Socket;
 | 
			
		||||
| 
						 | 
				
			
			@ -110,9 +118,8 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  if(!isIBid(data)){
 | 
			
		||||
    console.log(data)
 | 
			
		||||
  if (!isIBid(data)) {
 | 
			
		||||
    console.log(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
| 
						 | 
				
			
			@ -144,20 +151,24 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
            <Text className="text-xs tracking-wide">{`Current price: $${data.current_price}`}</Text>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          
 | 
			
		||||
 | 
			
		||||
          <Text className="text-sm italic opacity-80">
 | 
			
		||||
            {moment(lastUpdate).format("HH:mm:ss DD/MM/YYYY")}
 | 
			
		||||
          </Text>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
          {!isIBid(data) && <Tooltip label={'Time to tracking'}><Text>{`TT: ${moment(subtractSeconds(findNearestClosingChild(data)?.close_time || '', data.early_tracking_seconds)).format(
 | 
			
		||||
                  "HH:mm:ss DD/MM/YYYY"
 | 
			
		||||
                )}`}</Text></Tooltip>}
 | 
			
		||||
          {!isIBid(data) && (
 | 
			
		||||
            <Tooltip label={"Time to tracking"}>
 | 
			
		||||
              <Text>{`TT: ${moment(
 | 
			
		||||
                subtractSeconds(
 | 
			
		||||
                  findNearestClosingChild(data)?.close_time || "",
 | 
			
		||||
                  data.early_tracking_seconds
 | 
			
		||||
                )
 | 
			
		||||
              ).format("HH:mm:ss DD/MM/YYYY")}`}</Text>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
          )}
 | 
			
		||||
 | 
			
		||||
          <Box className="flex items-center gap-3">
 | 
			
		||||
            {isIBid(data) && (
 | 
			
		||||
              <Tooltip label={'Close time'}>
 | 
			
		||||
              <Tooltip label={"Close time"}>
 | 
			
		||||
                <Text
 | 
			
		||||
                  style={{ fontSize: "12px" }}
 | 
			
		||||
                  className="tracking-wide"
 | 
			
		||||
| 
						 | 
				
			
			@ -174,7 +185,7 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
                  data.web_bid?.early_tracking_seconds || 0
 | 
			
		||||
                )
 | 
			
		||||
              ) && (
 | 
			
		||||
                <Tooltip label={'Time to tracking'}>
 | 
			
		||||
                <Tooltip label={"Time to tracking"}>
 | 
			
		||||
                  <Text
 | 
			
		||||
                    style={{ fontSize: "12px" }}
 | 
			
		||||
                    className="tracking-wide"
 | 
			
		||||
| 
						 | 
				
			
			@ -189,6 +200,7 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
          </Box>
 | 
			
		||||
          <Box className="flex items-center gap-4">
 | 
			
		||||
            <Button
 | 
			
		||||
             rightSection={<IconImageInPicture size={14}/>}
 | 
			
		||||
              size="xs"
 | 
			
		||||
              color="green"
 | 
			
		||||
              onClick={open}
 | 
			
		||||
| 
						 | 
				
			
			@ -197,6 +209,7 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
              Show
 | 
			
		||||
            </Button>
 | 
			
		||||
            <Button
 | 
			
		||||
              rightSection={<IconExternalLink size={14} />}
 | 
			
		||||
              target="_blank"
 | 
			
		||||
              component="a"
 | 
			
		||||
              size="xs"
 | 
			
		||||
| 
						 | 
				
			
			@ -218,11 +231,15 @@ export default function WorkingPage({ data, socket }: IWorkingPageProps) {
 | 
			
		|||
 | 
			
		||||
          <Badge
 | 
			
		||||
            color={stringToColor(
 | 
			
		||||
              isIBid(data) ? extractDomainSmart(data.web_bid.origin_url) : extractDomainSmart(data.origin_url)
 | 
			
		||||
              isIBid(data)
 | 
			
		||||
                ? extractDomainSmart(data.web_bid.origin_url)
 | 
			
		||||
                : extractDomainSmart(data.origin_url)
 | 
			
		||||
            )}
 | 
			
		||||
            size="xs"
 | 
			
		||||
          >
 | 
			
		||||
            {isIBid(data) ? extractDomainSmart(data.web_bid.origin_url) : extractDomainSmart(data.origin_url)}
 | 
			
		||||
            {isIBid(data)
 | 
			
		||||
              ? extractDomainSmart(data.web_bid.origin_url)
 | 
			
		||||
              : extractDomainSmart(data.origin_url)}
 | 
			
		||||
          </Badge>
 | 
			
		||||
        </Box>
 | 
			
		||||
      </Box>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,222 +1,278 @@
 | 
			
		|||
/* eslint-disable @typescript-eslint/no-unused-vars */
 | 
			
		||||
import { Box, Button, ComboboxItem, Dialog, Select, SelectProps, Text, TextInput, TextInputProps } from '@mantine/core';
 | 
			
		||||
import { useForm } from '@mantine/form';
 | 
			
		||||
import { useDisclosure } from '@mantine/hooks';
 | 
			
		||||
import { IconSearch, IconX } from '@tabler/icons-react';
 | 
			
		||||
import { ReactNode, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react';
 | 
			
		||||
import { IActionData, ITableFilter, TRefTableActionFn } from './type';
 | 
			
		||||
import { searchKey } from './ultils';
 | 
			
		||||
import {
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  ComboboxItem,
 | 
			
		||||
  Dialog,
 | 
			
		||||
  Select,
 | 
			
		||||
  SelectProps,
 | 
			
		||||
  Text,
 | 
			
		||||
  TextInput,
 | 
			
		||||
  TextInputProps,
 | 
			
		||||
} from "@mantine/core";
 | 
			
		||||
import { useForm } from "@mantine/form";
 | 
			
		||||
import { useDisclosure } from "@mantine/hooks";
 | 
			
		||||
import { IconSearch, IconX } from "@tabler/icons-react";
 | 
			
		||||
import {
 | 
			
		||||
  ReactNode,
 | 
			
		||||
  useCallback,
 | 
			
		||||
  useEffect,
 | 
			
		||||
  useImperativeHandle,
 | 
			
		||||
  useMemo,
 | 
			
		||||
  useState,
 | 
			
		||||
} from "react";
 | 
			
		||||
import { IActionData, ITableFilter, TRefTableActionFn } from "./type";
 | 
			
		||||
import { searchKey } from "./ultils";
 | 
			
		||||
 | 
			
		||||
export interface ITableActionsProps<R extends Record<string, string | number>> {
 | 
			
		||||
    actions?: IActionData[];
 | 
			
		||||
    chooses?: R[];
 | 
			
		||||
    showSearch?: boolean;
 | 
			
		||||
    loading?: boolean;
 | 
			
		||||
    showAction?: boolean;
 | 
			
		||||
    initFilter?: ITableFilter<R>[];
 | 
			
		||||
    searchOptions?: {
 | 
			
		||||
        props?: TextInputProps;
 | 
			
		||||
        render?: () => ReactNode;
 | 
			
		||||
    };
 | 
			
		||||
    refAction?: TRefTableActionFn;
 | 
			
		||||
    selectProps?: SelectProps;
 | 
			
		||||
    onSearch?: (data: ITableFilter<R>[]) => void;
 | 
			
		||||
    renderComfirm?: (data: IActionData) => ReactNode;
 | 
			
		||||
    onCloseComfirm?: () => void;
 | 
			
		||||
  actions?: IActionData[];
 | 
			
		||||
  chooses?: R[];
 | 
			
		||||
  showSearch?: boolean;
 | 
			
		||||
  loading?: boolean;
 | 
			
		||||
  showAction?: boolean;
 | 
			
		||||
  initFilter?: ITableFilter<R>[];
 | 
			
		||||
  searchOptions?: {
 | 
			
		||||
    props?: TextInputProps;
 | 
			
		||||
    render?: () => ReactNode;
 | 
			
		||||
  };
 | 
			
		||||
  refAction?: TRefTableActionFn;
 | 
			
		||||
  selectProps?: SelectProps;
 | 
			
		||||
  leftActionSession?: ReactNode;
 | 
			
		||||
  onSearch?: (data: ITableFilter<R>[]) => void;
 | 
			
		||||
  renderComfirm?: (data: IActionData) => ReactNode;
 | 
			
		||||
  onCloseComfirm?: () => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function TableActions<R extends Record<string, string | number>>({
 | 
			
		||||
    showSearch = true,
 | 
			
		||||
    showAction = true,
 | 
			
		||||
    loading,
 | 
			
		||||
    searchOptions,
 | 
			
		||||
    initFilter,
 | 
			
		||||
    selectProps,
 | 
			
		||||
    actions,
 | 
			
		||||
    chooses,
 | 
			
		||||
    refAction,
 | 
			
		||||
    onSearch,
 | 
			
		||||
    renderComfirm,
 | 
			
		||||
    onCloseComfirm,
 | 
			
		||||
export default function TableActions<
 | 
			
		||||
  R extends Record<string, string | number>
 | 
			
		||||
>({
 | 
			
		||||
  showSearch = true,
 | 
			
		||||
  showAction = true,
 | 
			
		||||
  loading,
 | 
			
		||||
  searchOptions,
 | 
			
		||||
  initFilter,
 | 
			
		||||
  selectProps,
 | 
			
		||||
  actions,
 | 
			
		||||
  chooses,
 | 
			
		||||
  refAction,
 | 
			
		||||
  onSearch,
 | 
			
		||||
  renderComfirm,
 | 
			
		||||
  leftActionSession,
 | 
			
		||||
  onCloseComfirm,
 | 
			
		||||
}: ITableActionsProps<R>) {
 | 
			
		||||
    const [opened, { toggle, close }] = useDisclosure(false);
 | 
			
		||||
  const [opened, { toggle, close }] = useDisclosure(false);
 | 
			
		||||
 | 
			
		||||
    const [action, setAction] = useState<IActionData | null>(null);
 | 
			
		||||
  const [action, setAction] = useState<IActionData | null>(null);
 | 
			
		||||
 | 
			
		||||
    const [isLoading, setIsLoading] = useState(loading || false);
 | 
			
		||||
  const [isLoading, setIsLoading] = useState(loading || false);
 | 
			
		||||
 | 
			
		||||
    const [selectValue, setSelectValue] = useState<string | null>(null);
 | 
			
		||||
  const [selectValue, setSelectValue] = useState<string | null>(null);
 | 
			
		||||
 | 
			
		||||
    const form = useForm<{ [searchKey]: string }>({
 | 
			
		||||
        initialValues: {
 | 
			
		||||
            [searchKey]: '',
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
  const form = useForm<{ [searchKey]: string }>({
 | 
			
		||||
    initialValues: {
 | 
			
		||||
      [searchKey]: "",
 | 
			
		||||
    },
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
    const handleSubmit = (data: { [searchKey]: string }) => {
 | 
			
		||||
        const filter = { type: data[searchKey].trim(), key: searchKey } as ITableFilter<R>;
 | 
			
		||||
  const handleSubmit = (data: { [searchKey]: string }) => {
 | 
			
		||||
    const filter = {
 | 
			
		||||
      type: data[searchKey].trim(),
 | 
			
		||||
      key: searchKey,
 | 
			
		||||
    } as ITableFilter<R>;
 | 
			
		||||
 | 
			
		||||
        if (onSearch) {
 | 
			
		||||
            onSearch(data[searchKey]?.length ? [filter] : []);
 | 
			
		||||
    if (onSearch) {
 | 
			
		||||
      onSearch(data[searchKey]?.length ? [filter] : []);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    form.reset();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleClear = () => {
 | 
			
		||||
    form.reset();
 | 
			
		||||
 | 
			
		||||
    if (onSearch) {
 | 
			
		||||
      onSearch([]);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleChangeAction = (value: string | null, _option: ComboboxItem) => {
 | 
			
		||||
    setSelectValue(value);
 | 
			
		||||
 | 
			
		||||
    if (!actions) return;
 | 
			
		||||
 | 
			
		||||
    const action = actions.find((action) => String(action.key) === value);
 | 
			
		||||
 | 
			
		||||
    if (!action) return;
 | 
			
		||||
 | 
			
		||||
    setAction(action);
 | 
			
		||||
 | 
			
		||||
    if (action.comfirmAction) {
 | 
			
		||||
      toggle();
 | 
			
		||||
    } else {
 | 
			
		||||
      handleCallBack(action.callback);
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleCallBack = useCallback(
 | 
			
		||||
    async (callback: (chooses: R[]) => void) => {
 | 
			
		||||
      if (callback.constructor.name === "AsyncFunction") {
 | 
			
		||||
        try {
 | 
			
		||||
          setIsLoading(true);
 | 
			
		||||
          await callback((chooses || []) as R[]);
 | 
			
		||||
        } finally {
 | 
			
		||||
          setIsLoading(false);
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        callback((chooses || []) as R[]);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
        form.reset();
 | 
			
		||||
    };
 | 
			
		||||
      handleClose();
 | 
			
		||||
    },
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
    [chooses]
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    const handleClear = () => {
 | 
			
		||||
        form.reset();
 | 
			
		||||
  const actionDataMemo = useMemo(() => {
 | 
			
		||||
    const newActions = actions?.reduce((prev, cur) => {
 | 
			
		||||
      prev.push({
 | 
			
		||||
        value: String(cur.key),
 | 
			
		||||
        label: cur.title,
 | 
			
		||||
        disabled: cur?.disabled ? cur.disabled(chooses || []) : false,
 | 
			
		||||
      });
 | 
			
		||||
      return prev;
 | 
			
		||||
    }, [] as { value: string; label: string; disabled: boolean }[]);
 | 
			
		||||
 | 
			
		||||
        if (onSearch) {
 | 
			
		||||
            onSearch([]);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return newActions;
 | 
			
		||||
  }, [actions, chooses]);
 | 
			
		||||
 | 
			
		||||
    const handleChangeAction = (value: string | null, _option: ComboboxItem) => {
 | 
			
		||||
        setSelectValue(value);
 | 
			
		||||
  const handleClose = () => {
 | 
			
		||||
    close();
 | 
			
		||||
    setAction(null);
 | 
			
		||||
    setSelectValue(null);
 | 
			
		||||
 | 
			
		||||
        if (!actions) return;
 | 
			
		||||
    if (onCloseComfirm) {
 | 
			
		||||
      onCloseComfirm();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
        const action = actions.find((action) => String(action.key) === value);
 | 
			
		||||
  const handleClearAction = () => {
 | 
			
		||||
    setAction(null);
 | 
			
		||||
    setSelectValue(null);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
        if (!action) return;
 | 
			
		||||
  const comfirmViewMemo = useMemo(() => {
 | 
			
		||||
    if (!action || !action.comfirmAction) return;
 | 
			
		||||
 | 
			
		||||
        setAction(action);
 | 
			
		||||
 | 
			
		||||
        if (action.comfirmAction) {
 | 
			
		||||
            toggle();
 | 
			
		||||
        } else {
 | 
			
		||||
            handleCallBack(action.callback);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleCallBack = useCallback(
 | 
			
		||||
        async (callback: (chooses: R[]) => void) => {
 | 
			
		||||
            if (callback.constructor.name === 'AsyncFunction') {
 | 
			
		||||
                try {
 | 
			
		||||
                    setIsLoading(true);
 | 
			
		||||
                    await callback((chooses || []) as R[]);
 | 
			
		||||
                } finally {
 | 
			
		||||
                    setIsLoading(false);
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                callback((chooses || []) as R[]);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            handleClose();
 | 
			
		||||
        },
 | 
			
		||||
        // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
        [chooses],
 | 
			
		||||
    return renderComfirm ? (
 | 
			
		||||
      renderComfirm(action)
 | 
			
		||||
    ) : (
 | 
			
		||||
      <Dialog
 | 
			
		||||
        opened={opened}
 | 
			
		||||
        withCloseButton
 | 
			
		||||
        onClose={handleClose}
 | 
			
		||||
        size="lg"
 | 
			
		||||
        radius="md"
 | 
			
		||||
      >
 | 
			
		||||
        <Text size="sm" mb="xs" fw={500}>
 | 
			
		||||
          {action?.comfirmOption && action?.comfirmOption(action)?.title
 | 
			
		||||
            ? action.comfirmOption(action).title
 | 
			
		||||
            : "Are you sure to execute this action"}
 | 
			
		||||
        </Text>
 | 
			
		||||
        <div className="flex items-center justify-end w-full gap-3">
 | 
			
		||||
          <Button
 | 
			
		||||
            size="xs"
 | 
			
		||||
            disabled={isLoading}
 | 
			
		||||
            onClick={() => handleCallBack(action.callback)}
 | 
			
		||||
          >
 | 
			
		||||
            Ok
 | 
			
		||||
          </Button>
 | 
			
		||||
          <Button
 | 
			
		||||
            size="xs"
 | 
			
		||||
            disabled={isLoading}
 | 
			
		||||
            color="red"
 | 
			
		||||
            onClick={handleClose}
 | 
			
		||||
          >
 | 
			
		||||
            Close
 | 
			
		||||
          </Button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </Dialog>
 | 
			
		||||
    );
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, [action, opened, close, renderComfirm]);
 | 
			
		||||
 | 
			
		||||
    const actionDataMemo = useMemo(() => {
 | 
			
		||||
        const newActions = actions?.reduce((prev, cur) => {
 | 
			
		||||
            prev.push({ value: String(cur.key), label: cur.title, disabled: cur?.disabled ? cur.disabled(chooses || []) : false });
 | 
			
		||||
            return prev;
 | 
			
		||||
        }, [] as { value: string; label: string; disabled: boolean }[]);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!initFilter) return;
 | 
			
		||||
 | 
			
		||||
        return newActions;
 | 
			
		||||
    }, [actions, chooses]);
 | 
			
		||||
    const params = initFilter.reduce((prev, cur) => {
 | 
			
		||||
      if (cur.key === searchKey) {
 | 
			
		||||
        prev[cur.key] = cur.type;
 | 
			
		||||
      }
 | 
			
		||||
      return prev;
 | 
			
		||||
    }, {} as Record<string, string | number>);
 | 
			
		||||
 | 
			
		||||
    const handleClose = () => {
 | 
			
		||||
        close();
 | 
			
		||||
        setAction(null);
 | 
			
		||||
        setSelectValue(null);
 | 
			
		||||
    form.setValues(params);
 | 
			
		||||
    // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
  }, [initFilter]);
 | 
			
		||||
 | 
			
		||||
        if (onCloseComfirm) {
 | 
			
		||||
            onCloseComfirm();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    setIsLoading(!!loading);
 | 
			
		||||
  }, [loading]);
 | 
			
		||||
 | 
			
		||||
    const handleClearAction = () => {
 | 
			
		||||
        setAction(null);
 | 
			
		||||
        setSelectValue(null);
 | 
			
		||||
    };
 | 
			
		||||
  useImperativeHandle(
 | 
			
		||||
    refAction,
 | 
			
		||||
    () => {
 | 
			
		||||
      return {
 | 
			
		||||
        setAction,
 | 
			
		||||
        clearAction: handleClearAction,
 | 
			
		||||
      };
 | 
			
		||||
    },
 | 
			
		||||
    []
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
    const comfirmViewMemo = useMemo(() => {
 | 
			
		||||
        if (!action || !action.comfirmAction) return;
 | 
			
		||||
 | 
			
		||||
        return renderComfirm ? (
 | 
			
		||||
            renderComfirm(action)
 | 
			
		||||
        ) : (
 | 
			
		||||
            <Dialog opened={opened} withCloseButton onClose={handleClose} size="lg" radius="md">
 | 
			
		||||
                <Text size="sm" mb="xs" fw={500}>
 | 
			
		||||
                    {action?.comfirmOption && action?.comfirmOption(action)?.title ? action.comfirmOption(action).title : 'Are you sure to execute this action'}
 | 
			
		||||
                </Text>
 | 
			
		||||
                <div className="flex items-center justify-end w-full gap-3">
 | 
			
		||||
                    <Button size="xs" disabled={isLoading} onClick={() => handleCallBack(action.callback)}>
 | 
			
		||||
                        Ok
 | 
			
		||||
                    </Button>
 | 
			
		||||
                    <Button size="xs" disabled={isLoading} color="red" onClick={handleClose}>
 | 
			
		||||
                        Close
 | 
			
		||||
                    </Button>
 | 
			
		||||
                </div>
 | 
			
		||||
            </Dialog>
 | 
			
		||||
        );
 | 
			
		||||
    }, [action, opened, close, renderComfirm]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (!initFilter) return;
 | 
			
		||||
 | 
			
		||||
        const params = initFilter.reduce((prev, cur) => {
 | 
			
		||||
            if (cur.key === searchKey) {
 | 
			
		||||
                prev[cur.key] = cur.type;
 | 
			
		||||
            }
 | 
			
		||||
            return prev;
 | 
			
		||||
        }, {} as Record<string, string | number>);
 | 
			
		||||
 | 
			
		||||
        form.setValues(params);
 | 
			
		||||
        // eslint-disable-next-line react-hooks/exhaustive-deps
 | 
			
		||||
    }, [initFilter]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        setIsLoading(!!loading);
 | 
			
		||||
    }, [loading]);
 | 
			
		||||
 | 
			
		||||
    useImperativeHandle(
 | 
			
		||||
        refAction,
 | 
			
		||||
        () => {
 | 
			
		||||
            return {
 | 
			
		||||
                setAction,
 | 
			
		||||
                clearAction: handleClearAction,
 | 
			
		||||
            };
 | 
			
		||||
        },
 | 
			
		||||
        [],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Box className="flex justify-between items-center">
 | 
			
		||||
            {showSearch && searchOptions?.render ? (
 | 
			
		||||
                searchOptions.render()
 | 
			
		||||
            ) : (
 | 
			
		||||
                <form onSubmit={form.onSubmit(handleSubmit)}>
 | 
			
		||||
                    <TextInput
 | 
			
		||||
                        {...form.getInputProps(searchKey)}
 | 
			
		||||
                        className="min-w-[260px]"
 | 
			
		||||
                        leftSection={<IconSearch size={'14px'} />}
 | 
			
		||||
                        rightSection={
 | 
			
		||||
                            form.getValues()[searchKey].length ? <IconX onClick={handleClear} className="cursor-pointer hover:text-red-400 select-none" size={'14px'} /> : undefined
 | 
			
		||||
                        }
 | 
			
		||||
                        placeholder="Search by keyword"
 | 
			
		||||
                        size="xs"
 | 
			
		||||
                        label={'Search'}
 | 
			
		||||
                        {...searchOptions?.props}
 | 
			
		||||
                    />
 | 
			
		||||
                </form>
 | 
			
		||||
            )}
 | 
			
		||||
            {showAction && (
 | 
			
		||||
                <Select
 | 
			
		||||
                    size="xs"
 | 
			
		||||
                    value={selectValue}
 | 
			
		||||
                    onChange={handleChangeAction}
 | 
			
		||||
                    label="Actions"
 | 
			
		||||
                    placeholder="Pick value"
 | 
			
		||||
                    defaultChecked={false}
 | 
			
		||||
                    data={actionDataMemo}
 | 
			
		||||
                    {...selectProps}
 | 
			
		||||
  return (
 | 
			
		||||
    <Box className="flex justify-between items-center">
 | 
			
		||||
      {showSearch && searchOptions?.render ? (
 | 
			
		||||
        searchOptions.render()
 | 
			
		||||
      ) : (
 | 
			
		||||
        <form onSubmit={form.onSubmit(handleSubmit)}>
 | 
			
		||||
          <TextInput
 | 
			
		||||
            {...form.getInputProps(searchKey)}
 | 
			
		||||
            className="min-w-[260px]"
 | 
			
		||||
            leftSection={<IconSearch size={"14px"} />}
 | 
			
		||||
            rightSection={
 | 
			
		||||
              form.getValues()[searchKey].length ? (
 | 
			
		||||
                <IconX
 | 
			
		||||
                  onClick={handleClear}
 | 
			
		||||
                  className="cursor-pointer hover:text-red-400 select-none"
 | 
			
		||||
                  size={"14px"}
 | 
			
		||||
                />
 | 
			
		||||
            )}
 | 
			
		||||
              ) : undefined
 | 
			
		||||
            }
 | 
			
		||||
            placeholder="Search by keyword"
 | 
			
		||||
            size="xs"
 | 
			
		||||
            label={"Search"}
 | 
			
		||||
            {...searchOptions?.props}
 | 
			
		||||
          />
 | 
			
		||||
        </form>
 | 
			
		||||
      )}
 | 
			
		||||
      {showAction && (
 | 
			
		||||
        <Box className="flex items-end gap-4">
 | 
			
		||||
          {leftActionSession}
 | 
			
		||||
 | 
			
		||||
            {comfirmViewMemo}
 | 
			
		||||
          <Select
 | 
			
		||||
            size="xs"
 | 
			
		||||
            value={selectValue}
 | 
			
		||||
            onChange={handleChangeAction}
 | 
			
		||||
            label="Actions"
 | 
			
		||||
            placeholder="Pick value"
 | 
			
		||||
            defaultChecked={false}
 | 
			
		||||
            data={actionDataMemo}
 | 
			
		||||
            {...selectProps}
 | 
			
		||||
          />
 | 
			
		||||
        </Box>
 | 
			
		||||
    );
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      {comfirmViewMemo}
 | 
			
		||||
    </Box>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
// stores/useChooseStore.ts
 | 
			
		||||
import { create } from "zustand";
 | 
			
		||||
import { IBid } from "../../system/type";
 | 
			
		||||
 | 
			
		||||
interface ChoosesStore {
 | 
			
		||||
  chooses: IBid[];
 | 
			
		||||
  setChooses: (items: IBid[]) => void;
 | 
			
		||||
  addChoose: (item: IBid) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const useChoosesStore = create<ChoosesStore>((set) => ({
 | 
			
		||||
  chooses: [],
 | 
			
		||||
  setChooses: (items) => set({ chooses: items }),
 | 
			
		||||
  addChoose: (item) =>
 | 
			
		||||
    set((state) => ({
 | 
			
		||||
      chooses: [...state.chooses, item],
 | 
			
		||||
    })),
 | 
			
		||||
}));
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,13 @@
 | 
			
		|||
import { ActionIcon, Anchor, Badge, Box, Menu, Text, Tooltip } from "@mantine/core";
 | 
			
		||||
import {
 | 
			
		||||
  ActionIcon,
 | 
			
		||||
  Anchor,
 | 
			
		||||
  Badge,
 | 
			
		||||
  Box,
 | 
			
		||||
  Button,
 | 
			
		||||
  Menu,
 | 
			
		||||
  Text,
 | 
			
		||||
  Tooltip,
 | 
			
		||||
} from "@mantine/core";
 | 
			
		||||
import { useDisclosure } from "@mantine/hooks";
 | 
			
		||||
import {
 | 
			
		||||
  IconAd,
 | 
			
		||||
| 
						 | 
				
			
			@ -7,7 +16,7 @@ import {
 | 
			
		|||
  IconHammer,
 | 
			
		||||
  IconHistory,
 | 
			
		||||
  IconMenu,
 | 
			
		||||
  IconTrash
 | 
			
		||||
  IconPlus
 | 
			
		||||
} from "@tabler/icons-react";
 | 
			
		||||
import _ from "lodash";
 | 
			
		||||
import { useMemo, useRef, useState } from "react";
 | 
			
		||||
| 
						 | 
				
			
			@ -18,9 +27,11 @@ import {
 | 
			
		|||
  ShowHistoriesBidPicklesApiModal,
 | 
			
		||||
  ShowHistoriesModal,
 | 
			
		||||
} from "../components/bid";
 | 
			
		||||
import DeleteRowAction from "../components/bid/delete-row-action";
 | 
			
		||||
import constants, { 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";
 | 
			
		||||
| 
						 | 
				
			
			@ -31,6 +42,8 @@ export default function Bids() {
 | 
			
		|||
 | 
			
		||||
  const [clickData, setClickData] = useState<IBid | null>(null);
 | 
			
		||||
 | 
			
		||||
  const {setChooses} = useChoosesStore()
 | 
			
		||||
 | 
			
		||||
  const { setConfirm } = useConfirmStore();
 | 
			
		||||
 | 
			
		||||
  const [openedHistories, historiesModel] = useDisclosure(false);
 | 
			
		||||
| 
						 | 
				
			
			@ -52,9 +65,16 @@ export default function Bids() {
 | 
			
		|||
      title: "Name",
 | 
			
		||||
      typeFilter: "text",
 | 
			
		||||
      renderRow(row) {
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        return <Anchor className="text-[14px]"  href={row.url} size="sm" style={{color: 'inherit'}}>{row.name}</Anchor>
 | 
			
		||||
        return (
 | 
			
		||||
          <Anchor
 | 
			
		||||
            className="text-[14px]"
 | 
			
		||||
            href={row.url}
 | 
			
		||||
            size="sm"
 | 
			
		||||
            style={{ color: "inherit" }}
 | 
			
		||||
          >
 | 
			
		||||
            {row.name}
 | 
			
		||||
          </Anchor>
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -157,7 +177,7 @@ export default function Bids() {
 | 
			
		|||
  const handleDelete = (bid: IBid) => {
 | 
			
		||||
    setConfirm({
 | 
			
		||||
      title: "Delete ?",
 | 
			
		||||
      message: "This bid will be delete",
 | 
			
		||||
      message: `This bid will be delete: ${bid.name || bid.model}`,
 | 
			
		||||
      handleOk: async () => {
 | 
			
		||||
        await deleteBid(bid);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -192,7 +212,7 @@ export default function Bids() {
 | 
			
		|||
  const table = useMemo(() => {
 | 
			
		||||
    return (
 | 
			
		||||
      <Table
 | 
			
		||||
        
 | 
			
		||||
        onChooses={setChooses}
 | 
			
		||||
        tableChildProps={{
 | 
			
		||||
          trbody: {
 | 
			
		||||
            className: "cursor-pointer",
 | 
			
		||||
| 
						 | 
				
			
			@ -200,13 +220,13 @@ export default function Bids() {
 | 
			
		|||
        }}
 | 
			
		||||
        actionsOptions={{
 | 
			
		||||
          actions: [
 | 
			
		||||
            {
 | 
			
		||||
              key: "add",
 | 
			
		||||
              title: "Add",
 | 
			
		||||
              callback: () => {
 | 
			
		||||
                bidModal.open();
 | 
			
		||||
              },
 | 
			
		||||
            },
 | 
			
		||||
            // {
 | 
			
		||||
            //   key: "add",
 | 
			
		||||
            //   title: "Add",
 | 
			
		||||
            //   callback: () => {
 | 
			
		||||
            //     bidModal.open();
 | 
			
		||||
            //   },
 | 
			
		||||
            // },
 | 
			
		||||
            {
 | 
			
		||||
              key: "delete",
 | 
			
		||||
              title: "Delete",
 | 
			
		||||
| 
						 | 
				
			
			@ -228,6 +248,9 @@ export default function Bids() {
 | 
			
		|||
              disabled: (data) => data.length <= 0,
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
          leftActionSession: (
 | 
			
		||||
            <Button onClick={bidModal.open} size="xs" rightSection={<IconPlus size={14}/>}>Add</Button>
 | 
			
		||||
          )
 | 
			
		||||
        }}
 | 
			
		||||
        refTableFn={refTableFn}
 | 
			
		||||
        striped
 | 
			
		||||
| 
						 | 
				
			
			@ -257,84 +280,88 @@ export default function Bids() {
 | 
			
		|||
          title: <Box className="w-full text-center">Action</Box>,
 | 
			
		||||
          body: (row) => {
 | 
			
		||||
            return (
 | 
			
		||||
              <Menu shadow="md" width={200}>
 | 
			
		||||
                <Menu.Target>
 | 
			
		||||
                  <Box
 | 
			
		||||
                    onClick={(e) => e.stopPropagation()}
 | 
			
		||||
                    className="flex w-full items-center justify-center"
 | 
			
		||||
                  >
 | 
			
		||||
                    <ActionIcon size="sm" variant="light">
 | 
			
		||||
                      <IconMenu size={14} />
 | 
			
		||||
                    </ActionIcon>
 | 
			
		||||
                  </Box>
 | 
			
		||||
                </Menu.Target>
 | 
			
		||||
              <Box className="flex items-center gap-2">
 | 
			
		||||
                <Menu shadow="md" width={200}>
 | 
			
		||||
                  <Menu.Target>
 | 
			
		||||
                    <Box
 | 
			
		||||
                      onClick={(e) => e.stopPropagation()}
 | 
			
		||||
                      className="flex w-full items-center justify-center"
 | 
			
		||||
                    >
 | 
			
		||||
                      <ActionIcon size="sm" variant="light">
 | 
			
		||||
                        <IconMenu size={14} />
 | 
			
		||||
                      </ActionIcon>
 | 
			
		||||
                    </Box>
 | 
			
		||||
                  </Menu.Target>
 | 
			
		||||
 | 
			
		||||
                <Menu.Dropdown onClick={(e) => e.stopPropagation()}>
 | 
			
		||||
                  <Menu.Item
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                      setClickData(row);
 | 
			
		||||
                      bidModal.open();
 | 
			
		||||
                    }}
 | 
			
		||||
                    leftSection={<IconEdit size={14} />}
 | 
			
		||||
                  >
 | 
			
		||||
                    Edit
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
 | 
			
		||||
                  <Menu.Item
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                      setClickData(row);
 | 
			
		||||
                      historiesModel.open();
 | 
			
		||||
                    }}
 | 
			
		||||
                    leftSection={<IconHistory size={14} />}
 | 
			
		||||
                  >
 | 
			
		||||
                    Histories
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                  {haveHistories.includes(row?.web_bid.origin_url) && (
 | 
			
		||||
                  <Menu.Dropdown onClick={(e) => e.stopPropagation()}>
 | 
			
		||||
                    <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();
 | 
			
		||||
                          }
 | 
			
		||||
                        }
 | 
			
		||||
                        bidModal.open();
 | 
			
		||||
                      }}
 | 
			
		||||
                      leftSection={<IconHammer size={14} />}
 | 
			
		||||
                      leftSection={<IconEdit size={14} />}
 | 
			
		||||
                    >
 | 
			
		||||
                      Bids
 | 
			
		||||
                      Edit
 | 
			
		||||
                    </Menu.Item>
 | 
			
		||||
                  )}
 | 
			
		||||
 | 
			
		||||
                  <Menu.Item
 | 
			
		||||
                    disabled={row.status === "win-bid"}
 | 
			
		||||
                    onClick={() => handleToggleBid(row)}
 | 
			
		||||
                    leftSection={
 | 
			
		||||
                      row.status === "biding" ? (
 | 
			
		||||
                        <IconAdOff size={14} />
 | 
			
		||||
                      ) : (
 | 
			
		||||
                        <IconAd size={14} />
 | 
			
		||||
                      )
 | 
			
		||||
                    }
 | 
			
		||||
                  >
 | 
			
		||||
                    {row.status === "biding" ? "Disable" : "Enable"}
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                    <Menu.Item
 | 
			
		||||
                      onClick={() => {
 | 
			
		||||
                        setClickData(row);
 | 
			
		||||
                        historiesModel.open();
 | 
			
		||||
                      }}
 | 
			
		||||
                      leftSection={<IconHistory size={14} />}
 | 
			
		||||
                    >
 | 
			
		||||
                      Histories
 | 
			
		||||
                    </Menu.Item>
 | 
			
		||||
                    {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();
 | 
			
		||||
                            }
 | 
			
		||||
                          }
 | 
			
		||||
                        }}
 | 
			
		||||
                        leftSection={<IconHammer size={14} />}
 | 
			
		||||
                      >
 | 
			
		||||
                        Bids
 | 
			
		||||
                      </Menu.Item>
 | 
			
		||||
                    )}
 | 
			
		||||
 | 
			
		||||
                  <Menu.Item
 | 
			
		||||
                    onClick={() => handleDelete(row)}
 | 
			
		||||
                    leftSection={<IconTrash color="red" size={14} />}
 | 
			
		||||
                  >
 | 
			
		||||
                    Delete
 | 
			
		||||
                  </Menu.Item>
 | 
			
		||||
                </Menu.Dropdown>
 | 
			
		||||
              </Menu>
 | 
			
		||||
                    <Menu.Item
 | 
			
		||||
                      disabled={row.status === "win-bid"}
 | 
			
		||||
                      onClick={() => handleToggleBid(row)}
 | 
			
		||||
                      leftSection={
 | 
			
		||||
                        row.status === "biding" ? (
 | 
			
		||||
                          <IconAdOff size={14} />
 | 
			
		||||
                        ) : (
 | 
			
		||||
                          <IconAd size={14} />
 | 
			
		||||
                        )
 | 
			
		||||
                      }
 | 
			
		||||
                    >
 | 
			
		||||
                      {row.status === "biding" ? "Disable" : "Enable"}
 | 
			
		||||
                    </Menu.Item>
 | 
			
		||||
 | 
			
		||||
                    {/* <Menu.Item
 | 
			
		||||
                      onClick={() => handleDelete(row)}
 | 
			
		||||
                      leftSection={<IconTrash color="red" size={14} />}
 | 
			
		||||
                    >
 | 
			
		||||
                      Delete
 | 
			
		||||
                    </Menu.Item> */}
 | 
			
		||||
                  </Menu.Dropdown>
 | 
			
		||||
                </Menu>
 | 
			
		||||
 | 
			
		||||
                <DeleteRowAction data={row} onClick={() => handleDelete(row)} />
 | 
			
		||||
              </Box>
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
        }}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -419,12 +419,6 @@ const trackingLoginStatus = async () => {
 | 
			
		|||
            login_status,
 | 
			
		||||
          });
 | 
			
		||||
 | 
			
		||||
          console.log(
 | 
			
		||||
            "%cindex.js:422 ehehehehehe",
 | 
			
		||||
            "color: #007acc;",
 | 
			
		||||
            "ehehehehehe"
 | 
			
		||||
          );
 | 
			
		||||
 | 
			
		||||
          // Set time to update login sau 1 phút
 | 
			
		||||
          const now = new Date();
 | 
			
		||||
          const oneMinuteLater = new Date(now.getTime() + 60 * 1000);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue