Converting to shadcn, steps, validate page
This commit is contained in:
31
inventory/package-lock.json
generated
31
inventory/package-lock.json
generated
@@ -31,6 +31,7 @@
|
|||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.4",
|
||||||
"@radix-ui/react-collapsible": "^1.1.2",
|
"@radix-ui/react-collapsible": "^1.1.2",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
@@ -3300,6 +3301,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-checkbox": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.1",
|
||||||
|
"@radix-ui/react-context": "1.1.1",
|
||||||
|
"@radix-ui/react-presence": "1.1.2",
|
||||||
|
"@radix-ui/react-primitive": "2.0.2",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.1.0",
|
||||||
|
"@radix-ui/react-use-previous": "1.1.0",
|
||||||
|
"@radix-ui/react-use-size": "1.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-collapsible": {
|
"node_modules/@radix-ui/react-collapsible": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.3.tgz",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
"@radix-ui/react-alert-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
|
"@radix-ui/react-checkbox": "^1.1.4",
|
||||||
"@radix-ui/react-collapsible": "^1.1.2",
|
"@radix-ui/react-collapsible": "^1.1.2",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
||||||
|
|||||||
28
inventory/src/components/ui/checkbox.tsx
Normal file
28
inventory/src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { Check } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
AlertDialogPortal,
|
AlertDialogPortal,
|
||||||
AlertDialogOverlay,
|
AlertDialogOverlay,
|
||||||
} from "@/components/ui/alert-dialog"
|
} from "@/components/ui/alert-dialog"
|
||||||
import { useState } from "react"
|
|
||||||
import { useRsi } from "../hooks/useRsi"
|
import { useRsi } from "../hooks/useRsi"
|
||||||
|
|
||||||
type ModalCloseButtonProps = {
|
type ModalCloseButtonProps = {
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { StepState, StepType, UploadFlow } from "./UploadFlow"
|
import { StepState, StepType, UploadFlow } from "./UploadFlow"
|
||||||
import { ModalHeader } from "@chakra-ui/react"
|
|
||||||
import { useSteps, Step, Steps as Stepper } from "chakra-ui-steps"
|
|
||||||
import { CgCheck } from "react-icons/cg"
|
|
||||||
|
|
||||||
import { useRsi } from "../hooks/useRsi"
|
import { useRsi } from "../hooks/useRsi"
|
||||||
import { useRef, useState } from "react"
|
import { useRef, useState } from "react"
|
||||||
import { steps, stepTypeToStepIndex, stepIndexToStepType } from "../utils/steps"
|
import { steps, stepTypeToStepIndex, stepIndexToStepType } from "../utils/steps"
|
||||||
|
import { CgCheck } from "react-icons/cg"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
// @ts-ignore
|
||||||
|
import { useSteps, Step, Steps as Stepper } from "chakra-ui-steps"
|
||||||
|
|
||||||
const CheckIcon = ({ color }: { color: string }) => <CgCheck size="36px" color={color} />
|
const CheckIcon = ({ color }: { color: string }) => <CgCheck size="24" className={color} />
|
||||||
|
|
||||||
export const Steps = () => {
|
export const Steps = () => {
|
||||||
const { initialStepState, translations, isNavigationEnabled } = useRsi()
|
const { initialStepState, translations, isNavigationEnabled } = useRsi()
|
||||||
@@ -44,18 +44,49 @@ export const Steps = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ModalHeader display={["none", "none", "block"]}>
|
<div className="hidden border-b bg-muted px-8 py-6 md:block lg:px-24 xl:px-36">
|
||||||
<Stepper
|
<nav className="mx-auto flex items-center justify-between" aria-label="Steps">
|
||||||
activeStep={activeStep}
|
{steps.map((key, index) => {
|
||||||
checkIcon={CheckIcon}
|
const isActive = index === activeStep
|
||||||
onClickStep={isNavigationEnabled ? onClickStep : undefined}
|
const isCompleted = index < activeStep
|
||||||
responsive={false}
|
return (
|
||||||
|
<div key={key} className="flex items-center">
|
||||||
|
<button
|
||||||
|
className={`group flex items-center ${isNavigationEnabled ? 'cursor-pointer' : 'cursor-default'}`}
|
||||||
|
onClick={isNavigationEnabled ? () => onClickStep(index) : undefined}
|
||||||
|
disabled={!isNavigationEnabled}
|
||||||
>
|
>
|
||||||
{steps.map((key) => (
|
<div className={`flex shrink-0 h-10 w-10 items-center justify-center rounded-full border-2 ${
|
||||||
<Step label={translations[key].title} key={key} />
|
isActive
|
||||||
))}
|
? 'border-primary bg-primary text-primary-foreground'
|
||||||
</Stepper>
|
: isCompleted
|
||||||
</ModalHeader>
|
? 'border-primary bg-primary text-primary-foreground'
|
||||||
|
: 'border-muted-foreground/20 bg-background'
|
||||||
|
}`}>
|
||||||
|
{isCompleted ? (
|
||||||
|
<CheckIcon color="text-primary-foreground" />
|
||||||
|
) : (
|
||||||
|
<span className={`text-sm font-medium ${
|
||||||
|
isActive ? 'text-primary-foreground' : 'text-muted-foreground'
|
||||||
|
}`}>
|
||||||
|
{index + 1}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className={`ml-2 text-sm font-medium w-24 ${
|
||||||
|
isActive ? 'text-foreground' : 'text-muted-foreground'
|
||||||
|
}`}>
|
||||||
|
{translations[key].title}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
{index < steps.length - 1 && (
|
||||||
|
<Separator className="ml-2 mr-0 bg-muted-foreground/80" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
<UploadFlow state={state} onNext={onNext} onBack={isNavigationEnabled ? onBack : undefined} />
|
<UploadFlow state={state} onNext={onNext} onBack={isNavigationEnabled ? onBack : undefined} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { useCallback, useMemo, useState } from "react"
|
import { useCallback, useMemo, useState } from "react"
|
||||||
import { Box, Button, Heading, ModalBody, Switch, useStyleConfig, useToast } from "@chakra-ui/react"
|
|
||||||
import { ContinueButton } from "../../components/ContinueButton"
|
|
||||||
import { useRsi } from "../../hooks/useRsi"
|
import { useRsi } from "../../hooks/useRsi"
|
||||||
import type { Meta } from "./types"
|
import type { Meta } from "./types"
|
||||||
import { addErrorsAndRunHooks } from "./utils/dataMutations"
|
import { addErrorsAndRunHooks } from "./utils/dataMutations"
|
||||||
import { generateColumns } from "./components/columns"
|
import { generateColumns } from "./components/columns"
|
||||||
import { Table } from "../../components/Table"
|
// @ts-ignore
|
||||||
|
import type { Column, RowsChangeData } from "react-data-grid"
|
||||||
|
// @ts-ignore
|
||||||
|
import DataGrid from "react-data-grid"
|
||||||
import type { Data } from "../../types"
|
import type { Data } from "../../types"
|
||||||
import type { themeOverrides } from "../../theme"
|
|
||||||
import type { RowsChangeData } from "react-data-grid"
|
|
||||||
import {
|
import {
|
||||||
AlertDialog,
|
AlertDialog,
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@@ -21,6 +20,9 @@ import {
|
|||||||
AlertDialogPortal,
|
AlertDialogPortal,
|
||||||
AlertDialogOverlay,
|
AlertDialogOverlay,
|
||||||
} from "@/components/ui/alert-dialog"
|
} from "@/components/ui/alert-dialog"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import { useToast } from "@/hooks/use-toast"
|
||||||
|
|
||||||
type Props<T extends string> = {
|
type Props<T extends string> = {
|
||||||
initialData: (Data<T> & Meta)[]
|
initialData: (Data<T> & Meta)[]
|
||||||
@@ -30,13 +32,9 @@ type Props<T extends string> = {
|
|||||||
|
|
||||||
export const ValidationStep = <T extends string>({ initialData, file, onBack }: Props<T>) => {
|
export const ValidationStep = <T extends string>({ initialData, file, onBack }: Props<T>) => {
|
||||||
const { translations, fields, onClose, onSubmit, rowHook, tableHook, allowInvalidSubmit } = useRsi<T>()
|
const { translations, fields, onClose, onSubmit, rowHook, tableHook, allowInvalidSubmit } = useRsi<T>()
|
||||||
const styles = useStyleConfig(
|
const { toast } = useToast()
|
||||||
"ValidationStep",
|
|
||||||
) as (typeof themeOverrides)["components"]["ValidationStep"]["baseStyle"]
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const [data, setData] = useState<(Data<T> & Meta)[]>(initialData)
|
const [data, setData] = useState<(Data<T> & Meta)[]>(initialData)
|
||||||
|
|
||||||
const [selectedRows, setSelectedRows] = useState<ReadonlySet<number | string>>(new Set())
|
const [selectedRows, setSelectedRows] = useState<ReadonlySet<number | string>>(new Set())
|
||||||
const [filterByErrors, setFilterByErrors] = useState(false)
|
const [filterByErrors, setFilterByErrors] = useState(false)
|
||||||
const [showSubmitAlert, setShowSubmitAlert] = useState(false)
|
const [showSubmitAlert, setShowSubmitAlert] = useState(false)
|
||||||
@@ -63,7 +61,7 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
|
|||||||
|
|
||||||
const updateRows = useCallback(
|
const updateRows = useCallback(
|
||||||
(rows: typeof data, changedData?: RowsChangeData<(typeof data)[number]>) => {
|
(rows: typeof data, changedData?: RowsChangeData<(typeof data)[number]>) => {
|
||||||
const changes = changedData?.indexes.reduce((acc, index) => {
|
const changes = changedData?.indexes.reduce((acc: Record<number, (typeof data)[number]>, index: number) => {
|
||||||
// when data is filtered val !== actual index in data
|
// when data is filtered val !== actual index in data
|
||||||
const realIndex = data.findIndex((value) => value.__index === rows[index].__index)
|
const realIndex = data.findIndex((value) => value.__index === rows[index].__index)
|
||||||
acc[realIndex] = rows[index]
|
acc[realIndex] = rows[index]
|
||||||
@@ -119,12 +117,9 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
|
|||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
toast({
|
toast({
|
||||||
status: "error",
|
variant: "destructive",
|
||||||
variant: "left-accent",
|
title: translations.alerts.submitError.title,
|
||||||
position: "bottom-left",
|
description: err?.message || translations.alerts.submitError.defaultMessage,
|
||||||
title: `${translations.alerts.submitError.title}`,
|
|
||||||
description: err?.message || `${translations.alerts.submitError.defaultMessage}`,
|
|
||||||
isClosable: true,
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@@ -149,7 +144,7 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex h-full flex-col">
|
||||||
<AlertDialog open={showSubmitAlert} onOpenChange={setShowSubmitAlert}>
|
<AlertDialog open={showSubmitAlert} onOpenChange={setShowSubmitAlert}>
|
||||||
<AlertDialogPortal>
|
<AlertDialogPortal>
|
||||||
<AlertDialogOverlay className="z-[1400]" />
|
<AlertDialogOverlay className="z-[1400]" />
|
||||||
@@ -177,48 +172,64 @@ export const ValidationStep = <T extends string>({ initialData, file, onBack }:
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialogPortal>
|
</AlertDialogPortal>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
<ModalBody pb={0}>
|
<div className="flex-1 overflow-hidden">
|
||||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb="2rem" flexWrap="wrap" gap="8px">
|
<div className="px-8 py-6">
|
||||||
<Heading sx={styles.heading}>{translations.validationStep.title}</Heading>
|
<div className="mb-8 flex flex-wrap items-center justify-between gap-2">
|
||||||
<Box display="flex" gap="16px" alignItems="center" flexWrap="wrap">
|
<h2 className="text-3xl font-semibold text-foreground">
|
||||||
|
{translations.validationStep.title}
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
<Button variant="outline" size="sm" onClick={deleteSelectedRows}>
|
<Button variant="outline" size="sm" onClick={deleteSelectedRows}>
|
||||||
{translations.validationStep.discardButtonTitle}
|
{translations.validationStep.discardButtonTitle}
|
||||||
</Button>
|
</Button>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<Switch
|
<Switch
|
||||||
display="flex"
|
checked={filterByErrors}
|
||||||
alignItems="center"
|
onCheckedChange={setFilterByErrors}
|
||||||
isChecked={filterByErrors}
|
id="filter-errors"
|
||||||
onChange={() => setFilterByErrors(!filterByErrors)}
|
/>
|
||||||
>
|
<label htmlFor="filter-errors" className="text-sm text-muted-foreground">
|
||||||
{translations.validationStep.filterSwitchTitle}
|
{translations.validationStep.filterSwitchTitle}
|
||||||
</Switch>
|
</label>
|
||||||
</Box>
|
</div>
|
||||||
</Box>
|
</div>
|
||||||
<Table
|
</div>
|
||||||
|
<div className="h-[calc(100vh-20rem)] w-full">
|
||||||
|
<DataGrid
|
||||||
rowKeyGetter={rowKeyGetter}
|
rowKeyGetter={rowKeyGetter}
|
||||||
rows={tableData}
|
rows={tableData}
|
||||||
onRowsChange={updateRows}
|
onRowsChange={updateRows}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
selectedRows={selectedRows}
|
selectedRows={selectedRows}
|
||||||
onSelectedRowsChange={setSelectedRows}
|
onSelectedRowsChange={setSelectedRows}
|
||||||
components={{
|
className="h-full w-full"
|
||||||
noRowsFallback: (
|
noRowsFallback={
|
||||||
<Box display="flex" justifyContent="center" gridColumn="1/-1" mt="32px">
|
<div className="col-span-full mt-8 flex justify-center">
|
||||||
{filterByErrors
|
{filterByErrors
|
||||||
? translations.validationStep.noRowsMessageWhenFiltered
|
? translations.validationStep.noRowsMessageWhenFiltered
|
||||||
: translations.validationStep.noRowsMessage}
|
: translations.validationStep.noRowsMessage}
|
||||||
</Box>
|
</div>
|
||||||
),
|
}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</ModalBody>
|
</div>
|
||||||
<ContinueButton
|
</div>
|
||||||
isLoading={isSubmitting}
|
</div>
|
||||||
onContinue={onContinue}
|
<div className="px-8 py-8 border-t bg-muted -mt-4">
|
||||||
onBack={onBack}
|
<div className="flex items-center justify-between -mt-4">
|
||||||
title={translations.validationStep.nextButtonTitle}
|
{onBack && (
|
||||||
backTitle={translations.validationStep.backButtonTitle}
|
<Button variant="outline" onClick={onBack}>
|
||||||
/>
|
{translations.validationStep.backButtonTitle}
|
||||||
</>
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
className="ml-auto"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
onClick={onContinue}
|
||||||
|
>
|
||||||
|
{translations.validationStep.nextButtonTitle}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/zsh
|
#!/bin/zsh
|
||||||
|
|
||||||
#Clear previous mount in case it’s still there
|
#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
|
#Mount
|
||||||
sshfs matt@dashboard.kent.pw:/var/www/html/inventory -p 22122 /Users/matt/Library/Mobile Documents/com~apple~CloudDocs/Dev/inventory/inventory-server/
|
sshfs matt@dashboard.kent.pw:/var/www/html/inventory -p 22122 "/Users/matt/Library/Mobile Documents/com~apple~CloudDocs/Dev/inventory/inventory-server/"
|
||||||
Reference in New Issue
Block a user