UNPKG

@ucheeddev/refine-antd

Version:

Custom Ant Design components for Refine framework

1,797 lines (1,745 loc) 135 kB
// src/components/account-dropdown/account-dropdown.tsx import { useCan as useCan2 } from "@refinedev/core"; import { Select } from "antd"; // src/hooks/import-modal/import-modal.ts import { useCallback, useState } from "react"; import { useModalForm } from "@refinedev/antd"; import { useAsyncImport } from "@ucheeddev/refine-core"; var useImportModal = ({ resource, args }) => { const parseArgs = args?.parseArgs; const [isOpen, setIsOpen] = useState(false); const { isLoading, triggerImport, canAccess } = useAsyncImport({ resource }); const { modalProps, formProps } = useModalForm({ action: "create" }); const openModal = useCallback(() => setIsOpen(true), []); const onCancel = useCallback(() => setIsOpen(false), []); const onFinish = useCallback( (values) => { const parsedArgs = parseArgs ? parseArgs(values) : values; setIsOpen(false); return triggerImport(parsedArgs); }, [parseArgs, triggerImport] ); return { modalProps, isLoading, formProps, onCancel, isOpen, onFinish, openModal, canAccess }; }; // src/hooks/is-url-button/is-url-button.ts import { useCallback as useCallback2, useMemo } from "react"; // src/utils/is-url/is-url.ts var isUrl = (url) => { try { new URL(url || ""); return true; } catch (e) { return false; } }; // src/utils/lighten-hex-color/lighten-hex-color.ts var lightenHexColor = (color, amount) => { amount = amount || 20; if (!color) { return ""; } if (color[0] === "#") { color = color.slice(1); } let r = parseInt(color.substring(0, 2), 16); let g = parseInt(color.substring(2, 4), 16); let b = parseInt(color.substring(4, 6), 16); r = Math.min(255, r + amount); g = Math.min(255, g + amount); b = Math.min(255, b + amount); r = r < 0 ? 0 : r; g = g < 0 ? 0 : g; b = b < 0 ? 0 : b; const newColor = "#" + ("0" + r.toString(16)).slice(-2) + ("0" + g.toString(16)).slice(-2) + ("0" + b.toString(16)).slice(-2); return newColor; }; // src/hooks/is-url-button/is-url-button.ts var useIsUrlButton = ({ url }) => { const __isUrl = useMemo(() => isUrl(url), [url]); const onClick = useCallback2(() => { if (!__isUrl) { return; } window.open(url, "_blank"); }, [url, __isUrl]); return { isUrl: __isUrl, onClick }; }; // src/hooks/ownership/ownership.tsx import { useCallback as useCallback3 } from "react"; import { useCan } from "@refinedev/core"; import { FilterOutlined, UserOutlined } from "@ant-design/icons"; import { Fragment, jsx } from "react/jsx-runtime"; var useOwnership = (resource) => { const { data: canViewOthers } = useCan({ resource, action: "view_others" }); const { data: canEditOthers } = useCan({ resource, action: "edit_others" }); const { data: canEditOwnership } = useCan({ resource, action: "edit_ownership" }); const { data: canDeleteOthers } = useCan({ resource, action: "delete_others" }); const { data: canListUsers } = useCan({ resource: "users", action: "list" }); const appendOwnerTableColumns = useCallback3( (columns, args) => { const title = args?.title || "Owner"; const viewType = args?.viewType || "selectOptionWithAvatar"; const roleFilters = args?.role ? [ { field: "role", operator: "eq", value: args.role } ] : void 0; if (!canViewOthers?.can) { return; } columns.push({ title, dataIndex: "owner", filterIcon: args?.hideFilter ? false : /* @__PURE__ */ jsx(FilterOutlined, {}), filterDropdown: args?.hideFilter ? void 0 : !!canListUsers?.can ? (props) => /* @__PURE__ */ jsx(FilterDropdown, { ...props, children: /* @__PURE__ */ jsx(OwnerDropdown, { width: 200, filters: roleFilters }) }) : void 0, render: (owner) => { return owner ? viewType === "selectOptionWithAvatar" ? /* @__PURE__ */ jsx( SelectOptionWithAvatar, { name: owner.full_name, avatarUrl: owner.photo, shape: "square" } ) : owner.full_name : "--"; } }); }, [canListUsers?.can, canViewOthers?.can] ); const renderOwnerEditField = useCallback3( ({ getFormProps, owner, hideIcon, selectProps, label, viewType = "selectOptionWithAvatar", role }) => { if (!canViewOthers?.can) { return /* @__PURE__ */ jsx(Fragment, {}); } const roleFilters = role ? [ { field: "role", operator: "eq", value: role } ] : void 0; return /* @__PURE__ */ jsx( SingleElementForm, { icon: hideIcon ? void 0 : /* @__PURE__ */ jsx(UserOutlined, { className: "colorPrimary" }), ...getFormProps({ name: "owner", label: label || "Owner", initialValue: owner?.id }), view: owner ? viewType === "selectOptionWithAvatar" ? /* @__PURE__ */ jsx( SelectOptionWithAvatar, { name: owner.full_name, avatarUrl: owner.photo, shape: "square" } ) : owner.full_name : "--", isDisabled: !canEditOwnership?.can, children: /* @__PURE__ */ jsx( OwnerDropdown, { ...selectProps || {}, filters: selectProps?.filters ?? roleFilters } ) } ); }, [canEditOwnership?.can, canViewOthers?.can] ); return { canViewOthers: !!canViewOthers?.can, canEditOthers: !!canEditOthers?.can, canEditOwnership: !!canEditOwnership?.can, canDeleteOthers: !!canDeleteOthers?.can, canListUsers: !!canListUsers?.can, appendOwnerTableColumns, renderOwnerEditField }; }; // src/hooks/theme-config/use-theme-config.ts import { useMemo as useMemo2 } from "react"; import { useServerSettingsStore } from "@ucheeddev/refine-core"; import { theme } from "antd"; // src/constants/theme-config.ts var DEFAULT_THEME_CONFIG = { // token: { // colorPrimaryText: 'rgba(0, 0, 0, 0.85)', // colorTextSecondary: 'rgba(0, 0, 0, 0.65)', // colorTextTertiary: 'rgba(0, 0, 0, 0.45)', // colorPrimary: '#6eb2cc', // colorBgContainer: '#FFFFFF', // colorBgLayout: '#F0F2F5', // colorBorderBg: '#E8E9EA', // fontFamily: // "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', 'Arial', 'Noto Sans', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'", // }, // components: { // Typography: { // colorText: 'rgba(0, 0, 0, 0.85)', // colorTextDescription: 'rgba(0, 0, 0, 0.65)', // colorTextDisabled: 'rgba(0, 0, 0, 0.45)', // }, // Card: { // colorBgContainer: '#FFFFFF', // headerBg: '#FFFFFF', // boxShadowTertiary: // '0px 1px 2px 0px #00000008,0px 1px 6px -1px #000000050px,2px 4px 0px #00000005', // }, // Table: { // colorBgContainer: '#fff', // }, // Input: { // colorBgContainer: '#fff', // }, // InputNumber: { // colorBgContainer: '#fff', // }, // Calendar: { // colorBgContainer: '#FFFFFF', // }, // Radio: { // colorBgContainer: '#fff', // }, // Select: { // colorBgContainer: '#fff', // }, // }, }; // src/hooks/theme-config/use-theme-config.ts var useThemeConfig = (props) => { const { overrides, mode } = props; const { token: tokenOverrides, ...overridesRest } = overrides || {}; const settings = useServerSettingsStore((state) => state.settings); const colorPrimary = settings?.primary_color || ""; const colorPrimaryDark = settings?.primary_color_dark || lightenHexColor(colorPrimary); const token = useMemo2(() => { const _token = { ...DEFAULT_THEME_CONFIG.token || {}, ...tokenOverrides || {} }; if (colorPrimary) { _token.colorPrimary = mode === "light" ? colorPrimary : colorPrimaryDark; } return _token; }, [tokenOverrides, colorPrimary, colorPrimaryDark, mode]); const themeConfig = useMemo2( () => ({ ...DEFAULT_THEME_CONFIG, token, ...overridesRest || {} }), [token, overridesRest] ); const customTheme = useMemo2(() => { const { darkAlgorithm, defaultAlgorithm } = theme; const algorithm = mode === "light" ? defaultAlgorithm : darkAlgorithm; const customTheme2 = { ...themeConfig, cssVar: true, algorithm, components: { Card: { headerBg: mode === "dark" ? "#1F1F1F" : "#FAFAFA" } } }; return customTheme2; }, [mode, themeConfig]); return customTheme; }; // src/hooks/use-account-select/use-account-select.ts import { useSelect } from "@refinedev/antd"; var useAccountSelect = (params) => { const { filters } = params || {}; return useSelect({ resource: "accounts", optionLabel: "name", optionValue: "id", filters, onSearch: (value) => [ { field: "search", operator: "eq", value } ] }); }; // src/hooks/use-phone-input/use-phone-input.ts import { useCallback as useCallback4 } from "react"; var usePhoneinput = () => { const validator = useCallback4( (_, value) => { if (value?.valid?.(true)) { return Promise.resolve(); } return Promise.reject("Invalid phone number"); }, [] ); const fieldProps = { rules: [ { validator } ], enableSearch: true, enableArrow: true }; const parsePhoneNumber = useCallback4((value) => { if (!value?.valid?.(true)) return ""; const fullPhoneNumber = `+${value?.countryCode || ""}${value?.areaCode || ""}${value?.phoneNumber || ""}`; return fullPhoneNumber; }, []); return { fieldProps, parsePhoneNumber }; }; // src/hooks/use-user-select/use-user-select.ts import { useSelect as useSelect2 } from "@refinedev/antd"; var useUserSelect = (params) => { const { filters } = params || {}; return useSelect2({ resource: "users", optionLabel: "full_name", optionValue: "id", filters, onSearch: (value) => [ { field: "search", operator: "eq", value } ] }); }; // src/components/select-option-with-avatar/select-option-with-avatar.tsx import { Space } from "antd"; // src/components/custom-avatar/custom-avatar.tsx import { memo } from "react"; import { Avatar as AntdAvatar } from "antd"; import { jsx as jsx2 } from "react/jsx-runtime"; var getRandomColorFromString = (text) => { const colors = [ "#ff9c6e", "#ff7875", "#ffc069", "#ffd666", "#fadb14", "#95de64", "#5cdbd3", "#69c0ff", "#85a5ff", "#b37feb", "#ff85c0" ]; let hash = 0; for (let i = 0; i < text.length; i++) { hash = text.charCodeAt(i) + ((hash << 5) - hash); hash = hash & hash; } hash = (hash % colors.length + colors.length) % colors.length; return colors[hash]; }; var getNameInitials = (name, count = 2) => { const initials = name.split(" ").map((n) => n[0]).join(""); const filtered = initials.replace(/[^a-zA-Z]/g, ""); return filtered.slice(0, count).toUpperCase(); }; var CustomAvatarComponent = ({ name, style, ...rest }) => { return /* @__PURE__ */ jsx2( AntdAvatar, { alt: name, size: "small", style: { backgroundColor: rest?.src ? "transparent" : getRandomColorFromString(name || ""), display: "flex", alignItems: "center", border: "none", ...style }, ...rest, children: getNameInitials(name || "") } ); }; var CustomAvatar = memo( CustomAvatarComponent, (prevProps, nextProps) => { return prevProps.name === nextProps.name && prevProps.src === nextProps.src; } ); // src/components/text/text.tsx import { ConfigProvider, Typography } from "antd"; // src/components/text/sizes.ts var sizes = { xs: { fontSize: 12, lineHeight: 20 / 12 }, sm: { fontSize: 14, lineHeight: 22 / 14 }, md: { fontSize: 16, lineHeight: 24 / 16 }, lg: { fontSize: 20, lineHeight: 28 / 20 }, xl: { fontSize: 24, lineHeight: 32 / 24 }, xxl: { fontSize: 30, lineHeight: 38 / 30 }, xxxl: { fontSize: 38, lineHeight: 46 / 38 }, huge: { fontSize: 46, lineHeight: 54 / 46 }, xhuge: { fontSize: 56, lineHeight: 64 / 56 }, xxhuge: { fontSize: 68, lineHeight: 76 / 68 } }; // src/components/text/text.tsx import { jsx as jsx3 } from "react/jsx-runtime"; var Text = ({ size = "sm", children, ...rest }) => { return /* @__PURE__ */ jsx3( ConfigProvider, { theme: { token: { ...sizes[size] } }, children: /* @__PURE__ */ jsx3(Typography.Text, { ...rest, children }) } ); }; // src/components/select-option-with-avatar/select-option-with-avatar.tsx import { jsx as jsx4, jsxs } from "react/jsx-runtime"; var SelectOptionWithAvatar = ({ avatarUrl, name, shape }) => { return /* @__PURE__ */ jsxs(Space, { children: [ /* @__PURE__ */ jsx4(CustomAvatar, { shape, name, src: avatarUrl }), /* @__PURE__ */ jsx4(Text, { children: name }) ] }); }; // src/components/account-dropdown/account-dropdown.tsx import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime"; var __AccountSelect = ({ width, ...props }) => { const { selectProps: accountSelectProps, query: accountQuery } = useAccountSelect(); return /* @__PURE__ */ jsx5( Select, { ...props, ...accountSelectProps, filterSort: void 0, allowClear: true, options: accountQuery?.data?.data?.map(({ id, name, logo }) => ({ value: id, label: /* @__PURE__ */ jsx5(SelectOptionWithAvatar, { name, avatarUrl: logo }) })), placeholder: "Please select account", style: { width: width || "100%" } } ); }; var AccountDropdown = ({ width, ...props }) => { const { data: canListAccount } = useCan2({ resource: "accounts", action: "list" }); if (!canListAccount?.can) { return /* @__PURE__ */ jsx5(Fragment2, {}); } return /* @__PURE__ */ jsx5(__AccountSelect, { ...props, width }); }; // src/components/avatar-group/avatar-group.tsx import { Space as Space2, Tooltip } from "antd"; import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime"; var AvatarGroup = ({ avatars, size, overlap, maxCount = 3, gap = "8px", containerStyle, avatarStyle }) => { const visibleAvatars = avatars.slice(0, maxCount); const remainingAvatars = avatars.slice(maxCount); const hasRemainingAvatars = remainingAvatars.length > 0; const shouldOverlap = overlap && avatars.length > 3; const getImageSize = (size2) => { if (typeof size2 === "number") { return shouldOverlap ? `${size2 + 4}px` : `${size2}px`; } switch (size2) { case "large": return shouldOverlap ? "44px" : "40px"; case "small": return shouldOverlap ? "28px" : "24px"; default: return shouldOverlap ? "36px" : "32px"; } }; return /* @__PURE__ */ jsxs2( "div", { style: { display: "flex", alignItems: "center", justifyContent: "flex-start", gap: shouldOverlap ? "0" : gap, ...containerStyle }, children: [ visibleAvatars.map((avatar, index) => { const transform = shouldOverlap ? `translateX(-${index * 8}px)` : void 0; return /* @__PURE__ */ jsx6(Tooltip, { title: avatar.name, children: /* @__PURE__ */ jsx6( CustomAvatar, { style: { cursor: "pointer", transform, zIndex: index, border: shouldOverlap ? "2px solid #fff" : "none", width: getImageSize(size), height: getImageSize(size), ...avatarStyle }, name: avatar?.name, src: avatar?.src, size } ) }, index); }), hasRemainingAvatars && /* @__PURE__ */ jsx6( Tooltip, { destroyTooltipOnHide: true, title: /* @__PURE__ */ jsx6(Space2, { direction: "vertical", children: remainingAvatars.map((avatar, index) => { return /* @__PURE__ */ jsxs2(Space2, { children: [ /* @__PURE__ */ jsx6( CustomAvatar, { name: avatar.name, src: avatar.src, size: "small" } ), /* @__PURE__ */ jsx6( Text, { style: { color: "#fff" }, children: avatar.name }, avatar.name ) ] }, index); }) }), children: /* @__PURE__ */ jsxs2( Text, { className: "tertiary", style: { userSelect: "none", cursor: "pointer", fontSize: "10px", lineHeight: "22px", letterSpacing: "0.5px", fontWeight: 600, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: "50%", width: getImageSize(size), height: getImageSize(size), transform: shouldOverlap ? `translateX(-${visibleAvatars.length * 8}px)` : void 0, zIndex: shouldOverlap ? visibleAvatars.length : 1, backgroundColor: "#D9D9D9", border: overlap ? "2px solid #fff" : "none" }, children: [ "+", remainingAvatars.length ] } ) } ) ] } ); }; // src/components/back-button/back-button.tsx import { LeftOutlined } from "@ant-design/icons"; import { Button, Space as Space3 } from "antd"; import { jsx as jsx7 } from "react/jsx-runtime"; var BackButton = ({ onClick, label }) => { return /* @__PURE__ */ jsx7(Space3, { children: /* @__PURE__ */ jsx7( Button, { variant: "link", onClick, icon: /* @__PURE__ */ jsx7(LeftOutlined, {}), color: "primary", style: { fontSize: 17, fontWeight: "bold" }, children: label } ) }); }; // src/components/bootstrap/bootstrap.tsx import { useNotificationProvider } from "@refinedev/antd"; import { Bootstrap as CoreBootstrap } from "@ucheeddev/refine-core"; import { Alert, App as AntdApp } from "antd"; // src/providers/config/config-provider.tsx import { createContext, useCallback as useCallback5, useContext, useEffect, useState as useState2 } from "react"; import { ConfigProvider as AntdConfigProvider } from "antd"; import { ThemeProvider } from "antd-style"; import { jsx as jsx8 } from "react/jsx-runtime"; var ConfigProviderContext = createContext( {} ); var ConfigProvider2 = ({ children, theme: themeFromProps, forceLightTheme, ...rest }) => { const colorModeFromLocalStorage = localStorage.getItem("colorMode"); const isSystemPreferenceDark = window.matchMedia( "(prefers-color-scheme: dark)" ).matches; const systemPreference = isSystemPreferenceDark ? "dark" : "light"; const [mode, setMode] = useState2( forceLightTheme ? "light" : colorModeFromLocalStorage || systemPreference ); const themeConfig = useThemeConfig({ overrides: themeFromProps, mode }); const toggleMode = useCallback5(() => { if (forceLightTheme) { setMode("light"); return; } setMode((prev) => prev === "light" ? "dark" : "light"); }, [forceLightTheme]); useEffect(() => { window.localStorage.setItem("colorMode", mode); }, [mode]); return /* @__PURE__ */ jsx8( ConfigProviderContext.Provider, { value: { toggleMode, mode, isDarkMode: mode === "dark" }, children: /* @__PURE__ */ jsx8(AntdConfigProvider, { theme: themeConfig, children: /* @__PURE__ */ jsx8(ThemeProvider, { theme: themeConfig, appearance: mode, ...rest, children }) }) } ); }; var useConfigProvider = () => { const context = useContext(ConfigProviderContext); if (!context) { throw new Error("useConfigProvider must be used within a ConfigProvider"); } return context; }; // src/components/full-screen-loading/full-screen-loading.tsx import { Spin } from "antd"; import { jsx as jsx9 } from "react/jsx-runtime"; var FullScreenLoading = () => { return /* @__PURE__ */ jsx9( Spin, { size: "large", style: { height: "100vh", width: "100%", display: "flex", alignItems: "center", justifyContent: "center" } } ); }; // src/components/bootstrap/bootstrap.tsx import { jsx as jsx10 } from "react/jsx-runtime"; var Bootstrap = ({ theme: theme6, forceLightTheme, configProviderProps, children, notificationProvider: notificationProviderFromProps, ...rest }) => { return /* @__PURE__ */ jsx10( ConfigProvider2, { theme: theme6, forceLightTheme, ...configProviderProps || {}, children: /* @__PURE__ */ jsx10(AntdApp, { children: /* @__PURE__ */ jsx10( CoreBootstrap, { ...rest, notificationProvider: notificationProviderFromProps || useNotificationProvider, Loading: FullScreenLoading, Error: ({ error }) => /* @__PURE__ */ jsx10(Alert, { type: "error", message: error, showIcon: true }), children } ) }) } ); }; // src/components/circle-icon-btn-action/circle-icon-btn-action.tsx import { Button as Button2, Flex } from "antd"; import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime"; var CircleIconButtonAction = ({ icon, label, onClick }) => { return /* @__PURE__ */ jsxs3(Flex, { vertical: true, align: "center", children: [ /* @__PURE__ */ jsx11( Button2, { shape: "circle", type: "default", size: "large", icon, onClick } ), /* @__PURE__ */ jsx11(Text, { type: "secondary", style: { marginTop: 4, fontSize: 12 }, children: label }) ] }); }; // src/components/current-user/current-user.tsx import { useGetIdentity, useLogout } from "@refinedev/core"; import { LogoutOutlined, MailOutlined, UserOutlined as UserOutlined2 } from "@ant-design/icons"; import { Button as Button3, Popover, Space as Space4 } from "antd"; import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime"; var CurrentUser = ({ beforeLogout }) => { const { data: user } = useGetIdentity(); const { mutate: logout } = useLogout(); const content = /* @__PURE__ */ jsxs4( "div", { style: { display: "flex", flexDirection: "column" }, children: [ /* @__PURE__ */ jsxs4( "div", { style: { padding: "12px 20px" }, children: [ /* @__PURE__ */ jsx12("div", { children: /* @__PURE__ */ jsxs4(Space4, { size: 8, children: [ /* @__PURE__ */ jsx12(UserOutlined2, { className: "md colorPrimary" }), /* @__PURE__ */ jsx12(Text, { strong: true, children: user?.display_name || "" }) ] }) }), /* @__PURE__ */ jsx12("div", { style: { paddingTop: 7 }, children: /* @__PURE__ */ jsxs4(Space4, { size: 12, children: [ /* @__PURE__ */ jsx12(MailOutlined, { className: "md colorPrimary" }), /* @__PURE__ */ jsx12(Text, { style: { fontSize: 14, color: "#000" }, children: user?.email || "" }) ] }) }) ] } ), /* @__PURE__ */ jsx12( "div", { style: { borderTop: "1px solid #d9d9d9", padding: "4px", display: "flex", flexDirection: "column", gap: "4px" }, children: /* @__PURE__ */ jsx12( "div", { style: { padding: "8px", display: "flex", flexDirection: "column", gap: "4px" }, children: /* @__PURE__ */ jsxs4( "div", { style: { display: "flex", flexDirection: "column", gap: "4px" }, children: [ beforeLogout, /* @__PURE__ */ jsx12( Button3, { style: { textAlign: "left" }, icon: /* @__PURE__ */ jsx12(LogoutOutlined, {}), type: "text", danger: true, block: true, onClick: () => logout(), children: "Logout" } ) ] } ) } ) } ) ] } ); return /* @__PURE__ */ jsx12( Popover, { placement: "bottomRight", content, trigger: "click", overlayInnerStyle: { padding: 0 }, overlayStyle: { zIndex: 999 }, children: /* @__PURE__ */ jsx12( CustomAvatar, { name: user?.display_name || "", src: user?.avatar_url || "", size: "default", style: { cursor: "pointer" } } ) } ); }; // src/components/custom-date-picker/custom-date-picker.tsx import { useEffect as useEffect2, useState as useState3 } from "react"; import { DatePicker } from "antd"; import dayjs from "dayjs"; import { jsx as jsx13 } from "react/jsx-runtime"; var CustomDatePicker = ({ value, onChange, onOk, format, needConfirm, showTime, showNow, allowClear, style }) => { showNow = showNow === void 0 ? false : true; allowClear = allowClear === void 0 ? false : true; needConfirm = needConfirm === void 0 ? true : false; showTime = showTime === void 0 ? false : true; format = format === void 0 ? showTime ? "DD/MM/YYYY hh:mm a" : "DD/MM/YYYY" : format; const [_value, _setValue] = useState3(void 0); useEffect2(() => { if (!value) { _setValue(void 0); return; } _setValue(dayjs(value)); }, [value]); return /* @__PURE__ */ jsx13( DatePicker, { value: _value, onChange, onOk, allowClear, needConfirm, showNow, showTime, format, style } ); }; // src/components/custom-tabs/custom-tabs.tsx import { Tabs } from "antd"; import { jsx as jsx14 } from "react/jsx-runtime"; var CustomTabs = ({ items, defaultActiveKey = "1", onChange, style }) => { return /* @__PURE__ */ jsx14( Tabs, { defaultActiveKey, items, onChange, style } ); }; // src/components/custom-time-picker/custom-time-picker.tsx import { useEffect as useEffect3, useState as useState4 } from "react"; import { DatePicker as DatePicker2 } from "antd"; import dayjs2 from "dayjs"; import { jsx as jsx15 } from "react/jsx-runtime"; var CustomTimePicker = ({ value, onChange, onOk, format, needConfirm, allowClear, style }) => { needConfirm = needConfirm === void 0 ? true : false; allowClear = allowClear === void 0 ? false : true; format = format === void 0 ? "hh:mm a" : format; const [_value, _setValue] = useState4(void 0); useEffect3(() => { if (!value) { _setValue(void 0); return; } _setValue(dayjs2(value)); }, [value]); return /* @__PURE__ */ jsx15( DatePicker2.TimePicker, { value: _value, onChange, onOk, allowClear, needConfirm, format, style } ); }; // src/components/email-link/email-link.tsx import { Typography as Typography2 } from "antd"; import { jsx as jsx16 } from "react/jsx-runtime"; var { Link } = Typography2; var EmailLink = ({ email, style }) => { return /* @__PURE__ */ jsx16(Link, { href: `mailto:${email}`, target: "_blank", style, children: email }); }; // src/components/field-form-title/field-form-title.tsx import { Space as Space5, Typography as Typography3 } from "antd"; import { jsx as jsx17, jsxs as jsxs5 } from "react/jsx-runtime"; var FieldFormTitle = ({ title, Icon: Icon2, iconProps, titleStyle, ...props }) => { return /* @__PURE__ */ jsxs5(Space5, { size: 11, ...props, children: [ Icon2 && /* @__PURE__ */ jsx17(Icon2, { className: "md colorPrimary", ...iconProps }), /* @__PURE__ */ jsx17( Typography3.Text, { style: { fontSize: "15px", color: "darkslategray", ...titleStyle }, children: title } ) ] }); }; // src/components/filter-dropdown/filter-dropdown.tsx import React2, { useCallback as useCallback6, useMemo as useMemo3 } from "react"; import { useTranslate } from "@refinedev/core"; import { ClearOutlined, FilterOutlined as FilterOutlined2 } from "@ant-design/icons"; import { Button as Button4, Space as Space6 } from "antd"; import dayjs3 from "dayjs"; import { jsx as jsx18, jsxs as jsxs6 } from "react/jsx-runtime"; var FilterDropdown = (props) => { const { setSelectedKeys, confirm, clearFilters, mapValue = (value) => value, selectedKeys, children } = props; const translate = useTranslate(); const clearFilter = useCallback6(() => { if (clearFilters) { clearFilters(); confirm?.(); } }, [clearFilters, confirm]); const onFilter = useCallback6(() => { let keys; if (typeof selectedKeys === "number") { keys = `${selectedKeys}`; } else if (dayjs3.isDayjs(selectedKeys)) { keys = [selectedKeys.toISOString()]; } else { keys = selectedKeys; } setSelectedKeys(keys); confirm?.(); }, [selectedKeys, setSelectedKeys, confirm]); const onChange = useCallback6( (e) => { if (typeof e === "object") { if (Array.isArray(e)) { const mappedValue3 = mapValue(e, "onChange"); setSelectedKeys(mappedValue3); confirm?.(); return; } const changeEvent = !e || !e.target || dayjs3.isDayjs(e) ? { target: { value: e } } : e; const { target } = changeEvent; const mappedValue2 = mapValue(target.value, "onChange"); setSelectedKeys(mappedValue2); return; } const mappedValue = mapValue(e, "onChange"); if (typeof mappedValue === "number") { setSelectedKeys(`${mappedValue}`); } else { setSelectedKeys(mappedValue); } confirm?.(); }, [confirm, mapValue, setSelectedKeys] ); const childrenWithProps = useMemo3(() => { return React2.Children.map(children, (child) => { if (React2.isValidElement(child)) { return React2.cloneElement(child, { onChange, value: mapValue(selectedKeys, "value") }); } return child; }); }, [children, mapValue, onChange, selectedKeys]); return /* @__PURE__ */ jsxs6( "div", { style: { padding: 10, display: "flex", flexDirection: "column", alignItems: "flex-end" }, children: [ /* @__PURE__ */ jsx18("div", { style: { marginBottom: 12 }, children: childrenWithProps }), /* @__PURE__ */ jsxs6(Space6, { children: [ /* @__PURE__ */ jsxs6( Button4, { style: { backgroundColor: "#ffff" }, danger: true, size: "small", onClick: () => clearFilter(), children: [ /* @__PURE__ */ jsx18(ClearOutlined, {}), translate("buttons.clear", "Clear") ] } ), /* @__PURE__ */ jsxs6(Button4, { type: "primary", size: "small", onClick: () => onFilter(), children: [ /* @__PURE__ */ jsx18(FilterOutlined2, {}), " ", translate("buttons.filter", "Filter") ] }) ] }) ] } ); }; // src/components/form-loading-skeleton/form-loading-skeleton.tsx import { Col, Row, Skeleton } from "antd"; import { Fragment as Fragment3, jsx as jsx19 } from "react/jsx-runtime"; var FormLoadingSkeleton = () => { return /* @__PURE__ */ jsx19(Fragment3, { children: [...Array(4)].map((_, rowIndex) => /* @__PURE__ */ jsx19(Row, { gutter: 24, style: { marginBottom: 16 }, children: [...Array(2)].map((_2, colIndex) => /* @__PURE__ */ jsx19( Col, { span: 12, style: { display: "flex", flexDirection: "column", padding: 4 }, children: /* @__PURE__ */ jsx19(Skeleton.Input, { active: true, style: { width: "100%" }, size: "small" }) }, colIndex )) }, rowIndex)) }); }; // src/components/formatted-number-field/formatted-number-field.tsx import { NumberField } from "@refinedev/antd"; import { jsx as jsx20 } from "react/jsx-runtime"; var FormattedNumberField = ({ value, currency = "USD", ...rest }) => { return /* @__PURE__ */ jsx20( NumberField, { ...rest, value, options: { style: "currency", currency }, prefix: "2" } ); }; // src/components/full-width-layout/full-width-layout.tsx import { ThemedLayoutV2 } from "@refinedev/antd"; // src/components/header-with-menu/header-with-menu.tsx import { Link as Link2, useMenu } from "@refinedev/core"; import { Button as Button5, Flex as Flex2, Layout, Menu as Menu2 } from "antd"; import classNames from "classnames"; // src/components/icons/icon-invoicer.tsx import { jsx as jsx21, jsxs as jsxs7 } from "react/jsx-runtime"; var IconInvoicerLogo = (props) => { const { mode } = useConfigProvider(); const isDarkMode = mode === "dark"; return /* @__PURE__ */ jsxs7( "svg", { xmlns: "http://www.w3.org/2000/svg", width: 24, height: 20, viewBox: "0 0 24 20", fill: "none", ...props, children: [ /* @__PURE__ */ jsx21("title", { children: "Invoicer" }), /* @__PURE__ */ jsx21( "path", { fill: isDarkMode ? "#3E2069" : "#EFDBFF", d: "m0 0 24 4v12L0 20V0Z" } ), /* @__PURE__ */ jsx21( "path", { fill: isDarkMode ? "#CDA8F0" : "#531DAB", d: "M4.5 14.015 7.54 20 24 0 4.5 14.015Z" } ), /* @__PURE__ */ jsx21( "path", { fill: isDarkMode ? "#EBD7FA" : "#22075E", d: "M7.54 20v-4l2.681 1.267L7.541 20Z" } ), /* @__PURE__ */ jsx21( "path", { fill: isDarkMode ? "#AB7AE0" : "#9254DE", d: "m0 11 4.5 3.015L24 0 7.54 16 16 20l8-20L0 11Z" } ) ] } ); }; // src/components/icons/icon-moon.tsx import { jsx as jsx22, jsxs as jsxs8 } from "react/jsx-runtime"; var IconMoon = () => /* @__PURE__ */ jsxs8( "svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon icon-tabler icon-tabler-moon-stars", width: 20, height: 20, viewBox: "0 0 24 24", strokeWidth: 2, stroke: "currentColor", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [ /* @__PURE__ */ jsx22("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), /* @__PURE__ */ jsx22("path", { d: "M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" }), /* @__PURE__ */ jsx22("path", { d: "M17 4a2 2 0 0 0 2 2a2 2 0 0 0 -2 2a2 2 0 0 0 -2 -2a2 2 0 0 0 2 -2" }), /* @__PURE__ */ jsx22("path", { d: "M19 11h2m-1 -1v2" }) ] } ); // src/components/icons/icon-sun.tsx import { jsx as jsx23, jsxs as jsxs9 } from "react/jsx-runtime"; var IconSun = () => /* @__PURE__ */ jsxs9( "svg", { xmlns: "http://www.w3.org/2000/svg", className: "icon icon-tabler icon-tabler-sun", width: 20, height: 20, viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", fill: "none", strokeLinecap: "round", strokeLinejoin: "round", children: [ /* @__PURE__ */ jsx23("path", { stroke: "none", d: "M0 0h24v24H0z", fill: "none" }), /* @__PURE__ */ jsx23("circle", { cx: 12, cy: 12, r: 4 }), /* @__PURE__ */ jsx23("path", { d: "M3 12h1m8 -9v1m8 8h1m-9 8v1m-6.4 -15.4l.7 .7m12.1 -.7l-.7 .7m0 11.4l.7 .7m-12.1 -.7l-.7 .7" }) ] } ); // src/components/logo/logo.tsx import { jsx as jsx24 } from "react/jsx-runtime"; var WIDTH = 120; var HEIGHT = 120; var Logo = ({ height, width, src, alt }) => { height = height || HEIGHT; width = width || WIDTH; if (!src) { return; } return /* @__PURE__ */ jsx24( "img", { alt, src, width, height, style: { maxWidth: "100%", maxHeight: "100%" } } ); }; // src/components/sider/menu-items.tsx import { CanAccess, pickNotDeprecated } from "@refinedev/core"; import { UnorderedListOutlined } from "@ant-design/icons"; import { Menu } from "antd"; import { Fragment as Fragment4, jsx as jsx25, jsxs as jsxs10 } from "react/jsx-runtime"; var renderMenuItems = ({ tree, selectedKey, Link: Link5, siderCollapsed }) => { return /* @__PURE__ */ jsx25(Fragment4, { children: tree.map((item) => { const { icon, label, route, key, name, children, parentName, meta, options } = item; if (children.length > 0) { return /* @__PURE__ */ jsx25( CanAccess, { resource: name.toLowerCase(), action: "list", params: { resource: item }, children: /* @__PURE__ */ jsx25( Menu.SubMenu, { icon: icon ?? /* @__PURE__ */ jsx25(UnorderedListOutlined, {}), title: label, children: renderMenuItems({ tree: children, selectedKey, Link: Link5, siderCollapsed }) } ) }, key ); } const isSelected = key === selectedKey; const isRoute = !(pickNotDeprecated(meta?.parent, options?.parent, parentName) !== void 0 && children.length === 0); return /* @__PURE__ */ jsx25( CanAccess, { resource: name.toLowerCase(), action: "list", params: { resource: item }, children: /* @__PURE__ */ jsxs10( Menu.Item, { icon: icon ?? (isRoute && /* @__PURE__ */ jsx25(UnorderedListOutlined, {})), children: [ /* @__PURE__ */ jsx25(Link5, { to: route ?? "", children: label }), !siderCollapsed && isSelected && /* @__PURE__ */ jsx25("div", { className: "ant-menu-tree-arrow" }) ] }, key ) }, key ); }) }); }; // src/components/header-with-menu/styled.tsx import { createStyles } from "antd-style"; var useStyles = createStyles(({ css, token, isDarkMode, responsive }) => { return { headerContainer: { backgroundColor: token.colorBgElevated, padding: "0 16px", minHeight: "48px", height: "max-content" }, flexContainer: { width: "100%", maxWidth: "1440px", margin: "0 auto", height: "100%" }, headerTitleRefine: { fontFamily: "Bricolage Grotesque, sans-serif", color: isDarkMode ? token["purple-10"] : token["purple-8"] }, headerTitleInvoicer: { fontFamily: "Bricolage Grotesque, sans-serif", color: isDarkMode ? token["purple-10"] : token["purple-8"], fontWeight: 700 }, tabs: css` margin-left: auto; margin-right: auto; width: 600px; ${responsive.mobile} { width: 320px; } `, inputSuffix: { width: "20px", height: "20px", display: "flex", alignItems: "center", justifyContent: "center", backgroundColor: token.colorBgTextHover, color: token.colorTextDisabled, borderRadius: "4px", fontSize: "12px" }, inputPrefix: { color: token.colorTextPlaceholder, marginRight: "4px" }, languageSwitchText: { color: token.colorTextSecondary }, languageSwitchIcon: { color: token.colorTextTertiary, width: "10px" }, themeSwitch: { display: "flex", alignItems: "center", justifyContent: "center", height: "32px", width: "32px", borderRadius: "50%", cursor: "pointer", flexShrink: 0, backgroundColor: isDarkMode ? token.colorBgLayout : token.colorBgTextHover }, rightSlot: { marginLeft: "auto", "@media (max-width: 1000px)": { padding: "16px" } } }; }); // src/components/header-with-menu/header-with-menu.tsx import { Fragment as Fragment5, jsx as jsx26, jsxs as jsxs11 } from "react/jsx-runtime"; var HeaderWithMenu = ({ Search, logo, logoWidth, logoHeight, hideThemeButton, beforeLogout, disabledOverflow, overflowedIndicator, style, className }) => { const { menuItems, selectedKey, defaultOpenKeys } = useMenu(); const { styles: styles2 } = useStyles(); const { isDarkMode, toggleMode } = useConfigProvider(); return /* @__PURE__ */ jsx26( Layout.Header, { className: classNames("print-hidden", styles2.headerContainer, className), style, children: /* @__PURE__ */ jsxs11( Flex2, { align: "center", justify: "space-between", wrap: "wrap", className: styles2.flexContainer, children: [ /* @__PURE__ */ jsxs11(Flex2, { align: "center", wrap: "wrap", children: [ logo ? /* @__PURE__ */ jsx26( Link2, { go: { to: "/" }, children: /* @__PURE__ */ jsx26(Logo, { src: logo, width: logoWidth, height: logoHeight }) } ) : /* @__PURE__ */ jsx26(Fragment5, {}), /* @__PURE__ */ jsx26( Menu2, { selectedKeys: selectedKey ? [selectedKey] : [], defaultOpenKeys, mode: "horizontal", className: styles2.tabs, overflowedIndicator, disabledOverflow, children: renderMenuItems({ tree: menuItems, Link: Link2, selectedKey, siderCollapsed: false }) } ) ] }), /* @__PURE__ */ jsxs11(Flex2, { align: "center", gap: 32, className: styles2.rightSlot, children: [ Search ? /* @__PURE__ */ jsx26(Search, {}) : null, !hideThemeButton && /* @__PURE__ */ jsx26( Button5, { className: styles2.themeSwitch, type: "text", icon: !isDarkMode ? /* @__PURE__ */ jsx26(IconMoon, {}) : /* @__PURE__ */ jsx26(IconSun, {}), onClick: () => { toggleMode(); } } ), /* @__PURE__ */ jsx26(CurrentUser, { beforeLogout }) ] }) ] } ) } ); }; // src/components/full-width-layout/full-width-layout.tsx import { jsx as jsx27 } from "react/jsx-runtime"; var FullWidthLayout = ({ headerProps, overflowedIndicator, disabledOverflow, children, className, contentClassName, contentStyle, style }) => { return /* @__PURE__ */ jsx27( ThemedLayoutV2, { Header: (layoutHeaderProps) => /* @__PURE__ */ jsx27( HeaderWithMenu, { ...headerProps || {}, overflowedIndicator, disabledOverflow, ...layoutHeaderProps } ), Sider: () => null, children: /* @__PURE__ */ jsx27( "div", { style: { width: "100%", ...style }, className, children: /* @__PURE__ */ jsx27( "div", { style: { maxWidth: "100%", margin: "0 auto", ...contentStyle }, className: contentClassName, children } ) } ) } ); }; // src/components/header/header.tsx import { Layout as Layout2, Space as Space7, theme as theme2 } from "antd"; import { jsx as jsx28, jsxs as jsxs12 } from "react/jsx-runtime"; var { useToken } = theme2; var HeaderWithSearch = ({ Search, beforeLogout, beforeCurrentUser }) => { const { token } = useToken(); const headerStyles = { backgroundColor: token.colorBgElevated, display: "flex", justifyContent: Search ? "space-between" : "flex-end", alignItems: "center", padding: "0px 24px", height: "64px", position: "sticky", top: 0, zIndex: 999 }; return /* @__PURE__ */ jsxs12(Layout2.Header, { style: headerStyles, children: [ Search ? /* @__PURE__ */ jsx28(Search, {}) : null, /* @__PURE__ */ jsxs12(Space7, { align: "center", size: "middle", children: [ beforeCurrentUser, /* @__PURE__ */ jsx28(CurrentUser, { beforeLogout }) ] }) ] }); }; // src/components/image-upload/image-upload.tsx import { Image, Upload } from "antd"; // src/components/upload-button/upload-button.tsx import { jsx as jsx29, jsxs as jsxs13 } from "react/jsx-runtime"; var UploadButton = ({ icon, label }) => { return /* @__PURE__ */ jsxs13("button", { style: { border: 0, background: "none" }, type: "button", children: [ icon, /* @__PURE__ */ jsx29("div", { style: { marginTop: 8 }, children: label }) ] }); }; // src/components/image-upload/image-upload.tsx import { Fragment as Fragment6, jsx as jsx30, jsxs as jsxs14 } from "react/jsx-runtime"; var ImageUpload = ({ listType, fileList, onPreview, onChange, previewImage, previewImageOpen, onVisibleChange, afterOpenChange, onRemove, multiple, disabled, ...rest }) => { return /* @__PURE__ */ jsxs14(Fragment6, { children: [ /* @__PURE__ */ jsx30( Upload, { listType: listType || "picture-card", fileList, onPreview, onChange, beforeUpload: () => false, onRemove, multiple, disabled, ...rest, children: fileList && fileList.length >= 1 && !multiple ? null : /* @__PURE__ */ jsx30(UploadButton, {}) } ), previewImage && /* @__PURE__ */ jsx30( Image, { wrapperStyle: { display: "none" }, preview: { visible: previewImageOpen, onVisibleChange, afterOpenChange }, src: previewImage } ) ] }); }; // src/components/import-button/import-button.tsx import { UploadOutlined } from "@ant-design/icons"; import { Button as Button6 } from "antd"; import { useTheme } from "antd-style"; import { jsx as jsx31 } from "react/jsx-runtime"; var ImportButton = ({ canAccess, openModal, title }) => { const token = useTheme(); if (!canAccess) { return null; } return /* @__PURE__ */ jsx31( Button6, { icon: /* @__PURE__ */ jsx31(UploadOutlined, {}), style: { backgroundColor: token.colorPrimary, color: "#FFFF", border: "none" }, onClick: openModal, children: title || "Upload" } ); }; // src/components/import-modal/import-modal.tsx import { CloseOutlined, FileAddOutlined, InboxOutlined } from "@ant-design/icons"; import { useUpload } from "@ucheeddev/react-web"; import { Form, Modal } from "antd"; import Dragger from "antd/es/upload/Dragger"; import { jsx as jsx32, jsxs as jsxs15 } from "react/jsx-runtime"; var ImportModal = ({ modalProps, formProps, onFinish, onCancel, isOpen, isLoading, title, extra, width }) => { const attachmentsUploadProps = useUpload(); return /* @__PURE__ */ jsx32( Modal, { ...modalProps, onCancel, closeIcon: /* @__PURE__ */ jsx32(CloseOutlined, {}), open: isOpen, width: width || 460, title: title || "Import Records", okText: "Import", okButtonProps: { ...modalProps.okButtonProps || {}, loading: isLoading }, children: /* @__PURE__ */ jsxs15( Form, { ...formProps, layout: "vertical", onFinish: (values) => { const formData = new FormData(); for (const key in values) { formData.set(key, values[key]); } formData.set( "import_file", attachmentsUploadProps.file || "" ); return onFinish?.(formData); }, children: [ extra, /* @__PURE__ */ jsxs15(Form.Item, { name: "import_file", children: [ /* @__PURE__ */ jsx32( FieldFormTitle, { title: "Drag a File", Icon: FileAddOutlined, titleStyle: { fontSize: 16, fontWeight: 500 } } ), /* @__PURE__ */ jsxs15(Dragger, { ...attachmentsUploadProps, children: [ /* @__PURE__ */ jsx32("p", { className: "