feature/IO-3587-Commision-Cut - Additional test, layout enhancements
This commit is contained in:
@@ -1,9 +1,23 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { useMutation, useQuery } from "@apollo/client/react";
|
import { useMutation, useQuery } from "@apollo/client/react";
|
||||||
import { Button, Card, Col, Form, Input, InputNumber, Row, Select, Space, Switch, Tag, Typography } from "antd";
|
import {
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Col,
|
||||||
|
Form,
|
||||||
|
Input,
|
||||||
|
InputNumber,
|
||||||
|
Row,
|
||||||
|
Select,
|
||||||
|
Skeleton,
|
||||||
|
Space,
|
||||||
|
Switch,
|
||||||
|
Tag,
|
||||||
|
Typography
|
||||||
|
} from "antd";
|
||||||
|
|
||||||
import querystring from "query-string";
|
import querystring from "query-string";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
@@ -85,21 +99,40 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
|
|||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const search = querystring.parse(useLocation().search);
|
const search = querystring.parse(useLocation().search);
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
const [hydratedTeamId, setHydratedTeamId] = useState(search.employeeTeamId === "new" ? "new" : null);
|
||||||
|
const isNewTeam = search.employeeTeamId === "new";
|
||||||
|
|
||||||
const { error, data } = useQuery(QUERY_EMPLOYEE_TEAM_BY_ID, {
|
const { error, data, loading } = useQuery(QUERY_EMPLOYEE_TEAM_BY_ID, {
|
||||||
variables: { id: search.employeeTeamId },
|
variables: { id: search.employeeTeamId },
|
||||||
skip: !search.employeeTeamId || search.employeeTeamId === "new",
|
skip: !search.employeeTeamId || isNewTeam,
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only"
|
nextFetchPolicy: "network-only",
|
||||||
|
notifyOnNetworkStatusChange: true
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.employee_teams_by_pk) {
|
if (!search.employeeTeamId) return;
|
||||||
|
|
||||||
|
if (isNewTeam) {
|
||||||
|
form.resetFields();
|
||||||
|
setHydratedTeamId("new");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setHydratedTeamId(null);
|
||||||
|
}, [form, isNewTeam, search.employeeTeamId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!search.employeeTeamId || isNewTeam || loading) return;
|
||||||
|
|
||||||
|
if (data?.employee_teams_by_pk?.id === search.employeeTeamId) {
|
||||||
form.setFieldsValue(normalizeEmployeeTeam(data.employee_teams_by_pk));
|
form.setFieldsValue(normalizeEmployeeTeam(data.employee_teams_by_pk));
|
||||||
|
setHydratedTeamId(search.employeeTeamId);
|
||||||
} else {
|
} else {
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
|
setHydratedTeamId(search.employeeTeamId);
|
||||||
}
|
}
|
||||||
}, [form, data, search.employeeTeamId]);
|
}, [data, form, isNewTeam, loading, search.employeeTeamId]);
|
||||||
|
|
||||||
const [updateEmployeeTeam] = useMutation(UPDATE_EMPLOYEE_TEAM);
|
const [updateEmployeeTeam] = useMutation(UPDATE_EMPLOYEE_TEAM);
|
||||||
const [insertEmployeeTeam] = useMutation(INSERT_EMPLOYEE_TEAM);
|
const [insertEmployeeTeam] = useMutation(INSERT_EMPLOYEE_TEAM);
|
||||||
@@ -109,7 +142,10 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
|
|||||||
}));
|
}));
|
||||||
const teamName = Form.useWatch("name", form);
|
const teamName = Form.useWatch("name", form);
|
||||||
const teamMembers = Form.useWatch(["employee_team_members"], form) || [];
|
const teamMembers = Form.useWatch(["employee_team_members"], form) || [];
|
||||||
const teamCardTitle = teamName?.trim() || t("employee_teams.fields.name");
|
const isTeamHydrating = !isNewTeam && Boolean(search.employeeTeamId) && hydratedTeamId !== search.employeeTeamId;
|
||||||
|
const teamCardTitle = isTeamHydrating
|
||||||
|
? t("employee_teams.fields.name")
|
||||||
|
: teamName?.trim() || t("employee_teams.fields.name");
|
||||||
|
|
||||||
const getTeamMemberTitle = (teamMember = {}) => {
|
const getTeamMemberTitle = (teamMember = {}) => {
|
||||||
const employeeName =
|
const employeeName =
|
||||||
@@ -123,10 +159,10 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
|
|||||||
return (
|
return (
|
||||||
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
|
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
|
||||||
<Typography.Text strong>{employeeName}</Typography.Text>
|
<Typography.Text strong>{employeeName}</Typography.Text>
|
||||||
<Tag bordered={false} color="geekblue">
|
<Tag variant="filled" color="geekblue">
|
||||||
{`${t("employee_teams.fields.allocation")}: ${allocation || "--"}`}
|
{`${t("employee_teams.fields.allocation")}: ${allocation || "--"}`}
|
||||||
</Tag>
|
</Tag>
|
||||||
<Tag bordered={false} color={getPayoutMethodTagColor(teamMember.payout_method)}>
|
<Tag variant="filled" color={getPayoutMethodTagColor(teamMember.payout_method)}>
|
||||||
{payoutMethod}
|
{payoutMethod}
|
||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
@@ -228,191 +264,194 @@ export function ShopEmployeeTeamsFormComponent({ bodyshop }) {
|
|||||||
<Card
|
<Card
|
||||||
title={teamCardTitle}
|
title={teamCardTitle}
|
||||||
extra={
|
extra={
|
||||||
<Button type="primary" onClick={() => form.submit()}>
|
<Button type="primary" onClick={() => form.submit()} disabled={isTeamHydrating}>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Form onFinish={handleFinish} autoComplete={"off"} layout="vertical" form={form}>
|
{isTeamHydrating ? (
|
||||||
<LayoutFormRow>
|
<Skeleton active title={false} paragraph={{ rows: 12 }} />
|
||||||
<Form.Item
|
) : (
|
||||||
name="name"
|
<Form onFinish={handleFinish} autoComplete={"off"} layout="vertical" form={form}>
|
||||||
label={t("employee_teams.fields.name")}
|
<LayoutFormRow>
|
||||||
rules={[
|
<Form.Item
|
||||||
{
|
name="name"
|
||||||
required: true
|
label={t("employee_teams.fields.name")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
>
|
required: true
|
||||||
<Input />
|
}
|
||||||
</Form.Item>
|
]}
|
||||||
<Form.Item label={t("employee_teams.fields.active")} name="active" valuePropName="checked">
|
>
|
||||||
<Switch />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item label={t("employee_teams.fields.active")} name="active" valuePropName="checked">
|
||||||
label={t("employee_teams.fields.max_load")}
|
<Switch />
|
||||||
name="max_load"
|
</Form.Item>
|
||||||
rules={[
|
<Form.Item
|
||||||
{
|
label={t("employee_teams.fields.max_load")}
|
||||||
required: true
|
name="max_load"
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
>
|
required: true
|
||||||
<InputNumber min={0} precision={1} />
|
}
|
||||||
</Form.Item>
|
]}
|
||||||
</LayoutFormRow>
|
>
|
||||||
<Form.List name={["employee_team_members"]}>
|
<InputNumber min={0} precision={1} />
|
||||||
{(fields, { add, remove, move }) => {
|
</Form.Item>
|
||||||
return (
|
</LayoutFormRow>
|
||||||
<div>
|
<Form.List name={["employee_team_members"]}>
|
||||||
{fields.map((field, index) => {
|
{(fields, { add, remove, move }) => {
|
||||||
const teamMember = normalizeTeamMember(teamMembers[field.name]);
|
return (
|
||||||
|
<div>
|
||||||
return (
|
{fields.map((field, index) => {
|
||||||
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
|
const teamMember = normalizeTeamMember(teamMembers[field.name]);
|
||||||
<Form.Item label={t("employees.fields.id")} key={`${index}`} name={[field.name, "id"]} hidden>
|
|
||||||
<Input type="hidden" />
|
|
||||||
</Form.Item>
|
|
||||||
<LayoutFormRow
|
|
||||||
grow
|
|
||||||
title={getTeamMemberTitle(teamMember)}
|
|
||||||
extra={
|
|
||||||
<Space align="center" size="small">
|
|
||||||
<Button
|
|
||||||
type="text"
|
|
||||||
icon={<DeleteFilled />}
|
|
||||||
onClick={() => {
|
|
||||||
remove(field.name);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<FormListMoveArrows
|
|
||||||
move={move}
|
|
||||||
index={index}
|
|
||||||
total={fields.length}
|
|
||||||
orientation="horizontal"
|
|
||||||
/>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<Row gutter={[16, 0]}>
|
|
||||||
<Col {...TEAM_MEMBER_PRIMARY_FIELD_COLS.employee}>
|
|
||||||
<Form.Item
|
|
||||||
label={t("employee_teams.fields.employeeid")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "employeeid"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<EmployeeSearchSelectComponent options={bodyshop.employees} />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col {...TEAM_MEMBER_PRIMARY_FIELD_COLS.allocation}>
|
|
||||||
<Form.Item
|
|
||||||
label={t("employee_teams.fields.allocation_percentage")}
|
|
||||||
key={`${index}`}
|
|
||||||
name={[field.name, "percentage"]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<InputNumber min={0} max={100} precision={2} />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
<Col {...TEAM_MEMBER_PRIMARY_FIELD_COLS.payoutMethod}>
|
|
||||||
<Form.Item
|
|
||||||
label={t("employee_teams.fields.payout_method")}
|
|
||||||
key={`${index}-payout-method`}
|
|
||||||
name={[field.name, "payout_method"]}
|
|
||||||
initialValue="hourly"
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Select options={payoutMethodOptions} />
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
<Form.Item noStyle dependencies={[["employee_team_members", field.name, "payout_method"]]}>
|
|
||||||
{() => {
|
|
||||||
const payoutMethod =
|
|
||||||
form.getFieldValue(["employee_team_members", field.name, "payout_method"]) || "hourly";
|
|
||||||
const fieldName = payoutMethod === "commission" ? "commission_rates" : "labor_rates";
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Row gutter={[16, 0]}>
|
|
||||||
{LABOR_TYPES.map((laborType) => (
|
|
||||||
<Col {...TEAM_MEMBER_RATE_FIELD_COLS} key={`${index}-${fieldName}-${laborType}`}>
|
|
||||||
<Form.Item
|
|
||||||
label={
|
|
||||||
t(`joblines.fields.lbr_types.${laborType}`)
|
|
||||||
}
|
|
||||||
name={[field.name, fieldName, laborType]}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{payoutMethod === "commission" ? (
|
|
||||||
<InputNumber min={0} max={100} precision={2} />
|
|
||||||
) : (
|
|
||||||
<CurrencyInput />
|
|
||||||
)}
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
|
||||||
</div>
|
|
||||||
</LayoutFormRow>
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="dashed"
|
|
||||||
onClick={() => {
|
|
||||||
add({
|
|
||||||
percentage: 0,
|
|
||||||
payout_method: "hourly",
|
|
||||||
labor_rates: {},
|
|
||||||
commission_rates: {}
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
style={{ width: "100%" }}
|
|
||||||
>
|
|
||||||
{t("employee_teams.actions.newmember")}
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item noStyle shouldUpdate>
|
|
||||||
{() => {
|
|
||||||
const teamMembers = form.getFieldValue(["employee_team_members"]) || [];
|
|
||||||
const splitTotal = getSplitTotal(teamMembers);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Typography.Text type={hasExactSplitTotal(teamMembers) ? undefined : "danger"}>
|
<Form.Item key={field.key} style={{ padding: 0, margin: 2 }}>
|
||||||
{t("employee_teams.labels.allocation_total", {
|
<Form.Item label={t("employees.fields.id")} key={`${index}`} name={[field.name, "id"]} hidden>
|
||||||
total: splitTotal.toFixed(2)
|
<Input type="hidden" />
|
||||||
})}
|
</Form.Item>
|
||||||
</Typography.Text>
|
<LayoutFormRow
|
||||||
|
grow
|
||||||
|
title={getTeamMemberTitle(teamMember)}
|
||||||
|
extra={
|
||||||
|
<Space align="center" size="small">
|
||||||
|
<Button
|
||||||
|
type="text"
|
||||||
|
icon={<DeleteFilled />}
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<FormListMoveArrows
|
||||||
|
move={move}
|
||||||
|
index={index}
|
||||||
|
total={fields.length}
|
||||||
|
orientation="horizontal"
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<Row gutter={[16, 0]}>
|
||||||
|
<Col {...TEAM_MEMBER_PRIMARY_FIELD_COLS.employee}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("employee_teams.fields.employeeid")}
|
||||||
|
key={`${index}`}
|
||||||
|
name={[field.name, "employeeid"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<EmployeeSearchSelectComponent options={bodyshop.employees} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col {...TEAM_MEMBER_PRIMARY_FIELD_COLS.allocation}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("employee_teams.fields.allocation_percentage")}
|
||||||
|
key={`${index}`}
|
||||||
|
name={[field.name, "percentage"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<InputNumber min={0} max={100} precision={2} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col {...TEAM_MEMBER_PRIMARY_FIELD_COLS.payoutMethod}>
|
||||||
|
<Form.Item
|
||||||
|
label={t("employee_teams.fields.payout_method")}
|
||||||
|
key={`${index}-payout-method`}
|
||||||
|
name={[field.name, "payout_method"]}
|
||||||
|
initialValue="hourly"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Select options={payoutMethodOptions} />
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
<Form.Item noStyle dependencies={[["employee_team_members", field.name, "payout_method"]]}>
|
||||||
|
{() => {
|
||||||
|
const payoutMethod =
|
||||||
|
form.getFieldValue(["employee_team_members", field.name, "payout_method"]) ||
|
||||||
|
"hourly";
|
||||||
|
const fieldName = payoutMethod === "commission" ? "commission_rates" : "labor_rates";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Row gutter={[16, 0]}>
|
||||||
|
{LABOR_TYPES.map((laborType) => (
|
||||||
|
<Col {...TEAM_MEMBER_RATE_FIELD_COLS} key={`${index}-${fieldName}-${laborType}`}>
|
||||||
|
<Form.Item
|
||||||
|
label={t(`joblines.fields.lbr_types.${laborType}`)}
|
||||||
|
name={[field.name, fieldName, laborType]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{payoutMethod === "commission" ? (
|
||||||
|
<InputNumber min={0} max={100} precision={2} />
|
||||||
|
) : (
|
||||||
|
<CurrencyInput />
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
</LayoutFormRow>
|
||||||
|
</Form.Item>
|
||||||
);
|
);
|
||||||
}}
|
})}
|
||||||
</Form.Item>
|
<Form.Item>
|
||||||
</div>
|
<Button
|
||||||
);
|
type="dashed"
|
||||||
}}
|
onClick={() => {
|
||||||
</Form.List>
|
add({
|
||||||
</Form>
|
percentage: 0,
|
||||||
|
payout_method: "hourly",
|
||||||
|
labor_rates: {},
|
||||||
|
commission_rates: {}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
{t("employee_teams.actions.newmember")}
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle shouldUpdate>
|
||||||
|
{() => {
|
||||||
|
const teamMembers = form.getFieldValue(["employee_team_members"]) || [];
|
||||||
|
const splitTotal = getSplitTotal(teamMembers);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Typography.Text type={hasExactSplitTotal(teamMembers) ? undefined : "danger"}>
|
||||||
|
{t("employee_teams.labels.allocation_total", {
|
||||||
|
total: splitTotal.toFixed(2)
|
||||||
|
})}
|
||||||
|
</Typography.Text>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</Form.List>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
2346
package-lock.json
generated
2346
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
44
package.json
44
package.json
@@ -18,25 +18,25 @@
|
|||||||
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
"job-totals-fixtures:local": "docker exec node-app /usr/bin/node /app/download-job-totals-fixtures.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-cloudwatch-logs": "^3.997.0",
|
"@aws-sdk/client-cloudwatch-logs": "^3.1010.0",
|
||||||
"@aws-sdk/client-elasticache": "^3.997.0",
|
"@aws-sdk/client-elasticache": "^3.1010.0",
|
||||||
"@aws-sdk/client-s3": "^3.997.0",
|
"@aws-sdk/client-s3": "^3.1010.0",
|
||||||
"@aws-sdk/client-secrets-manager": "^3.997.0",
|
"@aws-sdk/client-secrets-manager": "^3.1010.0",
|
||||||
"@aws-sdk/client-ses": "^3.997.0",
|
"@aws-sdk/client-ses": "^3.1010.0",
|
||||||
"@aws-sdk/client-sqs": "^3.997.0",
|
"@aws-sdk/client-sqs": "^3.1010.0",
|
||||||
"@aws-sdk/client-textract": "^3.997.0",
|
"@aws-sdk/client-textract": "^3.1010.0",
|
||||||
"@aws-sdk/credential-provider-node": "^3.972.12",
|
"@aws-sdk/credential-provider-node": "^3.972.21",
|
||||||
"@aws-sdk/lib-storage": "^3.997.0",
|
"@aws-sdk/lib-storage": "^3.1010.0",
|
||||||
"@aws-sdk/s3-request-presigner": "^3.997.0",
|
"@aws-sdk/s3-request-presigner": "^3.1010.0",
|
||||||
"@opensearch-project/opensearch": "^2.13.0",
|
"@opensearch-project/opensearch": "^2.13.0",
|
||||||
"@socket.io/admin-ui": "^0.5.1",
|
"@socket.io/admin-ui": "^0.5.1",
|
||||||
"@socket.io/redis-adapter": "^8.3.0",
|
"@socket.io/redis-adapter": "^8.3.0",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"aws4": "^1.13.2",
|
"aws4": "^1.13.2",
|
||||||
"axios": "^1.13.5",
|
"axios": "^1.13.6",
|
||||||
"axios-curlirize": "^2.0.0",
|
"axios-curlirize": "^2.0.0",
|
||||||
"better-queue": "^3.8.12",
|
"better-queue": "^3.8.12",
|
||||||
"bullmq": "^5.70.1",
|
"bullmq": "^5.71.0",
|
||||||
"chart.js": "^4.5.1",
|
"chart.js": "^4.5.1",
|
||||||
"cloudinary": "^2.9.0",
|
"cloudinary": "^2.9.0",
|
||||||
"compression": "^1.8.1",
|
"compression": "^1.8.1",
|
||||||
@@ -46,20 +46,20 @@
|
|||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.1",
|
||||||
"fast-xml-parser": "^5.4.1",
|
"fast-xml-parser": "^5.5.6",
|
||||||
"firebase-admin": "^13.6.1",
|
"firebase-admin": "^13.7.0",
|
||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"graphql": "^16.13.0",
|
"graphql": "^16.13.1",
|
||||||
"graphql-request": "^6.1.0",
|
"graphql-request": "^6.1.0",
|
||||||
"intuit-oauth": "^4.2.2",
|
"intuit-oauth": "^4.2.2",
|
||||||
"ioredis": "^5.9.3",
|
"ioredis": "^5.10.0",
|
||||||
"json-2-csv": "^5.5.10",
|
"json-2-csv": "^5.5.10",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"juice": "^11.1.1",
|
"juice": "^11.1.1",
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"moment-timezone": "^0.6.0",
|
"moment-timezone": "^0.6.0",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.1.1",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"node-persist": "^4.0.4",
|
"node-persist": "^4.0.4",
|
||||||
"nodemailer": "^6.10.0",
|
"nodemailer": "^6.10.0",
|
||||||
@@ -69,15 +69,15 @@
|
|||||||
"recursive-diff": "^1.0.9",
|
"recursive-diff": "^1.0.9",
|
||||||
"rimraf": "^6.1.3",
|
"rimraf": "^6.1.3",
|
||||||
"skia-canvas": "^3.0.8",
|
"skia-canvas": "^3.0.8",
|
||||||
"soap": "^1.7.1",
|
"soap": "^1.8.0",
|
||||||
"socket.io": "^4.8.3",
|
"socket.io": "^4.8.3",
|
||||||
"socket.io-adapter": "^2.5.6",
|
"socket.io-adapter": "^2.5.6",
|
||||||
"ssh2-sftp-client": "^11.0.0",
|
"ssh2-sftp-client": "^11.0.0",
|
||||||
"twilio": "^5.12.2",
|
"twilio": "^5.13.0",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"winston": "^3.19.0",
|
"winston": "^3.19.0",
|
||||||
"winston-cloudwatch": "^6.3.0",
|
"winston-cloudwatch": "^6.3.0",
|
||||||
"xml-formatter": "^3.6.7",
|
"xml-formatter": "^3.7.0",
|
||||||
"xml2js": "^0.6.2",
|
"xml2js": "^0.6.2",
|
||||||
"xmlbuilder2": "^4.0.3",
|
"xmlbuilder2": "^4.0.3",
|
||||||
"yazl": "^3.3.1"
|
"yazl": "^3.3.1"
|
||||||
@@ -86,11 +86,11 @@
|
|||||||
"@eslint/js": "^9.39.2",
|
"@eslint/js": "^9.39.2",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.2",
|
||||||
"eslint-plugin-react": "^7.37.5",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^17.3.0",
|
"globals": "^17.4.0",
|
||||||
"mock-require": "^3.0.3",
|
"mock-require": "^3.0.3",
|
||||||
"p-limit": "^3.1.0",
|
"p-limit": "^3.1.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"supertest": "^7.2.2",
|
"supertest": "^7.2.2",
|
||||||
"vitest": "^4.0.18"
|
"vitest": "^4.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user