Quote Status
This commit is contained in:
parent
adcc6e3028
commit
1be73865ea
@ -1,6 +1,6 @@
|
|||||||
import { DownloadIcon, FilePenLineIcon } from "lucide-react";
|
import { DownloadIcon, FilePenLineIcon } from "lucide-react";
|
||||||
|
|
||||||
import { ColorBadge } from "@/components";
|
import { QuoteStatusBadge } from "@/components";
|
||||||
import { useCustomLocalization } from "@/lib/hooks";
|
import { useCustomLocalization } from "@/lib/hooks";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
@ -40,7 +40,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
|
|
||||||
const { useOne, useSetStatus, useSentTo, useDownloader, getQuotePDFFilename } = useQuotes();
|
const { useOne, useSetStatus, useSentTo, useDownloader, getQuotePDFFilename } = useQuotes();
|
||||||
const { data, status } = useOne(quoteId);
|
const { data, status } = useOne(quoteId);
|
||||||
const { mutate: setStatusMutation } = useSetStatus(quoteId);
|
const { mutate: setStatusMutation } = useSetStatus();
|
||||||
const { mutate: sentToMutation } = useSentTo(quoteId);
|
const { mutate: sentToMutation } = useSentTo(quoteId);
|
||||||
const { download, ...downloadProps } = useDownloader();
|
const { download, ...downloadProps } = useDownloader();
|
||||||
|
|
||||||
@ -80,9 +80,9 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
const allowToSent = useMemo(() => data?.status === "accepted" && !data?.date_sent, [data]);
|
const allowToSent = useMemo(() => data?.status === "accepted" && !data?.date_sent, [data]);
|
||||||
const isSent = useMemo(() => data?.status === "accepted" && data?.date_sent, [data]);
|
const isSent = useMemo(() => data?.status === "accepted" && data?.date_sent, [data]);
|
||||||
|
|
||||||
const handleOnChangeStatus = (_: string, newStatus: string) => {
|
const handleOnChangeStatus = (newStatus: string) => {
|
||||||
setStatusMutation(
|
setStatusMutation(
|
||||||
{ newStatus },
|
{ id: data!!.id, newStatus },
|
||||||
|
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@ -143,14 +143,14 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
<CardHeader className='gap-3 border-b bg-accent'>
|
<CardHeader className='gap-3 border-b bg-accent'>
|
||||||
<CardTitle className='flex items-center justify-between text-lg'>
|
<CardTitle className='flex items-center justify-between text-lg'>
|
||||||
<span>{t("quotes.list.resume.title")}</span>
|
<span>{t("quotes.list.resume.title")}</span>
|
||||||
<ColorBadge className='text-sm' label={t(`quotes.status.${data.status}`)} />
|
<QuoteStatusBadge className='text-sm' status={data.status} />
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className='flex mr-auto text-foreground'>
|
<div className='flex mr-auto text-foreground'>
|
||||||
<div className='flex items-center gap-1'>
|
<div className='flex items-center gap-1'>
|
||||||
{allowToSent && !isSent && (
|
{allowToSent && !isSent && (
|
||||||
<>
|
<>
|
||||||
<QuoteSentToEditor quote={data} onSentTo={handleOnSentTo} />
|
<QuoteSentToEditor quote={data} onSentTo={handleOnSentTo} />
|
||||||
<QuoteStatusEditor quote={data} onChangeStatus={handleOnChangeStatus} />
|
<QuoteStatusEditor status={data.status} onChangeStatus={handleOnChangeStatus} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -171,7 +171,7 @@ export const QuoteResume = ({ quoteId, className }: QuoteResumeProps) => {
|
|||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<QuoteStatusEditor quote={data} onChangeStatus={handleOnChangeStatus} />
|
<QuoteStatusEditor status={data.status} onChangeStatus={handleOnChangeStatus} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -8,29 +8,18 @@ import {
|
|||||||
} from "@/components";
|
} from "@/components";
|
||||||
import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
import { DataTableToolbar } from "@/components/DataTable/DataTableToolbar";
|
||||||
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
import { useDataTable, useDataTableContext } from "@/lib/hooks";
|
||||||
import {
|
import { Button, Card, CardContent, Tooltip, TooltipContent, TooltipTrigger } from "@/ui";
|
||||||
Button,
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuSeparator,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "@/ui";
|
|
||||||
import { useToast } from "@/ui/use-toast";
|
import { useToast } from "@/ui/use-toast";
|
||||||
import { IListQuotes_Response_DTO, UTCDateValue } from "@shared/contexts";
|
import { IListQuotes_Response_DTO, UTCDateValue } from "@shared/contexts";
|
||||||
import { ColumnDef, Row } from "@tanstack/react-table";
|
import { ColumnDef, Row } from "@tanstack/react-table";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { FilePenLineIcon, MoreVerticalIcon, SendIcon } from "lucide-react";
|
import { ArchiveIcon, CopyIcon, DownloadIcon, EditIcon } from "lucide-react";
|
||||||
import { useCallback, useEffect, useId, useMemo, useState } from "react";
|
import { useCallback, useEffect, useId, useMemo, useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useQuotes } from "../hooks";
|
import { useQuotes } from "../hooks";
|
||||||
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
import { DownloadQuoteDialog } from "./DownloadQuoteDialog";
|
||||||
import { QuoteResume } from "./QuoteResume";
|
import { QuoteResume } from "./QuoteResume";
|
||||||
|
import { QuoteStatusEditor } from "./editors";
|
||||||
|
|
||||||
export const QuotesDataTable = ({
|
export const QuotesDataTable = ({
|
||||||
status = "all",
|
status = "all",
|
||||||
@ -49,7 +38,9 @@ export const QuotesDataTable = ({
|
|||||||
|
|
||||||
const [activeRow, setActiveRow] = useState<Row<IListQuotes_Response_DTO> | undefined>(undefined);
|
const [activeRow, setActiveRow] = useState<Row<IListQuotes_Response_DTO> | undefined>(undefined);
|
||||||
|
|
||||||
const { useList, useDownloader, getQuotePDFFilename } = useQuotes();
|
const { useList, useDownloader, useSetStatus, getQuotePDFFilename } = useQuotes();
|
||||||
|
|
||||||
|
const { mutate: setStatusMutation } = useSetStatus();
|
||||||
|
|
||||||
const { data, isPending, isError, error } = useList({
|
const { data, isPending, isError, error } = useList({
|
||||||
pagination: {
|
pagination: {
|
||||||
@ -79,6 +70,20 @@ export const QuotesDataTable = ({
|
|||||||
[navigate, toast]
|
[navigate, toast]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleOnChangeStatus = (id: string, newStatus: string) => {
|
||||||
|
setStatusMutation(
|
||||||
|
{ id, newStatus },
|
||||||
|
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast({
|
||||||
|
description: t("quotes.quote_status_editor.toast_status_changed"),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const columns = useMemo<ColumnDef<IListQuotes_Response_DTO, unknown>[]>(() => {
|
const columns = useMemo<ColumnDef<IListQuotes_Response_DTO, unknown>[]>(() => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
@ -118,7 +123,13 @@ export const QuotesDataTable = ({
|
|||||||
header: () => <>{t("quotes.list.columns.status")}</>,
|
header: () => <>{t("quotes.list.columns.status")}</>,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => (
|
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => (
|
||||||
<ColorBadge label={t(`quotes.status.${original.status}`)} />
|
<QuoteStatusEditor
|
||||||
|
type='badge'
|
||||||
|
status={original.status}
|
||||||
|
onChangeStatus={(newStatus: string, oldStatus: string) =>
|
||||||
|
handleOnChangeStatus(original.id, newStatus)
|
||||||
|
}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -206,72 +217,86 @@ export const QuotesDataTable = ({
|
|||||||
header: () => null,
|
header: () => null,
|
||||||
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
|
cell: ({ row: { original } }: { row: { original: IListQuotes_Response_DTO } }) => {
|
||||||
const allowToSent = original?.status === "accepted" && !original?.date_sent;
|
const allowToSent = original?.status === "accepted" && !original?.date_sent;
|
||||||
const isSent = original?.status === "accepted" && original?.date_sent;
|
const isSent = original?.status === "accepted" && !!original?.date_sent;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup>
|
<ButtonGroup className='gap-0'>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<>
|
<Button
|
||||||
{allowToSent && !isSent && (
|
variant='ghost'
|
||||||
<Button
|
size='icon'
|
||||||
size='sm'
|
disabled={isSent}
|
||||||
variant='default'
|
onClick={(e) => {
|
||||||
className='h-8 gap-1'
|
e.preventDefault();
|
||||||
onClick={(e) => {
|
handleEditQuote(original);
|
||||||
e.preventDefault();
|
}}
|
||||||
//handleSentToUecko(original);
|
>
|
||||||
}}
|
<EditIcon className='w-4 h-4' />
|
||||||
>
|
<span className='sr-only'>Editar</span>
|
||||||
<SendIcon className='h-3.5 w-3.5' />
|
</Button>
|
||||||
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
|
|
||||||
{t("quotes.list.columns.actions.sent_to")}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isSent && (
|
|
||||||
<Button
|
|
||||||
size='sm'
|
|
||||||
variant='outline'
|
|
||||||
className='h-8 gap-1'
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
handleEditQuote(original);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FilePenLineIcon className='h-3.5 w-3.5' />
|
|
||||||
<span className='lg:sr-only xl:not-sr-only xl:whitespace-nowrap'>
|
|
||||||
{t("quotes.list.columns.actions.edit")}
|
|
||||||
</span>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("quotes.list.columns.actions.sent_to_uecko")}</p>
|
<p>Editar</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<DropdownMenu>
|
<Tooltip>
|
||||||
<DropdownMenuTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button size='icon' variant='outline' className='w-8 h-8'>
|
<Button
|
||||||
<MoreVerticalIcon className='h-3.5 w-3.5' />
|
variant='ghost'
|
||||||
<span className='sr-only'>{t("common.more")}</span>
|
size='icon'
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
//handleDuplicateQuote(original);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CopyIcon className='w-4 h-4' />
|
||||||
|
<span className='sr-only'>Duplicar</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</TooltipTrigger>
|
||||||
<DropdownMenuContent align='end'>
|
<TooltipContent>
|
||||||
<DropdownMenuItem
|
<p>Duplicar</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
size='icon'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
download(original.id, getQuotePDFFilename(original));
|
download(original.id, getQuotePDFFilename(original));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Download
|
<DownloadIcon className='w-4 h-4' />
|
||||||
</DropdownMenuItem>
|
<span className='sr-only'>Descargar</span>
|
||||||
<DropdownMenuSeparator />
|
</Button>
|
||||||
<DropdownMenuItem>{t("common.archive")}</DropdownMenuItem>
|
</TooltipTrigger>
|
||||||
</DropdownMenuContent>
|
<TooltipContent>
|
||||||
</DropdownMenu>
|
<p>Descargar</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant='ghost'
|
||||||
|
size='icon'
|
||||||
|
disabled={isSent}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
//handleArchiveQuote(original)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArchiveIcon className='w-4 h-4' />
|
||||||
|
<span className='sr-only'>Archivar</span>
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Archivar</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { QuoteStatusBadge } from "@/components";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
@ -12,7 +13,6 @@ import {
|
|||||||
Switch,
|
Switch,
|
||||||
} from "@/ui";
|
} from "@/ui";
|
||||||
import { DialogDescription } from "@radix-ui/react-dialog";
|
import { DialogDescription } from "@radix-ui/react-dialog";
|
||||||
import { IGetQuote_Response_DTO } from "@shared/contexts";
|
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { RefreshCwIcon } from "lucide-react";
|
import { RefreshCwIcon } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@ -31,37 +31,43 @@ const quoteStatusTransitions: Record<TQuoteStatus, TQuoteStatus[]> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const QuoteStatusEditor = ({
|
export const QuoteStatusEditor = ({
|
||||||
quote,
|
type = "button",
|
||||||
|
status,
|
||||||
onChangeStatus,
|
onChangeStatus,
|
||||||
}: {
|
}: {
|
||||||
quote: IGetQuote_Response_DTO;
|
type?: "button" | "badge";
|
||||||
onChangeStatus: (quoteId: string, newStatus: string) => void;
|
status: string;
|
||||||
|
onChangeStatus: (newStatus: string, oldStatus: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [newStatus, setNewStatus] = useState<string>("");
|
const [newStatus, setNewStatus] = useState<string>(status);
|
||||||
|
|
||||||
const changeStatus = (newStatus: string) => setNewStatus(newStatus);
|
const changeStatus = (newStatus: string) => setNewStatus(newStatus);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (quote) {
|
if (status) {
|
||||||
setNewStatus(quote.status);
|
setNewStatus(status);
|
||||||
}
|
}
|
||||||
}, [quote]);
|
}, [status]);
|
||||||
|
|
||||||
const handleChangeStatus = () => {
|
const handleChangeStatus = () => {
|
||||||
if (newStatus !== quote.status) {
|
if (newStatus !== status) {
|
||||||
onChangeStatus(quote.id, newStatus);
|
onChangeStatus(newStatus, status);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button size='sm' variant='outline' className='h-8 gap-1'>
|
{type === "button" ? (
|
||||||
<RefreshCwIcon className='h-3.5 w-3.5' />
|
<Button size='sm' variant='outline' className='h-8 gap-1'>
|
||||||
<span className='sr-only md:not-sr-only md:whitespace-nowrap'>
|
<RefreshCwIcon className='h-3.5 w-3.5' />
|
||||||
{t("quotes.quote_status_editor.trigger_button")}
|
<span className='sr-only md:not-sr-only md:whitespace-nowrap'>
|
||||||
</span>
|
{t("quotes.quote_status_editor.trigger_button")}
|
||||||
</Button>
|
</span>
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<QuoteStatusBadge status={status} isEditable />
|
||||||
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
@ -71,9 +77,10 @@ export const QuoteStatusEditor = ({
|
|||||||
|
|
||||||
<div className='grid gap-4 py-4'>
|
<div className='grid gap-4 py-4'>
|
||||||
{QuoteStatus.map((_status) => {
|
{QuoteStatus.map((_status) => {
|
||||||
const isDisabled = !quoteStatusTransitions[quote.status as TQuoteStatus].includes(
|
const isDisabled = false;
|
||||||
|
/*const isDisabled = !quoteStatusTransitions[status as TQuoteStatus].includes(
|
||||||
_status as TQuoteStatus
|
_status as TQuoteStatus
|
||||||
);
|
);*/
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={_status} className='flex items-start space-x-4'>
|
<div key={_status} className='flex items-start space-x-4'>
|
||||||
@ -108,7 +115,7 @@ export const QuoteStatusEditor = ({
|
|||||||
</DialogClose>
|
</DialogClose>
|
||||||
|
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button onClick={handleChangeStatus} disabled={newStatus === quote.status}>
|
<Button onClick={handleChangeStatus} disabled={newStatus === status}>
|
||||||
{t("quotes.quote_status_editor.submit_button")}
|
{t("quotes.quote_status_editor.submit_button")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
|
|||||||
@ -147,13 +147,12 @@ export const useQuotes = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
useSetStatus: (id?: string) => {
|
useSetStatus: () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<void, TDataSourceError, ISetStatusQuote_Request_DTO>({
|
return useMutation<void, TDataSourceError, ISetStatusQuote_Request_DTO & { id: string }>({
|
||||||
mutationKey: keys().data().resource("quotes").action("one").id(id).params().get(),
|
|
||||||
mutationFn: (data) => {
|
mutationFn: (data) => {
|
||||||
const { newStatus } = data;
|
const { id, newStatus } = data;
|
||||||
|
|
||||||
return dataSource.custom({
|
return dataSource.custom({
|
||||||
url: `${dataSource.getApiUrl()}/quotes/${id}/setStatus`,
|
url: `${dataSource.getApiUrl()}/quotes/${id}/setStatus`,
|
||||||
|
|||||||
80
client/src/components/QuoteStatusBadge/QuoteStatusBadge.tsx
Normal file
80
client/src/components/QuoteStatusBadge/QuoteStatusBadge.tsx
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Badge } from "@/ui";
|
||||||
|
import { t } from "i18next";
|
||||||
|
import { RefreshCwIcon } from "lucide-react";
|
||||||
|
|
||||||
|
export type QuoteStatusBadgeProps = {
|
||||||
|
status: string;
|
||||||
|
isEditable?: boolean;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type QuoteStatus = "draft" | "ready" | "delivered" | "accepted" | "rejected" | "archived";
|
||||||
|
|
||||||
|
const statusColorConfig: Record<
|
||||||
|
QuoteStatus,
|
||||||
|
{ color: string; bgColor: string; hoverColor: string; hoverBgColor: string }
|
||||||
|
> = {
|
||||||
|
draft: {
|
||||||
|
color: "text-gray-700",
|
||||||
|
bgColor: "bg-gray-200",
|
||||||
|
hoverColor: "hover:text-gray-900",
|
||||||
|
hoverBgColor: "hover:bg-gray-300",
|
||||||
|
},
|
||||||
|
ready: {
|
||||||
|
color: "text-blue-700",
|
||||||
|
bgColor: "bg-blue-200",
|
||||||
|
hoverColor: "hover:text-blue-900",
|
||||||
|
hoverBgColor: "hover:bg-blue-300",
|
||||||
|
},
|
||||||
|
delivered: {
|
||||||
|
color: "text-yellow-700",
|
||||||
|
bgColor: "bg-yellow-200",
|
||||||
|
hoverColor: "hover:text-yellow-900",
|
||||||
|
hoverBgColor: "hover:bg-yellow-300",
|
||||||
|
},
|
||||||
|
accepted: {
|
||||||
|
color: "text-green-700",
|
||||||
|
bgColor: "bg-green-200",
|
||||||
|
hoverColor: "hover:text-green-900",
|
||||||
|
hoverBgColor: "hover:bg-green-300",
|
||||||
|
},
|
||||||
|
rejected: {
|
||||||
|
color: "text-red-700",
|
||||||
|
bgColor: "bg-red-200",
|
||||||
|
hoverColor: "hover:text-red-900",
|
||||||
|
hoverBgColor: "hover:bg-red-300",
|
||||||
|
},
|
||||||
|
archived: {
|
||||||
|
color: "text-purple-700",
|
||||||
|
bgColor: "bg-purple-200",
|
||||||
|
hoverColor: "hover:text-purple-900",
|
||||||
|
hoverBgColor: "hover:bg-purple-300",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QuoteStatusBadge = ({
|
||||||
|
status,
|
||||||
|
isEditable,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: QuoteStatusBadgeProps) => {
|
||||||
|
return (
|
||||||
|
<Badge
|
||||||
|
className={cn(
|
||||||
|
statusColorConfig[status as QuoteStatus].bgColor,
|
||||||
|
statusColorConfig[status as QuoteStatus].color,
|
||||||
|
statusColorConfig[status as QuoteStatus].hoverBgColor,
|
||||||
|
statusColorConfig[status as QuoteStatus].hoverColor,
|
||||||
|
"transition-colors duration-200 cursor-pointer flex items-center group",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{t(`quotes.status.${status}`)}
|
||||||
|
{isEditable && (
|
||||||
|
<RefreshCwIcon className='w-3 h-3 ml-2 transition-opacity opacity-0 group-hover:opacity-100' />
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
};
|
||||||
1
client/src/components/QuoteStatusBadge/index.ts
Normal file
1
client/src/components/QuoteStatusBadge/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./QuoteStatusBadge";
|
||||||
@ -13,6 +13,8 @@ export * from "./LoadingOverlay";
|
|||||||
export * from "./LoadingSpinner";
|
export * from "./LoadingSpinner";
|
||||||
export * from "./PDFViewer";
|
export * from "./PDFViewer";
|
||||||
export * from "./ProtectedRoute";
|
export * from "./ProtectedRoute";
|
||||||
|
export * from "./QuoteStatusBadge";
|
||||||
|
|
||||||
//export * from "./SorteableDataTable";
|
//export * from "./SorteableDataTable";
|
||||||
|
|
||||||
export * from "./TailwindIndicator";
|
export * from "./TailwindIndicator";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user