added comment, attchment
This commit is contained in:
parent
297e0712bc
commit
86819dde03
@ -5,8 +5,13 @@
|
||||
}
|
||||
.offcanvas.offcanvas-wide {
|
||||
width: 700px !important; /* adjust as needed */
|
||||
max-width: 90vw; /* responsive fallback */
|
||||
}
|
||||
.sticky-section {
|
||||
position: sticky;
|
||||
top: var(--sticky-top, 0px) !important;
|
||||
z-index: 1025;
|
||||
}
|
||||
|
||||
|
||||
/* ===========================% Background_Colors %========================================================== */
|
||||
.bg-light-primary {
|
||||
|
||||
@ -20,7 +20,7 @@ const Filelist = ({ files, removeFile, expenseToEdit,sm=6,md=4 }) => {
|
||||
<i
|
||||
className={`bx ${getIconByFileType(
|
||||
file?.contentType
|
||||
)} fs-3 text-primary`}
|
||||
)} fs-3 `}
|
||||
style={{ minWidth: "30px" }}
|
||||
></i>
|
||||
|
||||
|
||||
214
src/components/ServiceProject/JobComments.jsx
Normal file
214
src/components/ServiceProject/JobComments.jsx
Normal file
@ -0,0 +1,214 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Avatar from "../common/Avatar";
|
||||
import { useAppForm } from "../../hooks/appHooks/useAppForm";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { JobCommentSchema } from "./ServiceProjectSchema";
|
||||
import {
|
||||
useAddCommentJob,
|
||||
useJobComments,
|
||||
} from "../../hooks/useServiceProject";
|
||||
import { ITEMS_PER_PAGE } from "../../utils/constants";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import Filelist from "../Expenses/Filelist";
|
||||
import { formatFileSize, getIconByFileType } from "../../utils/appUtils";
|
||||
|
||||
const JobComments = ({ data }) => {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
reset,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useAppForm({
|
||||
resolver: zodResolver(JobCommentSchema),
|
||||
default: { comment: "", attachments: [] },
|
||||
});
|
||||
|
||||
const {
|
||||
data: comments,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
} = useJobComments(data?.id, ITEMS_PER_PAGE, 1);
|
||||
const jobComments = comments?.pages?.flatMap((p) => p?.data ?? []) ?? [];
|
||||
|
||||
const { mutate: AddComment, isPending } = useAddCommentJob(() => reset());
|
||||
const onSubmit = (formData) => {
|
||||
formData.jobTicketId = data?.id;
|
||||
AddComment(formData);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty("--sticky-top", `-25px`);
|
||||
}, []);
|
||||
|
||||
const files = watch("attachments");
|
||||
const onFileChange = async (e) => {
|
||||
const newFiles = Array.from(e.target.files);
|
||||
if (newFiles.length === 0) return;
|
||||
|
||||
const existingFiles = Array.isArray(watch("attachments"))
|
||||
? watch("attachments")
|
||||
: [];
|
||||
|
||||
const parsedFiles = await Promise.all(
|
||||
newFiles.map(async (file) => {
|
||||
const base64Data = await toBase64(file);
|
||||
return {
|
||||
fileName: file.name,
|
||||
base64Data,
|
||||
contentType: file.type,
|
||||
fileSize: file.size,
|
||||
description: "",
|
||||
isActive: true,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
const combinedFiles = [
|
||||
...existingFiles,
|
||||
...parsedFiles.filter(
|
||||
(newFile) =>
|
||||
!existingFiles?.some(
|
||||
(f) =>
|
||||
f.fileName === newFile.fileName && f.fileSize === newFile.fileSize
|
||||
)
|
||||
),
|
||||
];
|
||||
|
||||
setValue("attachments", combinedFiles, {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true,
|
||||
});
|
||||
};
|
||||
|
||||
const toBase64 = (file) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => resolve(reader.result.split(",")[1]);
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
const removeFile = (index) => {
|
||||
const newFiles = files.filter((_, i) => i !== index);
|
||||
setValue("attachments", newFiles, { shouldValidate: true });
|
||||
};
|
||||
return (
|
||||
<div className="row">
|
||||
<div className="sticky-section bg-white p-3">
|
||||
<h6 className="m-0 fw-semibold mb-3">Add Comment</h6>
|
||||
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<div className="d-flex">
|
||||
<Avatar firstName={"A"} lastName={"D"} />
|
||||
|
||||
<div className="flex-grow-1">
|
||||
<textarea
|
||||
className="form-control"
|
||||
rows={3}
|
||||
placeholder="Write your comment..."
|
||||
{...register("comment")}
|
||||
></textarea>
|
||||
|
||||
{errors?.comment && (
|
||||
<small className="danger-text">
|
||||
{errors?.comment?.message}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="d-flex justify-content-end gap-3 align-items-center text-end mt-3">
|
||||
<div
|
||||
onClick={() => document.getElementById("attachments").click()}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept=".pdf,.jpg,.jpeg,.png"
|
||||
id="attachments"
|
||||
multiple
|
||||
className="d-none"
|
||||
{...register("attachments")}
|
||||
onChange={(e) => {
|
||||
onFileChange(e);
|
||||
e.target.value = "";
|
||||
}}
|
||||
/>
|
||||
<i className="bx bx-paperclip"></i>
|
||||
Add Attachment
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-primary btn-sm px-3"
|
||||
type="submit"
|
||||
disabled={!watch("comment")?.trim() || isPending}
|
||||
>
|
||||
<i className="bx bx-send me-1"></i>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
{files?.length > 0 && (
|
||||
<Filelist files={files} removeFile={removeFile} />
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
<div className="card-body p-0">
|
||||
<div className="list-group p-0 m-0">
|
||||
{jobComments?.map((item) => {
|
||||
const user = item?.createdBy;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
className="list-group-item border-0 border-bottom p-0"
|
||||
>
|
||||
<div className="d-flex align-items-start mt-2 mx-0 px-0">
|
||||
<Avatar
|
||||
firstName={user?.firstName}
|
||||
lastName={user?.lastName}
|
||||
/>
|
||||
<div className="">
|
||||
<div className="d-flex flex-row gap-3">
|
||||
<span className="fw-semibold">
|
||||
{user?.firstName} {user?.lastName}
|
||||
</span>
|
||||
<span className="text-secondary">
|
||||
<em>{formatUTCToLocalTime(item?.createdAt, true)}</em>
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-muted text-secondary">
|
||||
{user?.jobRoleName}
|
||||
</div>
|
||||
<div className="text-wrap">
|
||||
<p className="mb-1 mt-2 text-muted">{item.comment}</p>
|
||||
<div className="d-flex flex-wrap jusify-content-end gap-2 gap-sm-6 ">
|
||||
{item.attachments?.map((file) => (
|
||||
<div className="d-flex align-items-center">
|
||||
<i
|
||||
className={`bx bx-xxl ${getIconByFileType(
|
||||
file?.contentType
|
||||
)} fs-3`}
|
||||
></i>
|
||||
<div className="d-flex flex-column">
|
||||
<p className="m-0">{file.fileName}</p>
|
||||
<small className="text-secondary">
|
||||
{formatFileSize(file.fileSize)}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobComments;
|
||||
52
src/components/ServiceProject/JobStatusLog.jsx
Normal file
52
src/components/ServiceProject/JobStatusLog.jsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from "react";
|
||||
|
||||
const JobStatusLog = ({ data }) => {
|
||||
return (
|
||||
<div className="card shadow-none p-0">
|
||||
<div className="card-body p-0">
|
||||
<div className="list-group">
|
||||
|
||||
{data?.map((item) => (
|
||||
<div key={item.id} className="list-group-item border-0 border-bottom pb-3">
|
||||
|
||||
<div className="d-flex justify-content-between">
|
||||
<div>
|
||||
<span className="fw-semibold">
|
||||
{item.nextStatus?.displayName ?? item.status?.displayName ?? "Status"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<span className="badge bg-primary">
|
||||
Level {item.nextStatus?.level ?? item.status?.level}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p className="mb-1 mt-2 text-muted" style={{ fontSize: "0.9rem" }}>
|
||||
{item.comment}
|
||||
</p>
|
||||
|
||||
<div className="d-flex align-items-center mt-2">
|
||||
<div className="rounded-circle bg-light d-flex justify-content-center align-items-center"
|
||||
style={{ width: 32, height: 32 }}>
|
||||
<i className="bx bx-user"></i>
|
||||
</div>
|
||||
<div className="ms-2">
|
||||
<div className="fw-semibold" style={{ fontSize: "0.85rem" }}>
|
||||
{item.updatedBy?.firstName} {item.updatedBy?.lastName}
|
||||
</div>
|
||||
<div className="text-muted" style={{ fontSize: "0.75rem" }}>
|
||||
{item.updatedBy?.jobRoleName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobStatusLog;
|
||||
@ -7,6 +7,8 @@ import OffcanvasComponent from "../common/OffcanvasComponent";
|
||||
import showToast from "../../services/toastService";
|
||||
import ManageJob from "./ManageJob";
|
||||
import ManageJobTicket from "./ManageJobTicket";
|
||||
import GlobalModel from "../common/GlobalModel";
|
||||
import PreviewDocument from "../Expenses/PreviewDocument";
|
||||
|
||||
export const JonContext = createContext();
|
||||
export const useServiceProjectJobContext = () => {
|
||||
@ -72,6 +74,10 @@ const Jobs = () => {
|
||||
<JobList filterByProject={selectedProject} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<GlobalModel>
|
||||
<PreviewDocument />
|
||||
</GlobalModel>
|
||||
</JonContext.Provider>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -1,15 +1,34 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { useServiceProjectJobDetails } from "../../hooks/useServiceProject";
|
||||
import { SpinnerLoader } from "../common/Loader";
|
||||
import Error from "../common/Error";
|
||||
import { formatUTCToLocalTime } from "../../utils/dateUtils";
|
||||
import Avatar from "../common/Avatar";
|
||||
import EmployeeAvatarGroup from "../common/EmployeeAvatarGroup";
|
||||
import JobStatusLog from "./JobStatusLog";
|
||||
import JobComments from "./JobComments";
|
||||
|
||||
const ManageJobTicket = ({ Job }) => {
|
||||
const { data, isLoading, isError, error } = useServiceProjectJobDetails(
|
||||
Job?.job
|
||||
);
|
||||
|
||||
const tabsData = [
|
||||
{
|
||||
id: "comment",
|
||||
title: "Coments",
|
||||
active: true,
|
||||
content: <JobComments data={data} />,
|
||||
},
|
||||
{
|
||||
id: "history",
|
||||
title: "History",
|
||||
active: false,
|
||||
content: <JobStatusLog data={data?.updateLogs} />,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
if (isLoading) return <SpinnerLoader />;
|
||||
if (isError)
|
||||
return (
|
||||
@ -45,7 +64,8 @@ const ManageJobTicket = ({ Job }) => {
|
||||
<small className="fs-6 text-secondary fs-italic me-3 mt-2">
|
||||
<em>Created By</em>
|
||||
</small>{" "}
|
||||
<Avatar size="xs"
|
||||
<Avatar
|
||||
size="xs"
|
||||
firstName={data?.createdBy?.firstName}
|
||||
lastName={data?.createdBy?.lastName}
|
||||
/>{" "}
|
||||
@ -53,15 +73,41 @@ const ManageJobTicket = ({ Job }) => {
|
||||
</div>
|
||||
<div className="d-flex align-items-center">
|
||||
<small className="fs-6 text-secondary fs-italic me-3 mt-2">
|
||||
<em>Created By</em>
|
||||
</small><EmployeeAvatarGroup employees={data?.assignees}/>
|
||||
<em>Assigned</em>
|
||||
</small>
|
||||
<EmployeeAvatarGroup employees={data?.assignees} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="divider"/>
|
||||
<div className="col-12">
|
||||
<div className="">
|
||||
|
||||
<div className="nav-align-top nav-tabs-shadow p-0 shadow-none">
|
||||
<ul className="nav nav-tabs" role="tablist">
|
||||
{tabsData.map((tab) => (
|
||||
<li className="nav-item" key={tab.id}>
|
||||
<button
|
||||
type="button"
|
||||
className={`nav-link ${tab.active ? "active" : ""}`}
|
||||
data-bs-toggle="tab"
|
||||
data-bs-target={`#tab-${tab.id}`}
|
||||
role="tab"
|
||||
>
|
||||
{tab.title}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<div className="tab-content p-1 px-sm-3">
|
||||
{tabsData.map((tab) => (
|
||||
<div
|
||||
key={tab.id}
|
||||
className={`tab-pane fade ${tab.active ? "show active" : ""}`}
|
||||
id={`tab-${tab.id}`}
|
||||
role="tabpanel"
|
||||
>
|
||||
{tab.content}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -4,7 +4,9 @@ export const projectSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
shortName: z.string().min(1, "Short name is required"),
|
||||
clientId: z.string().min(1, { message: "Client is required" }),
|
||||
services:z.array(z.string()).min(1,{message:"At least one service required"}),
|
||||
services: z
|
||||
.array(z.string())
|
||||
.min(1, { message: "At least one service required" }),
|
||||
address: z.string().min(1, "Address is required"),
|
||||
assignedDate: z.string().min(1, { message: "Date is required" }),
|
||||
statusId: z.string().min(1, "Status is required"),
|
||||
@ -31,7 +33,6 @@ export const defaultProjectValues = {
|
||||
|
||||
//#region JobSchema
|
||||
|
||||
|
||||
export const TagSchema = z.object({
|
||||
name: z.string().min(1, "Tag name is required"),
|
||||
isActive: z.boolean().default(true),
|
||||
@ -39,11 +40,9 @@ export const TagSchema = z.object({
|
||||
export const jobSchema = z.object({
|
||||
title: z.string().min(1, "Title is required"),
|
||||
description: z.string().min(1, "Description is required"),
|
||||
projectId: z.string().min(1,"Project is required"),
|
||||
projectId: z.string().min(1, "Project is required"),
|
||||
|
||||
assignees: z
|
||||
.array(z.string() )
|
||||
.nonempty("At least one assignee is required"),
|
||||
assignees: z.array(z.string()).nonempty("At least one assignee is required"),
|
||||
|
||||
startDate: z.string(),
|
||||
dueDate: z.string(),
|
||||
@ -51,15 +50,39 @@ export const jobSchema = z.object({
|
||||
tags: z.array(TagSchema).optional().default([]),
|
||||
});
|
||||
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
||||
const ALLOWED_TYPES = [
|
||||
"application/pdf",
|
||||
"image/png",
|
||||
"image/jpg",
|
||||
"image/jpeg",
|
||||
];
|
||||
export const JobCommentSchema = z.object({
|
||||
comment: z.string().min(1, { message: "Please leave comment" }),
|
||||
attachments: z.array(
|
||||
z.object({
|
||||
fileName: z.string().min(1, { message: "Filename is required" }),
|
||||
base64Data: z.string().nullable(),
|
||||
contentType: z.string().refine((val) => ALLOWED_TYPES.includes(val), {
|
||||
message: "Only PDF, PNG, JPG, or JPEG files are allowed",
|
||||
}),
|
||||
documentId: z.string().optional(),
|
||||
fileSize: z.number().max(MAX_FILE_SIZE, {
|
||||
message: "File size must be less than or equal to 5MB",
|
||||
}),
|
||||
description: z.string().optional(),
|
||||
isActive: z.boolean().default(true),
|
||||
})
|
||||
),
|
||||
});
|
||||
export const defaultJobValue = {
|
||||
title:"",
|
||||
description:"",
|
||||
projectId:"",
|
||||
assignees:[],
|
||||
startDate:null,
|
||||
dueDate:null,
|
||||
tags:[]
|
||||
|
||||
}
|
||||
title: "",
|
||||
description: "",
|
||||
projectId: "",
|
||||
assignees: [],
|
||||
startDate: null,
|
||||
dueDate: null,
|
||||
tags: [],
|
||||
};
|
||||
|
||||
//#endregion
|
||||
@ -1,4 +1,4 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ServiceProjectRepository } from "../repositories/ServiceProjectRepository";
|
||||
import showToast from "../services/toastService";
|
||||
|
||||
@ -102,26 +102,75 @@ export const useActiveInActiveServiceProject = (onSuccessCallback) => {
|
||||
|
||||
//#region Service Jobs
|
||||
|
||||
|
||||
export const useServiceProjectJobs=(pageSize,pageNumber,isActive=true,project)=>{
|
||||
export const useServiceProjectJobs = (
|
||||
pageSize,
|
||||
pageNumber,
|
||||
isActive = true,
|
||||
project
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey:["serviceProjectJobs",pageSize,pageNumber,isActive,project],
|
||||
queryFn:async() =>{
|
||||
const resp = await ServiceProjectRepository.GetJobList(pageSize,pageNumber,isActive,project);
|
||||
queryKey: ["serviceProjectJobs", pageSize, pageNumber, isActive, project],
|
||||
queryFn: async () => {
|
||||
const resp = await ServiceProjectRepository.GetJobList(
|
||||
pageSize,
|
||||
pageNumber,
|
||||
isActive,
|
||||
project
|
||||
);
|
||||
return resp.data;
|
||||
}
|
||||
})
|
||||
}
|
||||
export const useServiceProjectJobDetails = (job)=>{
|
||||
},
|
||||
});
|
||||
};
|
||||
export const useJobComments = (jobId, pageSize, pageNumber ) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: ["jobComments", jobId, pageSize],
|
||||
|
||||
queryFn: async ({ pageParam = pageNumber }) => {
|
||||
const resp = await ServiceProjectRepository.GetJobComment(jobId, pageSize, pageParam);
|
||||
return resp.data;
|
||||
},
|
||||
|
||||
initialPageParam: pageNumber,
|
||||
|
||||
getNextPageParam: (lastPage, allPages) => {
|
||||
if (!lastPage?.data?.length) return null;
|
||||
|
||||
return allPages.length + pageNumber;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useServiceProjectJobDetails = (job) => {
|
||||
return useQuery({
|
||||
queryKey:['service-job',job],
|
||||
queryFn:async() =>{
|
||||
queryKey: ["service-job", job],
|
||||
queryFn: async () => {
|
||||
const resp = await ServiceProjectRepository.GetJobDetails(job);
|
||||
return resp.data;
|
||||
},
|
||||
enabled:!!job
|
||||
})
|
||||
}
|
||||
enabled: !!job,
|
||||
});
|
||||
};
|
||||
export const useAddCommentJob = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: async (data) => await ServiceProjectRepository.AddComment(data),
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ["jobComments", variables?.jobTicketId],
|
||||
});
|
||||
if (onSuccessCallback) onSuccessCallback();
|
||||
showToast("Job Created successfully", "success");
|
||||
},
|
||||
onError: (error) => {
|
||||
showToast(
|
||||
error?.response?.data?.message ||
|
||||
error.message ||
|
||||
"Failed to update project",
|
||||
"error"
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateServiceProjectJob = (onSuccessCallback) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
@ -15,9 +15,14 @@ export const ServiceProjectRepository = {
|
||||
//#region Job
|
||||
|
||||
CreateJob: (data) => api.post(`/api/ServiceProject/job/create`, data),
|
||||
GetJobList: (pageSize, pageNumber, isActive,projectId,) =>
|
||||
GetJobList: (pageSize, pageNumber, isActive, projectId) =>
|
||||
api.get(
|
||||
`/api/ServiceProject/job/list?pageSize=${pageSize}&pageNumber=${pageNumber}&isActive=${isActive}&projectId=${projectId}`
|
||||
),
|
||||
GetJobDetails:(id)=>api.get(`/api/ServiceProject/job/details/${id}`)
|
||||
GetJobDetails: (id) => api.get(`/api/ServiceProject/job/details/${id}`),
|
||||
AddComment: (data) => api.post("/api/ServiceProject/job/add/comment", data),
|
||||
GetJobComment: (jobTicketId,pageSize,pageNumber) =>
|
||||
api.get(
|
||||
`/api/ServiceProject/job/comment/list?jobTicketId=${jobTicketId}&pageSize=${pageSize}&pageNumber=${pageNumber}`
|
||||
),
|
||||
};
|
||||
|
||||
@ -51,13 +51,15 @@ export const useDebounce = (value, delay = 500) => {
|
||||
export const getIconByFileType = (type = "") => {
|
||||
const lower = type.toLowerCase();
|
||||
|
||||
if (lower === "application/pdf") return "bxs-file-pdf";
|
||||
if (lower.includes("word")) return "bxs-file-doc";
|
||||
if (lower === "application/pdf") return "bxs-file-pdf text-danger";
|
||||
if (lower.includes("word")) return "bxs-file-doc text-primary text-primry";
|
||||
if (lower.includes("excel") || lower.includes("spreadsheet"))
|
||||
return "bxs-file-xls";
|
||||
return "bxs-file-xls text-primry";
|
||||
if (lower === "image/png") return "bxs-file-png";
|
||||
if (lower === "image/jpeg" || lower === "image/jpg") return "bxs-file-jpg";
|
||||
if (lower.includes("zip") || lower.includes("rar")) return "bxs-file-archive";
|
||||
if (lower === "image/jpeg" || lower === "image/jpg")
|
||||
return "bxs-file-jpg text-primry";
|
||||
if (lower.includes("zip") || lower.includes("rar"))
|
||||
return "bxs-file-archive text-secondary";
|
||||
|
||||
return "bx bx-file";
|
||||
};
|
||||
@ -192,19 +194,12 @@ export const frequencyLabel = (
|
||||
}
|
||||
};
|
||||
|
||||
const badgeColors = [
|
||||
"primary",
|
||||
"secondary",
|
||||
"success",
|
||||
"warning",
|
||||
"info",
|
||||
];
|
||||
const badgeColors = ["primary", "secondary", "success", "warning", "info"];
|
||||
|
||||
let colorIndex = 0;
|
||||
|
||||
export function getNextBadgeColor(type="label") {
|
||||
export function getNextBadgeColor(type = "label") {
|
||||
const color = badgeColors[colorIndex];
|
||||
colorIndex = (colorIndex + 1) % badgeColors.length;
|
||||
return `rounded-pill text-bg-${color}`;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user