From 5bf265ed46dec9dc5d0e8e879cd1be376ebee378 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 19 Feb 2025 01:41:06 -0500 Subject: [PATCH] Shadcn conversion + styling, match columns page --- .../components/settings/DataManagement.tsx | 2 +- .../MatchColumnsStep/MatchColumnsStep.tsx | 20 +- .../components/ColumnGrid.tsx | 128 +++++++----- .../components/TemplateColumn.tsx | 189 +++++++++++------- .../components/UserTableColumn.tsx | 66 +++--- .../src/steps/Steps.tsx | 10 +- .../steps/UploadStep/components/DropZone.tsx | 8 +- .../lib/react-spreadsheet-import/src/theme.ts | 4 +- inventory/src/pages/import/Import.tsx | 181 ++++++++++------- mountremote.command | 4 +- 10 files changed, 355 insertions(+), 257 deletions(-) diff --git a/inventory/src/components/settings/DataManagement.tsx b/inventory/src/components/settings/DataManagement.tsx index 52d6e5a..4e97413 100644 --- a/inventory/src/components/settings/DataManagement.tsx +++ b/inventory/src/components/settings/DataManagement.tsx @@ -27,7 +27,7 @@ import { import { Loader2, X, RefreshCw, AlertTriangle, RefreshCcw, Hourglass } from "lucide-react"; import config from "../../config"; import { toast } from "sonner"; -import { Table, TableBody, TableCell, TableRow, TableHeader, TableHead } from "@/components/ui/table"; +import { Table, TableBody, TableCell, TableRow } from "@/components/ui/table"; interface ImportProgress { status: "running" | "error" | "complete" | "cancelled"; diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx index 570f982..fc66a36 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/MatchColumnsStep.tsx @@ -1,5 +1,4 @@ import { useCallback, useEffect, useMemo, useState } from "react" -import { useToast } from "@chakra-ui/react" import { UserTableColumn } from "./components/UserTableColumn" import { useRsi } from "../../hooks/useRsi" import { TemplateColumn } from "./components/TemplateColumn" @@ -11,6 +10,7 @@ import { normalizeTableData } from "./utils/normalizeTableData" import type { Field, RawData } from "../../types" import { getMatchedColumns } from "./utils/getMatchedColumns" import { findUnmatchedRequiredFields } from "./utils/findUnmatchedRequiredFields" +import { toast } from "sonner" import { AlertDialog, AlertDialogAction, @@ -80,7 +80,6 @@ export const MatchColumnsStep = ({ onContinue, onBack, }: MatchColumnsProps) => { - const toast = useToast() const dataExample = data.slice(0, 2) const { fields, autoMapHeaders, autoMapSelectValues, autoMapDistance, translations, allowInvalidSubmit } = useRsi() const [isLoading, setIsLoading] = useState(false) @@ -92,24 +91,24 @@ export const MatchColumnsStep = ({ const onChange = useCallback( (value: T, columnIndex: number) => { - const field = fields.find((field) => field.key === value) as unknown as Field + const field = fields.find((field: Field) => field.key === value) + if (!field) return + const existingFieldIndex = columns.findIndex((column) => "value" in column && column.value === field.key) + setColumns( columns.map>((column, index) => { - columnIndex === index ? setColumn(column, field, data) : column if (columnIndex === index) { + // Set the new column value return setColumn(column, field, data, autoMapSelectValues) } else if (index === existingFieldIndex) { - toast({ - status: "warning", - variant: "left-accent", - position: "bottom-left", - title: translations.matchColumnsStep.duplicateColumnWarningTitle, + // Clear the old column that had this field + toast.warning(translations.matchColumnsStep.duplicateColumnWarningTitle, { description: translations.matchColumnsStep.duplicateColumnWarningDescription, - isClosable: true, }) return setColumn(column) } else { + // Leave other columns unchanged return column } }), @@ -120,7 +119,6 @@ export const MatchColumnsStep = ({ columns, data, fields, - toast, translations.matchColumnsStep.duplicateColumnWarningDescription, translations.matchColumnsStep.duplicateColumnWarningTitle, ], diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/ColumnGrid.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/ColumnGrid.tsx index 491214d..3fddb12 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/ColumnGrid.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/ColumnGrid.tsx @@ -1,10 +1,8 @@ import type React from "react" import type { Column, Columns } from "../MatchColumnsStep" -import { Box, Flex, Heading, ModalBody, Text, useStyleConfig } from "@chakra-ui/react" -import { FadingWrapper } from "../../../components/FadingWrapper" -import { ContinueButton } from "../../../components/ContinueButton" import { useRsi } from "../../../hooks/useRsi" -import type { themeOverrides } from "../../../theme" +import { Button } from "@/components/ui/button" +import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area" type ColumnGridProps = { columns: Columns @@ -15,8 +13,6 @@ type ColumnGridProps = { isLoading: boolean } -export type Styles = (typeof themeOverrides)["components"]["MatchColumnsStep"]["baseStyle"] - export const ColumnGrid = ({ columns, userColumn, @@ -26,52 +22,82 @@ export const ColumnGrid = ({ isLoading, }: ColumnGridProps) => { const { translations } = useRsi() - const styles = useStyleConfig("MatchColumnsStep") as Styles + const columnWidth = 250 + const gap = 16 + const totalWidth = columns.length * columnWidth + (columns.length - 1) * gap return ( - <> - - {translations.matchColumnsStep.title} - - - {translations.matchColumnsStep.userTableTitle} - - {columns.map((column, index) => ( - - {userColumn(column)} - - ))} - - - {translations.matchColumnsStep.templateTitle} - - - {columns.map((column, index) => ( - - {templateColumn(column)} - - ))} - - - - +
+
+
+
+

+ {translations.matchColumnsStep.title} +

+
+ +
+ {/* Your table section */} +
+

+ {translations.matchColumnsStep.userTableTitle} +

+
+
+ {columns.map((column, index) => ( +
+ {userColumn(column)} +
+ ))} +
+
+
+
+ + {/* Will become section */} +
+

+ {translations.matchColumnsStep.templateTitle} +

+
+ {columns.map((column, index) => ( +
+ {templateColumn(column)} +
+ ))} +
+
+
+ + +
+
+
+
+ {onBack && ( + + )} + +
+
+
) } diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/TemplateColumn.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/TemplateColumn.tsx index 4cc1a0d..a8db66d 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/TemplateColumn.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/TemplateColumn.tsx @@ -1,96 +1,139 @@ -import { - Flex, - Text, - Accordion, - AccordionItem, - AccordionButton, - AccordionIcon, - Box, - AccordionPanel, - useStyleConfig, -} from "@chakra-ui/react" import { useRsi } from "../../../hooks/useRsi" import type { Column } from "../MatchColumnsStep" import { ColumnType } from "../MatchColumnsStep" -import { MatchIcon } from "./MatchIcon" -import type { Fields } from "../../../types" -import type { Translations } from "../../../translationsRSIProps" -import { MatchColumnSelect } from "../../../components/Selects/MatchColumnSelect" -import { SubMatchingSelect } from "./SubMatchingSelect" -import type { Styles } from "./ColumnGrid" +import type { Fields, Field } from "../../../types" +import { + Card, + CardContent, + CardHeader, +} from "@/components/ui/card" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" +import { Check } from "lucide-react" +import { cn } from "@/lib/utils" -const getAccordionTitle = (fields: Fields, column: Column, translations: Translations) => { +type TemplateColumnProps = { + column: Column + onChange: (value: T, columnIndex: number) => void + onSubChange: (value: string, columnIndex: number, entry: string) => void +} + +const getAccordionTitle = (fields: Fields, column: Column, translations: any) => { const fieldLabel = fields.find((field) => "value" in column && field.key === column.value)!.label return `${translations.matchColumnsStep.matchDropdownTitle} ${fieldLabel} (${ "matchedOptions" in column && column.matchedOptions.filter((option) => !option.value).length } ${translations.matchColumnsStep.unmatched})` } -type TemplateColumnProps = { - onChange: (val: T, index: number) => void - onSubChange: (val: T, index: number, option: string) => void - column: Column -} - export const TemplateColumn = ({ column, onChange, onSubChange }: TemplateColumnProps) => { const { translations, fields } = useRsi() - const styles = useStyleConfig("MatchColumnsStep") as Styles const isIgnored = column.type === ColumnType.ignored const isChecked = column.type === ColumnType.matched || column.type === ColumnType.matchedCheckbox || column.type === ColumnType.matchedSelectOptions const isSelect = "matchedOptions" in column - const selectOptions = fields.map(({ label, key }) => ({ value: key, label })) - const selectValue = selectOptions.find(({ value }) => "value" in column && column.value === value) + const selectOptions = fields.map(({ label, key }: { label: string; key: string }) => ({ value: key, label })) + const selectValue = column.type === ColumnType.empty ? undefined : + selectOptions.find(({ value }: { value: string }) => "value" in column && column.value === value)?.value + + if (isIgnored) { + return ( + + +

+ {translations.matchColumnsStep.ignoredColumnText} +

+
+
+ ) + } return ( - - {isIgnored ? ( - {translations.matchColumnsStep.ignoredColumnText} - ) : ( - <> - - - onChange(value?.value as T, column.index)} - options={selectOptions} - name={column.header} - /> - - - - {isSelect && ( - - - - - - - - {getAccordionTitle(fields, column, translations)} - - - - - {column.matchedOptions.map((option) => ( - - ))} - - - - - )} - + + +
+ +
+ {isChecked && ( +
+ +
+ )} +
+ {isSelect && ( + + + + + {getAccordionTitle(fields, column, translations)} + + +
+ {column.matchedOptions.map((option) => ( +
+

+ {option.entry} +

+ +
+ ))} +
+
+
+
+
)} -
+ ) } diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/UserTableColumn.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/UserTableColumn.tsx index fda537a..02405ad 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/UserTableColumn.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/MatchColumnsStep/components/UserTableColumn.tsx @@ -1,10 +1,10 @@ -import { Box, Flex, IconButton, Text, useStyleConfig } from "@chakra-ui/react" -import { CgClose, CgUndo } from "react-icons/cg" +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader } from "@/components/ui/card" +import { X, Undo2 } from "lucide-react" import type { Column } from "../MatchColumnsStep" import { ColumnType } from "../MatchColumnsStep" -import { dataAttr } from "@chakra-ui/utils" -import type { Styles } from "./ColumnGrid" import type { RawData } from "../../../types" +import { cn } from "@/lib/utils" type UserTableColumnProps = { column: Column @@ -14,7 +14,6 @@ type UserTableColumnProps = { } export const UserTableColumn = (props: UserTableColumnProps) => { - const styles = useStyleConfig("MatchColumnsStep") as Styles const { column: { header, index, type }, entries, @@ -22,33 +21,38 @@ export const UserTableColumn = (props: UserTableColumnProps onRevertIgnore, } = props const isIgnored = type === ColumnType.ignored + return ( - - - + + +

{header} - - {type === ColumnType.ignored ? ( - } - onClick={() => onRevertIgnore(index)} - {...styles.userTable.ignoreButton} - /> - ) : ( - } - onClick={() => onIgnore(index)} - {...styles.userTable.ignoreButton} - /> - )} - - {entries.map((entry, index) => ( - - {entry} - - ))} - +

+ +
+ + {entries.slice(0, 3).map((entry, i) => ( +
+ {entry || ""} +
+ ))} +
+
) } diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx index 978af26..5fd7ed6 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/Steps.tsx @@ -44,8 +44,8 @@ export const Steps = () => { return ( <> -
-
) })} diff --git a/inventory/src/lib/react-spreadsheet-import/src/steps/UploadStep/components/DropZone.tsx b/inventory/src/lib/react-spreadsheet-import/src/steps/UploadStep/components/DropZone.tsx index bcc9305..c080aec 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/steps/UploadStep/components/DropZone.tsx +++ b/inventory/src/lib/react-spreadsheet-import/src/steps/UploadStep/components/DropZone.tsx @@ -57,13 +57,13 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {
{isDragActive ? ( -

+

{translations.uploadStep.dropzone.activeDropzoneTitle}

) : loading || isLoading ? ( @@ -75,7 +75,7 @@ export const DropZone = ({ onContinue, isLoading }: DropZoneProps) => {

{translations.uploadStep.dropzone.title}

- diff --git a/inventory/src/lib/react-spreadsheet-import/src/theme.ts b/inventory/src/lib/react-spreadsheet-import/src/theme.ts index 1fa9ef2..278ad4d 100644 --- a/inventory/src/lib/react-spreadsheet-import/src/theme.ts +++ b/inventory/src/lib/react-spreadsheet-import/src/theme.ts @@ -311,7 +311,7 @@ export const themeOverrides = { Modal: { baseStyle: { dialog: { - borderRadius: "lg", + borderRadius: "md", bg: "background", fontSize: "lg", color: "textColor", @@ -360,7 +360,7 @@ export const themeOverrides = { minH: "calc(var(--chakra-vh) - 4rem)", maxW: "calc(var(--chakra-vw) - 4rem)", my: "2rem", - borderRadius: "3xl", + borderRadius: "lg", overflow: "hidden", }, }, diff --git a/inventory/src/pages/import/Import.tsx b/inventory/src/pages/import/Import.tsx index df412f6..487040e 100644 --- a/inventory/src/pages/import/Import.tsx +++ b/inventory/src/pages/import/Import.tsx @@ -1,99 +1,128 @@ -import { ReactSpreadsheetImport } from "@/lib/react-spreadsheet-import/src"; import { useState } from "react"; +import { ReactSpreadsheetImport } from "@/lib/react-spreadsheet-import/src"; import { Button } from "@/components/ui/button"; -import { Box, Card, ScrollArea } from "@/components/ui/card"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Code } from "@/components/ui/code"; +import { toast } from "sonner"; +import { motion } from "framer-motion"; + +const IMPORT_FIELDS = [ + { + label: "Name", + key: "name", + alternateMatches: ["product", "product name", "item name"], + fieldType: { + type: "input", + }, + example: "Widget X", + validations: [ + { + rule: "required", + errorMessage: "Name is required", + level: "error", + }, + ], + }, + { + label: "SKU", + key: "sku", + alternateMatches: ["item number", "product code"], + fieldType: { + type: "input", + }, + example: "WX-123", + validations: [ + { + rule: "required", + errorMessage: "SKU is required", + level: "error", + }, + ], + }, + { + label: "Quantity", + key: "quantity", + alternateMatches: ["qty", "stock", "amount"], + fieldType: { + type: "input", + }, + example: "100", + validations: [ + { + rule: "required", + errorMessage: "Quantity is required", + level: "error", + }, + ], + }, +]; export function Import() { const [isOpen, setIsOpen] = useState(false); const [importedData, setImportedData] = useState(null); - const fields = [ - { - label: "Name", - key: "name", - alternateMatches: ["product", "product name", "item name"], - fieldType: { - type: "input", - }, - example: "Widget X", - validations: [ - { - rule: "required", - errorMessage: "Name is required", - level: "error", - }, - ], - }, - { - label: "SKU", - key: "sku", - alternateMatches: ["item number", "product code"], - fieldType: { - type: "input", - }, - example: "WX-123", - validations: [ - { - rule: "required", - errorMessage: "SKU is required", - level: "error", - }, - ], - }, - { - label: "Quantity", - key: "quantity", - alternateMatches: ["qty", "stock", "amount"], - fieldType: { - type: "input", - }, - example: "100", - validations: [ - { - rule: "required", - errorMessage: "Quantity is required", - level: "error", - }, - ], - }, - ]; - const handleData = async (data: any, file: File) => { - console.log("Imported Data:", data); - console.log("File:", file); - setImportedData(data); - setIsOpen(false); + try { + console.log("Imported Data:", data); + console.log("File:", file); + setImportedData(data); + setIsOpen(false); + toast.success("Data imported successfully"); + } catch (error) { + toast.error("Failed to import data"); + console.error("Import error:", error); + } }; return ( -
-
-

Import Data

- -
+ + +

Add New Products

+
+ + + + Import Data + + + + + {importedData && ( -
-

Imported Data

- - - - {JSON.stringify(importedData, null, 2)} - - - -
+ + + Preview Imported Data + + + + {JSON.stringify(importedData, null, 2)} + + + )} setIsOpen(false)} onSubmit={handleData} - fields={fields} + fields={IMPORT_FIELDS} /> -
+ ); } \ No newline at end of file diff --git a/mountremote.command b/mountremote.command index d46fbb4..3e3722f 100755 --- a/mountremote.command +++ b/mountremote.command @@ -1,7 +1,7 @@ #!/bin/zsh #Clear previous mount in case it’s still there -umount "/Users/matt/Library/Mobile Documents/com~apple~CloudDocs/Dev/inventory/inventory-server" +umount '/Users/matt/Library/Mobile Documents/com~apple~CloudDocs/Dev/inventory/inventory-server' #Mount -sshfs matt@dashboard.kent.pw:/var/www/html/inventory -p 22122 "/Users/matt/Library/Mobile Documents/com~apple~CloudDocs/Dev/inventory/inventory-server/" \ No newline at end of file +sshfs matt@dashboard.kent.pw:/var/www/html/inventory -p 22122 '/Users/matt/Library/Mobile Documents/com~apple~CloudDocs/Dev/inventory/inventory-server/' \ No newline at end of file