712 lines
24 KiB
JavaScript
712 lines
24 KiB
JavaScript
import { CloseOutlined, DeleteFilled, HolderOutlined } from "@ant-design/icons";
|
|
import { closestCenter, DndContext, PointerSensor, useSensor, useSensors } from "@dnd-kit/core";
|
|
import { arrayMove, rectSortingStrategy, SortableContext, useSortable } from "@dnd-kit/sortable";
|
|
import { CSS } from "@dnd-kit/utilities";
|
|
import { Button, Form, Select, Space } from "antd";
|
|
import { ChromePicker } from "react-color";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import styled from "styled-components";
|
|
import { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils";
|
|
import ConfigListEmptyState from "../layout-form-row/config-list-empty-state.component.jsx";
|
|
import InlineValidatedFormRow from "../layout-form-row/inline-validated-form-row.component.jsx";
|
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|
import { DEFAULT_TRANSLUCENT_CARD_COLOR, getTintedCardSurfaceStyles } from "./shop-info.color.utils";
|
|
|
|
import { connect } from "react-redux";
|
|
import { createStructuredSelector } from "reselect";
|
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react";
|
|
|
|
const mapStateToProps = createStructuredSelector({
|
|
bodyshop: selectBodyshop
|
|
});
|
|
|
|
const mapDispatchToProps = () => ({
|
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
});
|
|
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoROStatusComponent);
|
|
|
|
const SelectorDiv = styled.div`
|
|
.ant-form-item .ant-select {
|
|
width: 200px;
|
|
}
|
|
|
|
.production-status-color-title-select {
|
|
min-width: 160px;
|
|
width: 100%;
|
|
}
|
|
|
|
.production-status-color-title-select .ant-select-selector {
|
|
background: transparent !important;
|
|
border: none !important;
|
|
box-shadow: none !important;
|
|
padding-inline: 0 !important;
|
|
}
|
|
|
|
.production-status-color-title-select .ant-select-selection-item,
|
|
.production-status-color-title-select .ant-select-selection-placeholder {
|
|
font-weight: 500;
|
|
}
|
|
|
|
.job-statuses-source-select .ant-select-selector {
|
|
align-items: flex-start !important;
|
|
}
|
|
|
|
.job-statuses-source-select .ant-select-selection-wrap {
|
|
gap: 4px 0;
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper {
|
|
display: inline-flex;
|
|
max-width: 100%;
|
|
margin-inline-end: 6px;
|
|
touch-action: none;
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
min-height: 30px;
|
|
min-width: 132px;
|
|
max-width: 100%;
|
|
padding-inline: 10px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--ant-color-border);
|
|
background: var(--ant-color-fill-quaternary);
|
|
justify-content: space-between;
|
|
max-width: 100%;
|
|
cursor: grab;
|
|
margin-inline-end: 0;
|
|
user-select: none;
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper .job-statuses-source-tag-handle {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--ant-color-text-tertiary);
|
|
flex: none;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item-content {
|
|
flex: 1 1 auto;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item-remove {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: none;
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 999px;
|
|
color: var(--ant-color-text-tertiary);
|
|
transition:
|
|
background 0.2s ease,
|
|
color 0.2s ease;
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper .ant-select-selection-item-remove:hover {
|
|
background: var(--ant-color-fill-secondary);
|
|
color: var(--ant-color-text);
|
|
}
|
|
|
|
.job-statuses-source-tag-wrapper--dragging {
|
|
opacity: 0.55;
|
|
}
|
|
`;
|
|
|
|
const normalizeStatuses = (statuses) => [...new Set((statuses || []).map((item) => item?.trim()).filter(Boolean))];
|
|
|
|
const getTranslatedDragRect = (active, delta) => {
|
|
const rect = active?.rect?.current?.initial || active?.rect?.current?.translated;
|
|
|
|
if (!rect) return null;
|
|
|
|
const x = delta?.x || 0;
|
|
const y = delta?.y || 0;
|
|
|
|
return {
|
|
left: rect.left + x,
|
|
right: rect.right + x,
|
|
top: rect.top + y,
|
|
bottom: rect.bottom + y,
|
|
width: rect.width,
|
|
height: rect.height
|
|
};
|
|
};
|
|
|
|
const isPointWithinRect = (point, rect) => {
|
|
if (!point || !rect) return false;
|
|
|
|
return point.x >= rect.left && point.x <= rect.right && point.y >= rect.top && point.y <= rect.bottom;
|
|
};
|
|
|
|
const DraggableStatusTag = ({ label, value, closable, onClose }) => {
|
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
id: value
|
|
});
|
|
const labelText = String(label ?? value);
|
|
|
|
return (
|
|
<span
|
|
ref={setNodeRef}
|
|
className={`job-statuses-source-tag-wrapper ${isDragging ? "job-statuses-source-tag-wrapper--dragging" : ""}`}
|
|
data-status-tag-value={value}
|
|
style={{ transform: CSS.Transform.toString(transform), transition }}
|
|
onMouseDown={(event) => {
|
|
event.stopPropagation();
|
|
}}
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
}}
|
|
{...attributes}
|
|
{...listeners}
|
|
>
|
|
<span
|
|
className="ant-select-selection-item"
|
|
onMouseDown={(event) => {
|
|
if (event.target.closest(".ant-select-selection-item-remove")) {
|
|
event.stopPropagation();
|
|
return;
|
|
}
|
|
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
}}
|
|
onClick={(event) => {
|
|
if (event.target.closest(".ant-select-selection-item-remove")) {
|
|
event.stopPropagation();
|
|
return;
|
|
}
|
|
|
|
event.stopPropagation();
|
|
}}
|
|
title={labelText}
|
|
>
|
|
<span className="job-statuses-source-tag-handle" aria-hidden>
|
|
<HolderOutlined />
|
|
</span>
|
|
<span className="ant-select-selection-item-content">{labelText}</span>
|
|
{closable ? (
|
|
<span
|
|
className="ant-select-selection-item-remove"
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
onClose?.(event);
|
|
}}
|
|
onMouseDown={(event) => {
|
|
event.stopPropagation();
|
|
}}
|
|
>
|
|
<CloseOutlined />
|
|
</span>
|
|
) : null}
|
|
</span>
|
|
</span>
|
|
);
|
|
};
|
|
|
|
const SortableStatusesSelect = ({ value, onChange, mode = "tags", options = [] }) => {
|
|
const statuses = normalizeStatuses(value);
|
|
const isTagsMode = mode === "tags";
|
|
const [knownStatuses, setKnownStatuses] = useState(statuses);
|
|
const selectWrapperRef = useRef(null);
|
|
const dragRectRef = useRef(null);
|
|
const tagSensors = useSensors(
|
|
useSensor(PointerSensor, {
|
|
activationConstraint: {
|
|
distance: 6
|
|
}
|
|
})
|
|
);
|
|
|
|
const handleStatusesChange = (nextValues) => {
|
|
const normalizedNextValues = normalizeStatuses(nextValues);
|
|
if (isTagsMode) {
|
|
setKnownStatuses((currentKnownStatuses) => normalizeStatuses([...currentKnownStatuses, ...normalizedNextValues]));
|
|
}
|
|
onChange?.(normalizedNextValues);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (isTagsMode) {
|
|
setKnownStatuses((currentKnownStatuses) => normalizeStatuses([...currentKnownStatuses, ...statuses]));
|
|
}
|
|
}, [isTagsMode, statuses]);
|
|
|
|
const shouldMoveStatusToEnd = (activeId, dragRect) => {
|
|
const selectRect =
|
|
selectWrapperRef.current?.querySelector?.(".ant-select-selector")?.getBoundingClientRect?.() ||
|
|
selectWrapperRef.current?.getBoundingClientRect?.();
|
|
if (!dragRect || !selectRect) return false;
|
|
|
|
const dragLeadingPoint = {
|
|
x: dragRect.left,
|
|
y: dragRect.top
|
|
};
|
|
const dragTrailingPoint = {
|
|
x: dragRect.right,
|
|
y: dragRect.bottom
|
|
};
|
|
|
|
if (!isPointWithinRect(dragLeadingPoint, selectRect) && !isPointWithinRect(dragTrailingPoint, selectRect)) {
|
|
return false;
|
|
}
|
|
|
|
const trailingStatus = statuses.filter((status) => status !== activeId).at(-1);
|
|
if (!trailingStatus) return false;
|
|
|
|
const trailingTagNode = selectWrapperRef.current?.querySelector?.(
|
|
`.job-statuses-source-tag-wrapper[data-status-tag-value="${CSS.escape(String(trailingStatus))}"]`
|
|
);
|
|
const trailingTagRect = trailingTagNode?.getBoundingClientRect?.();
|
|
|
|
if (!trailingTagRect) return false;
|
|
|
|
const isOnTrailingRow = dragRect.bottom >= trailingTagRect.top && dragRect.top <= trailingTagRect.bottom;
|
|
if (isOnTrailingRow) {
|
|
return dragRect.left >= trailingTagRect.right - 4;
|
|
}
|
|
|
|
return dragRect.top >= trailingTagRect.bottom - 4;
|
|
};
|
|
|
|
const handleStatusSortEnd = ({ active, over, delta }) => {
|
|
const oldIndex = statuses.indexOf(active.id);
|
|
const dragRect = dragRectRef.current || getTranslatedDragRect(active, delta);
|
|
dragRectRef.current = null;
|
|
|
|
if (oldIndex < 0) return;
|
|
|
|
if (!over) {
|
|
if (oldIndex !== statuses.length - 1 && shouldMoveStatusToEnd(active.id, dragRect)) {
|
|
onChange?.(arrayMove(statuses, oldIndex, statuses.length - 1));
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (active.id === over.id) return;
|
|
|
|
const newIndex = statuses.indexOf(over.id);
|
|
|
|
if (newIndex < 0) return;
|
|
|
|
onChange?.(arrayMove(statuses, oldIndex, newIndex));
|
|
};
|
|
|
|
const renderStatusTag = ({ label, value: tagValue, closable, onClose }) => {
|
|
return <DraggableStatusTag closable={closable} label={label} onClose={onClose} value={tagValue} />;
|
|
};
|
|
|
|
const statusSelectOptions = isTagsMode
|
|
? knownStatuses.map((status) => ({
|
|
value: status,
|
|
label: status
|
|
}))
|
|
: options;
|
|
|
|
if (statuses.length === 0) {
|
|
return (
|
|
<Select
|
|
className="job-statuses-source-select"
|
|
mode={mode}
|
|
onChange={handleStatusesChange}
|
|
options={statusSelectOptions}
|
|
value={statuses}
|
|
/>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div ref={selectWrapperRef}>
|
|
<DndContext
|
|
collisionDetection={closestCenter}
|
|
onDragCancel={() => {
|
|
dragRectRef.current = null;
|
|
}}
|
|
onDragEnd={handleStatusSortEnd}
|
|
onDragMove={({ active, delta }) => {
|
|
dragRectRef.current = getTranslatedDragRect(active, delta);
|
|
}}
|
|
sensors={tagSensors}
|
|
>
|
|
<SortableContext items={statuses} strategy={rectSortingStrategy}>
|
|
<Select
|
|
className="job-statuses-source-select"
|
|
mode={mode}
|
|
onChange={handleStatusesChange}
|
|
options={statusSelectOptions}
|
|
tagRender={renderStatusTag}
|
|
value={statuses}
|
|
/>
|
|
</SortableContext>
|
|
</DndContext>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
|
const { t } = useTranslation();
|
|
const allStatuses = normalizeStatuses(Form.useWatch(["md_ro_statuses", "statuses"], form));
|
|
const productionStatuses = Form.useWatch(["md_ro_statuses", "production_statuses"], form) || [];
|
|
const additionalBoardStatuses = Form.useWatch(["md_ro_statuses", "additional_board_statuses"], form) || [];
|
|
const productionColors = Form.useWatch(["md_ro_statuses", "production_colors"], form) || [];
|
|
const statusOptions = allStatuses;
|
|
const statusSelectOptions = statusOptions.map((item) => ({ value: item, label: item }));
|
|
const availableProductionStatuses = [...new Set([...productionStatuses, ...additionalBoardStatuses].filter(Boolean))];
|
|
|
|
const {
|
|
treatments: { Production_List_Status_Colors }
|
|
} = useTreatmentsWithConfig({
|
|
attributes: {},
|
|
names: ["Production_List_Status_Colors"],
|
|
splitKey: bodyshop.imexshopid
|
|
});
|
|
|
|
return (
|
|
<SelectorDiv id="jobstatus">
|
|
<LayoutFormRow grow header={t("bodyshop.labels.job_status_options")}>
|
|
<div>
|
|
<Form.Item
|
|
name={["md_ro_statuses", "statuses"]}
|
|
label={t("bodyshop.labels.alljobstatuses")}
|
|
required
|
|
rules={[
|
|
{
|
|
validator: async (_, value) => {
|
|
const populatedStatuses = normalizeStatuses(value);
|
|
|
|
if (populatedStatuses.length === 0) {
|
|
return Promise.reject(
|
|
new Error(
|
|
t("general.validation.required", {
|
|
label: t("bodyshop.labels.alljobstatuses")
|
|
})
|
|
)
|
|
);
|
|
}
|
|
|
|
if (populatedStatuses.length !== (value || []).filter(Boolean).length) {
|
|
return Promise.reject(new Error(t("bodyshop.errors.duplicate_job_status")));
|
|
}
|
|
}
|
|
}
|
|
]}
|
|
>
|
|
<SortableStatusesSelect />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name={["md_ro_statuses", "active_statuses"]}
|
|
label={t("bodyshop.fields.statuses.active_statuses")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
type: "array"
|
|
}
|
|
]}
|
|
>
|
|
<SortableStatusesSelect mode="multiple" options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name={["md_ro_statuses", "pre_production_statuses"]}
|
|
label={t("bodyshop.fields.statuses.pre_production_statuses")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
type: "array"
|
|
}
|
|
]}
|
|
>
|
|
<SortableStatusesSelect mode="multiple" options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name={["md_ro_statuses", "production_statuses"]}
|
|
label={t("bodyshop.fields.statuses.production_statuses")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
type: "array"
|
|
}
|
|
]}
|
|
>
|
|
<SortableStatusesSelect mode="multiple" options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name={["md_ro_statuses", "post_production_statuses"]}
|
|
label={t("bodyshop.fields.statuses.post_production_statuses")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
//message: t("general.validation.required"),
|
|
type: "array"
|
|
}
|
|
]}
|
|
>
|
|
<SortableStatusesSelect mode="multiple" options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name={["md_ro_statuses", "ready_statuses"]}
|
|
label={t("bodyshop.fields.statuses.ready_statuses")}
|
|
rules={[
|
|
{
|
|
//required: true,
|
|
//message: t("general.validation.required"),
|
|
type: "array"
|
|
}
|
|
]}
|
|
>
|
|
<SortableStatusesSelect mode="multiple" options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
name={["md_ro_statuses", "additional_board_statuses"]}
|
|
label={t("bodyshop.fields.statuses.additional_board_statuses")}
|
|
rules={[
|
|
{
|
|
//required: true,
|
|
//message: t("general.validation.required"),
|
|
type: "array"
|
|
}
|
|
]}
|
|
>
|
|
<SortableStatusesSelect mode="multiple" options={statusSelectOptions} />
|
|
</Form.Item>
|
|
</div>
|
|
</LayoutFormRow>
|
|
<LayoutFormRow grow header={t("general.actions.defaults")}>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_scheduled")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_scheduled"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_arrived")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_arrived"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_exported")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_exported"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_imported")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_imported"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_invoiced")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_invoiced"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_completed")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_completed"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_delivered")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_delivered"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
<Form.Item
|
|
label={t("bodyshop.fields.statuses.default_void")}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
name={["md_ro_statuses", "default_void"]}
|
|
>
|
|
<Select options={statusSelectOptions} />
|
|
</Form.Item>
|
|
</LayoutFormRow>
|
|
{Production_List_Status_Colors.treatment === "on" && (
|
|
<Form.List name={["md_ro_statuses", "production_colors"]}>
|
|
{(fields, { add, remove }) => {
|
|
return (
|
|
<LayoutFormRow
|
|
grow
|
|
header={t("bodyshop.fields.statuses.production_colors")}
|
|
id="production_colors"
|
|
actions={[
|
|
<Button
|
|
key="add-production-status-color"
|
|
type="primary"
|
|
block
|
|
onClick={() => {
|
|
add({
|
|
color: { ...DEFAULT_TRANSLUCENT_CARD_COLOR }
|
|
});
|
|
}}
|
|
>
|
|
{t("bodyshop.actions.add_production_status_color")}
|
|
</Button>
|
|
]}
|
|
>
|
|
<div>
|
|
{fields.length === 0 ? (
|
|
<ConfigListEmptyState actionLabel={t("bodyshop.actions.add_production_status_color")} />
|
|
) : (
|
|
<Space size="large" wrap align="start">
|
|
{fields.map((field, index) => {
|
|
const productionColor = productionColors[field.name] || {};
|
|
const productionColorSurfaceStyles = getTintedCardSurfaceStyles(productionColor.color);
|
|
const selectedProductionColorStatuses = productionColors
|
|
.map((item) => item?.status)
|
|
.filter(Boolean);
|
|
const productionColorStatusOptions = [
|
|
...new Set([productionColor.status, ...availableProductionStatuses])
|
|
]
|
|
.filter(Boolean)
|
|
.filter(
|
|
(status) =>
|
|
status === productionColor.status || !selectedProductionColorStatuses.includes(status)
|
|
);
|
|
|
|
return (
|
|
<InlineValidatedFormRow
|
|
form={form}
|
|
errorNames={[["md_ro_statuses", "production_colors", field.name, "status"]]}
|
|
key={field.key}
|
|
noDivider
|
|
title={
|
|
<Form.Item
|
|
noStyle
|
|
key={`${index}status`}
|
|
name={[field.name, "status"]}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
>
|
|
<Select
|
|
className="production-status-color-title-select"
|
|
variant="borderless"
|
|
placeholder={getFormListItemTitle(
|
|
t("jobs.fields.status"),
|
|
index,
|
|
productionColor.status
|
|
)}
|
|
options={productionColorStatusOptions.map((item) => ({
|
|
value: item,
|
|
label: item
|
|
}))}
|
|
/>
|
|
</Form.Item>
|
|
}
|
|
extra={
|
|
<Button
|
|
type="text"
|
|
danger
|
|
icon={<DeleteFilled />}
|
|
onClick={() => {
|
|
remove(field.name);
|
|
}}
|
|
/>
|
|
}
|
|
{...productionColorSurfaceStyles}
|
|
style={{ width: 260, marginBottom: 0 }}
|
|
>
|
|
<div>
|
|
<Form.Item
|
|
key={`${index}color`}
|
|
name={[field.name, "color"]}
|
|
rules={[
|
|
{
|
|
required: true
|
|
//message: t("general.validation.required"),
|
|
}
|
|
]}
|
|
>
|
|
<ColorPicker />
|
|
</Form.Item>
|
|
</div>
|
|
</InlineValidatedFormRow>
|
|
);
|
|
})}
|
|
</Space>
|
|
)}
|
|
</div>
|
|
</LayoutFormRow>
|
|
);
|
|
}}
|
|
</Form.List>
|
|
)}
|
|
</SelectorDiv>
|
|
);
|
|
}
|
|
|
|
export const ColorPicker = ({ value, onChange, ...restProps }) => {
|
|
const handleChange = (color) => {
|
|
if (onChange) onChange(color.rgb);
|
|
};
|
|
return <ChromePicker {...restProps} color={value} onChangeComplete={handleChange} />;
|
|
};
|