diff --git a/client/src/components/layout-form-row/inline-form-row-title.utils.js b/client/src/components/layout-form-row/inline-form-row-title.utils.js new file mode 100644 index 000000000..df659026a --- /dev/null +++ b/client/src/components/layout-form-row/inline-form-row-title.utils.js @@ -0,0 +1,66 @@ +export const inlineFormRowTitleStyles = Object.freeze({ + input: Object.freeze({ + background: "var(--imex-form-title-input-bg)", + border: "1px solid var(--imex-form-title-input-border)", + borderRadius: 6, + paddingInline: 10, + paddingBlock: 4, + lineHeight: 1.3 + }), + row: Object.freeze({ + display: "flex", + gap: 4, + flexWrap: "wrap", + alignItems: "center", + width: "100%", + paddingInline: 4 + }), + group: Object.freeze({ + display: "flex", + alignItems: "center", + gap: 6, + minWidth: 0 + }), + label: Object.freeze({ + color: "var(--ant-color-text)", + fontSize: "var(--ant-font-size)", + fontWeight: 500, + whiteSpace: "nowrap" + }), + handle: Object.freeze({ + color: "var(--ant-color-text-tertiary)", + fontSize: 14, + flex: "0 0 auto", + marginRight: 4 + }), + separator: Object.freeze({ + width: 1, + height: 18, + background: "color-mix(in srgb, var(--imex-form-surface-border) 78%, transparent)", + borderRadius: 999, + flex: "0 0 auto", + marginInline: 2 + }), + text: Object.freeze({ + whiteSpace: "nowrap", + fontWeight: 500, + fontSize: "var(--ant-font-size-lg)", + lineHeight: 1.2 + }) +}); + +export const INLINE_TITLE_INPUT_STYLE = inlineFormRowTitleStyles.input; +export const INLINE_TITLE_ROW_STYLE = inlineFormRowTitleStyles.row; +export const INLINE_TITLE_GROUP_STYLE = inlineFormRowTitleStyles.group; +export const INLINE_TITLE_LABEL_STYLE = inlineFormRowTitleStyles.label; +export const INLINE_TITLE_HANDLE_STYLE = inlineFormRowTitleStyles.handle; +export const INLINE_TITLE_SEPARATOR_STYLE = inlineFormRowTitleStyles.separator; +export const INLINE_TITLE_TEXT_STYLE = inlineFormRowTitleStyles.text; + +export const INLINE_FORM_ROW_WRAP_TITLE_STYLES = Object.freeze({ + title: Object.freeze({ + whiteSpace: "normal", + overflow: "visible", + textOverflow: "unset" + }) +}); diff --git a/client/src/components/layout-form-row/layout-form-row.component.jsx b/client/src/components/layout-form-row/layout-form-row.component.jsx index c45e5c75b..ade2cbaab 100644 --- a/client/src/components/layout-form-row/layout-form-row.component.jsx +++ b/client/src/components/layout-form-row/layout-form-row.component.jsx @@ -1,5 +1,6 @@ import { Card, Col, Row } from "antd"; import { Children, isValidElement } from "react"; +import { INLINE_FORM_ROW_WRAP_TITLE_STYLES } from "./inline-form-row-title.utils.js"; import "./layout-form-row.styles.scss"; export default function LayoutFormRow({ @@ -7,6 +8,8 @@ export default function LayoutFormRow({ children, grow = false, noDivider = false, + titleOnly = false, + wrapTitle = false, gutter, rowProps, @@ -19,10 +22,14 @@ export default function LayoutFormRow({ ...cardProps }) { const items = Children.toArray(children).filter(Boolean); - if (items.length === 0) return null; const isCompactRow = noDivider; const title = !noDivider && header ? header : undefined; + const resolvedTitle = cardProps.title ?? title; + const isHeaderOnly = titleOnly || items.length === 0; + const hideBody = isHeaderOnly; + + if (items.length === 0 && !resolvedTitle) return null; const resolvedGutter = gutter ?? [16, isCompactRow ? 8 : 16]; const bg = surfaceBg ?? (surface ? "var(--imex-form-surface)" : undefined); @@ -31,13 +38,15 @@ export default function LayoutFormRow({ const mergedStyles = mergeSemanticStyles( { + ...(wrapTitle ? INLINE_FORM_ROW_WRAP_TITLE_STYLES : null), header: { - paddingInline: isCompactRow ? 12 : 16, + paddingInline: isHeaderOnly ? 8 : isCompactRow ? 12 : 16, background: headBg, borderBottomColor: borderColor }, body: { - padding: isCompactRow ? 12 : 16, + padding: hideBody ? 0 : isCompactRow ? 12 : 16, + display: hideBody ? "none" : undefined, background: bg } }, @@ -45,31 +54,12 @@ export default function LayoutFormRow({ ); const baseCardStyle = { - marginBottom: isCompactRow ? "8px" : ".8rem", + marginBottom: isHeaderOnly ? "0" : isCompactRow ? "8px" : ".8rem", ...(bg ? { background: bg } : null), // ensures the “circled area” is tinted ...(borderColor ? { borderColor } : null), ...cardProps.style }; - // single child => just render it - if (items.length === 1) { - return ( - - {items[0]} - - ); - } - const count = items.length; // Modern responsive strategy leveraging Ant Design 6: @@ -133,22 +123,32 @@ export default function LayoutFormRow({ return ( - - {items.map((child, idx) => ( - - {child} - + {!isHeaderOnly && + (items.length === 1 ? ( + items[0] + ) : ( + + {items.map((child, idx) => ( + + {child} + + ))} + ))} - ); } @@ -162,6 +162,7 @@ function mergeSemanticStyles(defaults, userStyles) { return { ...defaults, ...computed, + title: { ...(defaults.title || {}), ...(computed.title || {}) }, header: { ...defaults.header, ...(computed.header || {}) }, body: { ...defaults.body, ...(computed.body || {}) } }; @@ -171,6 +172,7 @@ function mergeSemanticStyles(defaults, userStyles) { return { ...defaults, ...userStyles, + title: { ...(defaults.title || {}), ...(userStyles.title || {}) }, header: { ...defaults.header, ...(userStyles.header || {}) }, body: { ...defaults.body, ...(userStyles.body || {}) } }; diff --git a/client/src/components/layout-form-row/layout-form-row.styles.scss b/client/src/components/layout-form-row/layout-form-row.styles.scss index 9bc77277b..4284c5223 100644 --- a/client/src/components/layout-form-row/layout-form-row.styles.scss +++ b/client/src/components/layout-form-row/layout-form-row.styles.scss @@ -13,6 +13,8 @@ --imex-form-surface: #fafafa; /* subtle contrast vs white page */ --imex-form-surface-head: #f5f5f5; /* header strip */ --imex-form-surface-border: #d9d9d9; /* matches AntD-ish border */ + --imex-form-title-input-bg: rgba(255, 255, 255, 0.96); + --imex-form-title-input-border: rgba(0, 0, 0, 0.08); } /* Pick the selector that matches your app and remove the rest */ @@ -20,6 +22,8 @@ html[data-theme="dark"] { --imex-form-surface: rgba(255, 255, 255, 0.01); /* subtle lift off page bg */ --imex-form-surface-head: rgba(255, 255, 255, 0.06); /* slightly stronger for header strip */ --imex-form-surface-border: rgba(5, 5, 5, 0.12); + --imex-form-title-input-bg: rgba(255, 255, 255, 0.12); + --imex-form-title-input-border: rgba(255, 255, 255, 0.2); } .imex-form-row { @@ -58,6 +62,54 @@ html[data-theme="dark"] { } } + &.imex-form-row--title-only { + .ant-card-head { + min-height: auto; + padding-inline: 6px; + padding-block: 0; + border-radius: inherit; + } + + .ant-card-head-wrapper { + gap: 2px; + align-items: center; + } + + .ant-card-head-title, + .ant-card-extra { + padding-block: 0; + display: flex; + align-items: center; + } + + .ant-card-head-title { + white-space: normal; + overflow: visible; + text-overflow: unset; + font-size: var(--ant-font-size); + line-height: 1.1; + padding-inline: 4px; + } + + .ant-card-body { + display: none; + padding: 0; + } + + .ant-input, + .ant-input-number, + .ant-input-affix-wrapper, + .ant-select-selector, + .ant-picker { + background: var(--imex-form-title-input-bg); + border-color: var(--imex-form-title-input-border); + } + + .ant-input-number-input { + background: transparent; + } + } + .ant-card-body { background: var(--imex-form-surface); } @@ -68,7 +120,7 @@ html[data-theme="dark"] { /* Optional: tighter spacing on phones for better space usage */ @media (max-width: 575px) { - .ant-card-head { + &:not(.imex-form-row--title-only) .ant-card-head { padding-inline: 12px; padding-block: 12px; } @@ -89,10 +141,14 @@ html[data-theme="dark"] { width: 100%; } - .ant-form-item:has(> .imex-form-row--compact) { + .ant-form-item:has(.imex-form-row--compact) { margin-bottom: 8px; } + .ant-form-item:has(.imex-form-row--title-only) { + margin-bottom: 4px; + } + /* Better form item spacing on mobile */ @media (max-width: 575px) { .ant-form-item { diff --git a/client/src/components/shop-employees/shop-employees-form.component.jsx b/client/src/components/shop-employees/shop-employees-form.component.jsx index e9e87ec89..89cc115ff 100644 --- a/client/src/components/shop-employees/shop-employees-form.component.jsx +++ b/client/src/components/shop-employees/shop-employees-form.component.jsx @@ -1,4 +1,4 @@ -import { DeleteFilled } from "@ant-design/icons"; +import { DeleteFilled, HolderOutlined } from "@ant-design/icons"; import { useApolloClient, useMutation, useQuery } from "@apollo/client/react"; import { useTreatmentsWithConfig } from "@splitsoftware/splitio-react"; import { Button, Card, Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd"; @@ -27,8 +27,16 @@ import dayjs from "../../utils/day"; import AlertComponent from "../alert/alert.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component"; -import { getFormListItemTitle } from "../form-list-move-arrows/form-list-item-title.utils"; import LayoutFormRow from "../layout-form-row/layout-form-row.component"; +import { + INLINE_TITLE_GROUP_STYLE, + INLINE_TITLE_HANDLE_STYLE, + INLINE_TITLE_INPUT_STYLE, + INLINE_TITLE_LABEL_STYLE, + INLINE_TITLE_ROW_STYLE, + INLINE_TITLE_SEPARATOR_STYLE, + INLINE_TITLE_TEXT_STYLE +} from "../layout-form-row/inline-form-row-title.utils.js"; import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component.jsx"; @@ -42,17 +50,16 @@ const mapDispatchToProps = () => ({ export function ShopEmployeesFormComponent({ bodyshop }) { const { t } = useTranslation(); const [form] = useForm(); - const employeeRates = Form.useWatch(["rates"], form) || []; const employeeNumber = Form.useWatch("employee_number", form); const firstName = Form.useWatch("first_name", form); const lastName = Form.useWatch("last_name", form); const employeeOptionsColProps = { xs: 24, sm: 12, - md: 8, - lg: { flex: "0 0 320px" }, - xl: { flex: "0 0 280px" }, - xxl: { flex: "0 0 380px" } + md: 12, + lg: 8, + xl: 8, + xxl: 8 }; const history = useNavigate(); const search = queryString.parse(useLocation().search); @@ -194,161 +201,200 @@ export function ShopEmployeesFormComponent({ bodyshop }) { } >