Sync
This commit is contained in:
3
copy.sh
Normal file
3
copy.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
cd ..
|
||||||
|
cp -a industrializer/src/* industrializer-wails/frontend/src/
|
||||||
|
cd industrializer-wails
|
||||||
@@ -11,9 +11,11 @@
|
|||||||
will-change: filter;
|
will-change: filter;
|
||||||
transition: filter 300ms;
|
transition: filter 300ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo:hover {
|
.logo:hover {
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
filter: drop-shadow(0 0 2em #646cffaa);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo.react:hover {
|
.logo.react:hover {
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||||
}
|
}
|
||||||
@@ -22,6 +24,7 @@
|
|||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,23 +95,34 @@ const JobCard: React.FC<JobCardProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const importBillOfMaterials = async () => {
|
const importBillOfMaterials = async () => {
|
||||||
|
if (!onImportBOM) {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Import functionality is not available",
|
||||||
|
variant: "destructive",
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const clipboardText = await navigator.clipboard.readText();
|
const clipboardText = await navigator.clipboard.readText();
|
||||||
const lines = clipboardText.split('\n').filter(line => line.trim());
|
const lines = clipboardText.split('\n').filter(line => line.trim());
|
||||||
const items: { name: string; quantity: number }[] = [];
|
const items: { name: string; quantity: number }[] = [];
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const parts = line.trim().split(/\s+/);
|
const parts = line.trim().split(/[\s\t]+/);
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
const name = parts.slice(0, -1).join(' ');
|
const name = parts.slice(0, -1).join(' ');
|
||||||
const quantity = parseInt(parts[parts.length - 1]);
|
const quantityPart = parts[parts.length - 1].replace(/,/g, '');
|
||||||
|
const quantity = parseInt(quantityPart);
|
||||||
if (name && !isNaN(quantity)) {
|
if (name && !isNaN(quantity)) {
|
||||||
items.push({ name, quantity });
|
items.push({ name, quantity });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.length > 0 && onImportBOM) {
|
if (items.length > 0) {
|
||||||
onImportBOM(job.id, items);
|
onImportBOM(job.id, items);
|
||||||
toast({
|
toast({
|
||||||
title: "BOM Imported",
|
title: "BOM Imported",
|
||||||
@@ -214,31 +225,6 @@ const JobCard: React.FC<JobCardProps> = ({
|
|||||||
<Badge className={`${getStatusColor(job.status)} text-white flex-shrink-0`}>
|
<Badge className={`${getStatusColor(job.status)} text-white flex-shrink-0`}>
|
||||||
{job.status}
|
{job.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
<div className="flex gap-1 flex-shrink-0">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="p-1 h-6 w-6"
|
|
||||||
onClick={handleImportClick}
|
|
||||||
title="Import BOM from clipboard"
|
|
||||||
>
|
|
||||||
<Import className="w-4 h-4 text-blue-400" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
className="p-1 h-6 w-6"
|
|
||||||
onClick={handleExportClick}
|
|
||||||
disabled={!job.billOfMaterials?.length}
|
|
||||||
title="Export BOM to clipboard"
|
|
||||||
>
|
|
||||||
{copyingBom ? (
|
|
||||||
<Check className="w-4 h-4 text-green-400" />
|
|
||||||
) : (
|
|
||||||
<Upload className="w-4 h-4 text-blue-400" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-400 text-sm">
|
<p className="text-gray-400 text-sm">
|
||||||
Quantity: {job.outputQuantity.toLocaleString()}
|
Quantity: {job.outputQuantity.toLocaleString()}
|
||||||
@@ -272,22 +258,49 @@ const JobCard: React.FC<JobCardProps> = ({
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2 flex-shrink-0">
|
<div className="flex flex-col gap-2 flex-shrink-0">
|
||||||
<Button
|
<div className="flex gap-2">
|
||||||
variant="outline"
|
<Button
|
||||||
size="sm"
|
variant="outline"
|
||||||
onClick={handleEditClick}
|
size="sm"
|
||||||
className="border-gray-600 hover:bg-gray-800"
|
onClick={handleEditClick}
|
||||||
>
|
className="border-gray-600 hover:bg-gray-800"
|
||||||
Edit
|
>
|
||||||
</Button>
|
Edit
|
||||||
<Button
|
</Button>
|
||||||
variant="destructive"
|
<Button
|
||||||
size="sm"
|
variant="destructive"
|
||||||
onClick={handleDeleteClick}
|
size="sm"
|
||||||
>
|
onClick={handleDeleteClick}
|
||||||
Delete
|
>
|
||||||
</Button>
|
Delete
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-1 justify-end">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="p-1 h-6 w-6"
|
||||||
|
onClick={handleImportClick}
|
||||||
|
title="Import BOM from clipboard"
|
||||||
|
>
|
||||||
|
<Import className="w-4 h-4 text-blue-400" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="p-1 h-6 w-6"
|
||||||
|
onClick={handleExportClick}
|
||||||
|
disabled={!job.billOfMaterials?.length}
|
||||||
|
title="Export BOM to clipboard"
|
||||||
|
>
|
||||||
|
{copyingBom ? (
|
||||||
|
<Check className="w-4 h-4 text-green-400" />
|
||||||
|
) : (
|
||||||
|
<Upload className="w-4 h-4 text-blue-400" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -17,8 +16,21 @@ interface JobFormProps {
|
|||||||
|
|
||||||
const formatDateForInput = (dateString: string | undefined | null): string => {
|
const formatDateForInput = (dateString: string | undefined | null): string => {
|
||||||
if (!dateString) return '';
|
if (!dateString) return '';
|
||||||
// Convert ISO string to datetime-local format (YYYY-MM-DDTHH:MM)
|
|
||||||
return new Date(dateString).toISOString().slice(0, 16);
|
// Create a date object in local timezone
|
||||||
|
const date = new Date(dateString);
|
||||||
|
|
||||||
|
// Format to YYYY-MM-DD
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
|
||||||
|
// Format to HH:MM
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
|
||||||
|
// Combine into format required by datetime-local (YYYY-MM-DDTHH:MM)
|
||||||
|
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
const JobForm: React.FC<JobFormProps> = ({ job, onSubmit, onCancel }) => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Badge } from '@/components/ui/badge';
|
|||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||||
import { parseTransactionLine, formatISK } from '@/utils/priceUtils';
|
import { parseTransactionLine, formatISK } from '@/utils/priceUtils';
|
||||||
import { IndTransactionRecord, IndTransactionRecordNoId } from '@/lib/pbtypes';
|
import { IndTransactionRecordNoId } from '@/lib/pbtypes';
|
||||||
import { Check, X } from 'lucide-react';
|
import { Check, X } from 'lucide-react';
|
||||||
|
|
||||||
interface TransactionFormProps {
|
interface TransactionFormProps {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ const badgeVariants = cva(
|
|||||||
|
|
||||||
export interface BadgeProps
|
export interface BadgeProps
|
||||||
extends React.HTMLAttributes<HTMLDivElement>,
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
VariantProps<typeof badgeVariants> {}
|
VariantProps<typeof badgeVariants> { }
|
||||||
|
|
||||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const buttonVariants = cva(
|
|||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean
|
asChild?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,13 +82,13 @@ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
|||||||
([theme, prefix]) => `
|
([theme, prefix]) => `
|
||||||
${prefix} [data-chart=${id}] {
|
${prefix} [data-chart=${id}] {
|
||||||
${colorConfig
|
${colorConfig
|
||||||
.map(([key, itemConfig]) => {
|
.map(([key, itemConfig]) => {
|
||||||
const color =
|
const color =
|
||||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||||
itemConfig.color
|
itemConfig.color
|
||||||
return color ? ` --color-${key}: ${color};` : null
|
return color ? ` --color-${key}: ${color};` : null
|
||||||
})
|
})
|
||||||
.join("\n")}
|
.join("\n")}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@@ -103,13 +103,13 @@ const ChartTooltip = RechartsPrimitive.Tooltip
|
|||||||
const ChartTooltipContent = React.forwardRef<
|
const ChartTooltipContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||||
React.ComponentProps<"div"> & {
|
React.ComponentProps<"div"> & {
|
||||||
hideLabel?: boolean
|
hideLabel?: boolean
|
||||||
hideIndicator?: boolean
|
hideIndicator?: boolean
|
||||||
indicator?: "line" | "dot" | "dashed"
|
indicator?: "line" | "dot" | "dashed"
|
||||||
nameKey?: string
|
nameKey?: string
|
||||||
labelKey?: string
|
labelKey?: string
|
||||||
}
|
}
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -259,10 +259,10 @@ const ChartLegend = RechartsPrimitive.Legend
|
|||||||
const ChartLegendContent = React.forwardRef<
|
const ChartLegendContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.ComponentProps<"div"> &
|
React.ComponentProps<"div"> &
|
||||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||||
hideIcon?: boolean
|
hideIcon?: boolean
|
||||||
nameKey?: string
|
nameKey?: string
|
||||||
}
|
}
|
||||||
>(
|
>(
|
||||||
(
|
(
|
||||||
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
||||||
@@ -326,8 +326,8 @@ function getPayloadConfigFromPayload(
|
|||||||
|
|
||||||
const payloadPayload =
|
const payloadPayload =
|
||||||
"payload" in payload &&
|
"payload" in payload &&
|
||||||
typeof payload.payload === "object" &&
|
typeof payload.payload === "object" &&
|
||||||
payload.payload !== null
|
payload.payload !== null
|
||||||
? payload.payload
|
? payload.payload
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const Command = React.forwardRef<
|
|||||||
))
|
))
|
||||||
Command.displayName = CommandPrimitive.displayName
|
Command.displayName = CommandPrimitive.displayName
|
||||||
|
|
||||||
interface CommandDialogProps extends DialogProps {}
|
interface CommandDialogProps extends DialogProps { }
|
||||||
|
|
||||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const labelVariants = cva(
|
|||||||
const Label = React.forwardRef<
|
const Label = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
VariantProps<typeof labelVariants>
|
VariantProps<typeof labelVariants>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<LabelPrimitive.Root
|
<LabelPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -31,9 +31,9 @@ const ScrollBar = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex touch-none select-none transition-colors",
|
"flex touch-none select-none transition-colors",
|
||||||
orientation === "vertical" &&
|
orientation === "vertical" &&
|
||||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||||
orientation === "horizontal" &&
|
orientation === "horizontal" &&
|
||||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const SelectContent = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
position={position}
|
position={position}
|
||||||
@@ -86,7 +86,7 @@ const SelectContent = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"p-1",
|
"p-1",
|
||||||
position === "popper" &&
|
position === "popper" &&
|
||||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -128,4 +128,3 @@ export {
|
|||||||
Sheet, SheetClose,
|
Sheet, SheetClose,
|
||||||
SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger
|
SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetOverlay, SheetPortal, SheetTitle, SheetTrigger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ const SidebarMenuAction = React.forwardRef<
|
|||||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
"peer-data-[size=lg]/menu-button:top-2.5",
|
||||||
"group-data-[collapsible=icon]:hidden",
|
"group-data-[collapsible=icon]:hidden",
|
||||||
showOnHover &&
|
showOnHover &&
|
||||||
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
|
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as React from "react"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
export interface TextareaProps
|
export interface TextareaProps
|
||||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { }
|
||||||
|
|
||||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||||
({ className, ...props }, ref) => {
|
({ className, ...props }, ref) => {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ const toastVariants = cva(
|
|||||||
const Toast = React.forwardRef<
|
const Toast = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||||
VariantProps<typeof toastVariants>
|
VariantProps<typeof toastVariants>
|
||||||
>(({ className, variant, ...props }, ref) => {
|
>(({ className, variant, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<ToastPrimitives.Root
|
<ToastPrimitives.Root
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const ToggleGroupContext = React.createContext<
|
|||||||
const ToggleGroup = React.forwardRef<
|
const ToggleGroup = React.forwardRef<
|
||||||
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
||||||
VariantProps<typeof toggleVariants>
|
VariantProps<typeof toggleVariants>
|
||||||
>(({ className, variant, size, children, ...props }, ref) => (
|
>(({ className, variant, size, children, ...props }, ref) => (
|
||||||
<ToggleGroupPrimitive.Root
|
<ToggleGroupPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@@ -33,7 +33,7 @@ ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName
|
|||||||
const ToggleGroupItem = React.forwardRef<
|
const ToggleGroupItem = React.forwardRef<
|
||||||
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
||||||
VariantProps<typeof toggleVariants>
|
VariantProps<typeof toggleVariants>
|
||||||
>(({ className, children, variant, size, ...props }, ref) => {
|
>(({ className, children, variant, size, ...props }, ref) => {
|
||||||
const context = React.useContext(ToggleGroupContext)
|
const context = React.useContext(ToggleGroupContext)
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const toggleVariants = cva(
|
|||||||
const Toggle = React.forwardRef<
|
const Toggle = React.forwardRef<
|
||||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||||
VariantProps<typeof toggleVariants>
|
VariantProps<typeof toggleVariants>
|
||||||
>(({ className, variant, size, ...props }, ref) => (
|
>(({ className, variant, size, ...props }, ref) => (
|
||||||
<TogglePrimitive.Root
|
<TogglePrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -33,21 +33,21 @@ type ActionType = typeof actionTypes
|
|||||||
|
|
||||||
type Action =
|
type Action =
|
||||||
| {
|
| {
|
||||||
type: ActionType["ADD_TOAST"]
|
type: ActionType["ADD_TOAST"]
|
||||||
toast: ToasterToast
|
toast: ToasterToast
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["UPDATE_TOAST"]
|
type: ActionType["UPDATE_TOAST"]
|
||||||
toast: Partial<ToasterToast>
|
toast: Partial<ToasterToast>
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["DISMISS_TOAST"]
|
type: ActionType["DISMISS_TOAST"]
|
||||||
toastId?: ToasterToast["id"]
|
toastId?: ToasterToast["id"]
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: ActionType["REMOVE_TOAST"]
|
type: ActionType["REMOVE_TOAST"]
|
||||||
toastId?: ToasterToast["id"]
|
toastId?: ToasterToast["id"]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
toasts: ToasterToast[]
|
toasts: ToasterToast[]
|
||||||
@@ -105,9 +105,9 @@ export const reducer = (state: State, action: Action): State => {
|
|||||||
toasts: state.toasts.map((t) =>
|
toasts: state.toasts.map((t) =>
|
||||||
t.id === toastId || toastId === undefined
|
t.id === toastId || toastId === undefined
|
||||||
? {
|
? {
|
||||||
...t,
|
...t,
|
||||||
open: false,
|
open: false,
|
||||||
}
|
}
|
||||||
: t
|
: t
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client';
|
||||||
import App from './App.tsx'
|
import App from './App.tsx';
|
||||||
import './index.css'
|
import './index.css';
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(<App />);
|
createRoot(document.getElementById("root")!).render(<App />);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import JobCard from '@/components/JobCard';
|
import JobCard from '@/components/JobCard';
|
||||||
@@ -21,7 +20,8 @@ const JobDetails = () => {
|
|||||||
updateTransaction,
|
updateTransaction,
|
||||||
deleteTransaction,
|
deleteTransaction,
|
||||||
updateJob,
|
updateJob,
|
||||||
deleteJob
|
deleteJob,
|
||||||
|
createMultipleBillItems
|
||||||
} = useJobs();
|
} = useJobs();
|
||||||
|
|
||||||
const job = useJob(jobId || null);
|
const job = useJob(jobId || null);
|
||||||
@@ -99,6 +99,19 @@ const JobDetails = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleImportBOM = async (jobId: string, items: { name: string; quantity: number }[]) => {
|
||||||
|
try {
|
||||||
|
const billItems = items.map(item => ({
|
||||||
|
name: item.name,
|
||||||
|
quantity: item.quantity,
|
||||||
|
unitPrice: 0
|
||||||
|
}));
|
||||||
|
await createMultipleBillItems(jobId, billItems, 'billOfMaterials');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error importing BOM:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (showJobForm) {
|
if (showJobForm) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-950 p-6">
|
<div className="min-h-screen bg-gray-950 p-6">
|
||||||
@@ -139,6 +152,7 @@ const JobDetails = () => {
|
|||||||
onEdit={handleEditJob}
|
onEdit={handleEditJob}
|
||||||
onDelete={handleDeleteJob}
|
onDelete={handleDeleteJob}
|
||||||
onUpdateProduced={handleUpdateProduced}
|
onUpdateProduced={handleUpdateProduced}
|
||||||
|
onImportBOM={handleImportBOM}
|
||||||
/>
|
/>
|
||||||
<TransactionForm
|
<TransactionForm
|
||||||
jobId={job.id}
|
jobId={job.id}
|
||||||
|
|||||||
@@ -6,5 +6,21 @@ export async function addBillItem(
|
|||||||
billItem: IndBillitemRecordNoId
|
billItem: IndBillitemRecordNoId
|
||||||
): Promise<IndBillitemRecord> {
|
): Promise<IndBillitemRecord> {
|
||||||
console.log('Adding bill item:', billItem);
|
console.log('Adding bill item:', billItem);
|
||||||
return await pb.collection<IndBillitemRecord>('ind_billItem').create(billItem);
|
// Set the job ID in the bill item record
|
||||||
|
const billItemWithJob = {
|
||||||
|
...billItem,
|
||||||
|
job: jobId
|
||||||
|
};
|
||||||
|
return await pb.collection<IndBillitemRecord>('ind_billItem').create(billItemWithJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteBillItem(id: string): Promise<void> {
|
||||||
|
console.log('Deleting bill item:', id);
|
||||||
|
await pb.collection('ind_billItem').delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteBillItems(ids: string[]): Promise<void> {
|
||||||
|
console.log('Deleting bill items:', ids);
|
||||||
|
// Delete items in parallel for better performance
|
||||||
|
await Promise.all(ids.map(id => deleteBillItem(id)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
import { IndJob } from '@/lib/types';
|
import { IndJob } from '@/lib/types';
|
||||||
import { IndJobRecord, IndJobRecordNoId, IndTransactionRecord, IndTransactionRecordNoId, IndBillitemRecord, IndBillitemRecordNoId } from '@/lib/pbtypes';
|
import { IndJobRecord, IndJobRecordNoId, IndTransactionRecord, IndTransactionRecordNoId, IndBillitemRecord, IndBillitemRecordNoId } from '@/lib/pbtypes';
|
||||||
import * as jobService from './jobService';
|
import * as jobService from './jobService';
|
||||||
@@ -219,6 +217,12 @@ export class DataService {
|
|||||||
const job = this.getJob(jobId);
|
const job = this.getJob(jobId);
|
||||||
if (!job) throw new Error(`Job with id ${jobId} not found`);
|
if (!job) throw new Error(`Job with id ${jobId} not found`);
|
||||||
|
|
||||||
|
// Delete existing bill items
|
||||||
|
const existingItemIds = job[type].map(item => item.id);
|
||||||
|
if (existingItemIds.length > 0) {
|
||||||
|
await billItemService.deleteBillItems(existingItemIds);
|
||||||
|
}
|
||||||
|
|
||||||
const createdBillItems: IndBillitemRecord[] = [];
|
const createdBillItems: IndBillitemRecord[] = [];
|
||||||
|
|
||||||
// Create all bill items
|
// Create all bill items
|
||||||
@@ -227,17 +231,16 @@ export class DataService {
|
|||||||
createdBillItems.push(createdBillItem);
|
createdBillItems.push(createdBillItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the job's bill item references in one database call
|
// Update the job's bill item references with ONLY the new IDs
|
||||||
const currentIds = (job[type] || []).map(item => item.id);
|
|
||||||
const newIds = createdBillItems.map(item => item.id);
|
const newIds = createdBillItems.map(item => item.id);
|
||||||
await jobService.updateJob(jobId, {
|
await jobService.updateJob(jobId, {
|
||||||
[type]: [...currentIds, ...newIds]
|
[type]: newIds // Replace instead of append
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update local state
|
// Update local state
|
||||||
const jobIndex = this.jobs.findIndex(j => j.id === jobId);
|
const jobIndex = this.jobs.findIndex(j => j.id === jobId);
|
||||||
if (jobIndex !== -1) {
|
if (jobIndex !== -1) {
|
||||||
this.jobs[jobIndex][type].push(...createdBillItems);
|
this.jobs[jobIndex][type] = createdBillItems; // Replace instead of append
|
||||||
this.notifyListeners();
|
this.notifyListeners();
|
||||||
return this.jobs[jobIndex];
|
return this.jobs[jobIndex];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user