lots of changes with docker
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m57s

This commit is contained in:
2026-04-03 09:51:52 -05:00
parent 82ab735982
commit beae6eb648
36 changed files with 2284 additions and 36 deletions

View File

@@ -11,15 +11,15 @@ import {
useSidebar,
} from "../ui/sidebar";
type AdminSidebarProps = {
session: {
user: {
name?: string | null;
email?: string | null;
role?: string | string[];
};
} | null;
};
// type AdminSidebarProps = {
// session: {
// user: {
// name?: string | null;
// email?: string | null;
// role?: string | string[];
// };
// } | null;
//};
export default function AdminSidebar({ session }: any) {
const { setOpen } = useSidebar();

View File

@@ -0,0 +1,190 @@
import * as React from "react"
import { Select as SelectPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return (
<SelectPrimitive.Group
data-slot="select-group"
className={cn("scroll-my-1 p-1", className)}
{...props}
/>
)
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "item-aligned",
align = "center",
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
data-slot="select-content"
data-align-trigger={position === "item-aligned"}
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none 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 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", 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", className )}
position={position}
align={align}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
data-position={position}
className={cn(
"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
position === "popper" && ""
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="pointer-events-none" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronUpIcon
/>
</SelectPrimitive.ScrollUpButton>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
>
<ChevronDownIcon
/>
</SelectPrimitive.ScrollDownButton>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}

View File

@@ -0,0 +1,87 @@
import { Trash2 } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Button } from "../../components/ui/button";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type DynamicInputField = {
name?: string;
label: string;
inputType: "text" | "email" | "password" | "number" | "username";
required?: boolean;
description?: string;
addLabel?: string;
placeholder?: string;
disabled?: boolean;
};
const autoCompleteMap: Record<string, string> = {
email: "email",
password: "current-password",
text: "off",
username: "username",
};
export const DynamicInputField = ({
label,
inputType = "text",
required = false,
description,
addLabel,
}: DynamicInputField) => {
const field = useFieldContext<any>();
const values = Array.isArray(field.state.value) ? field.state.value : [];
return (
<div className="grid gap-3 mt-2">
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<Label>{label}</Label>
{description ? (
<p className="text-sm text-muted-foreground">{description}</p>
) : null}
</div>
<Button
type="button"
variant="secondary"
onClick={() => {
field.pushValue("");
}}
>
{addLabel}
</Button>
</div>
<div className="grid gap-3">
{values.map((_: string, index: number) => (
<div key={`${field.name}-${index}`} className="grid gap-2">
<div className="flex items-center gap-2">
<Label htmlFor={field.name}>{label}</Label>
<Input
id={field.name}
autoComplete={autoCompleteMap[inputType] ?? "off"}
value={field.state.value?.[index] ?? ""}
onChange={(e) => field.replaceValue(index, e.target.value)}
onBlur={field.handleBlur}
type={inputType}
required={required}
/>
{values.length > 1 ? (
<Button
type="button"
size={"icon"}
variant="destructive"
onClick={() => field.removeValue(index)}
>
<Trash2 className="w-32 h-32" />
</Button>
) : null}
<FieldErrors meta={field.state.meta} />
</div>
</div>
))}
</div>
</div>
);
};

View File

@@ -0,0 +1,57 @@
import { Label } from "../../components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../../components/ui/select";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type SelectOption = {
value: string;
label: string;
};
type SelectFieldProps = {
label: string;
options: SelectOption[];
placeholder?: string;
};
export const SelectField = ({
label,
options,
placeholder,
}: SelectFieldProps) => {
const field = useFieldContext<string>();
return (
<div className="grid gap-3">
<div className="grid gap-3">
<Label htmlFor={field.name}>{label}</Label>
<Select
value={field.state.value}
onValueChange={(value) => field.handleChange(value)}
>
<SelectTrigger
id={field.name}
onBlur={field.handleBlur}
className="w-min-2/3 w-max-fit"
>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -1,7 +1,9 @@
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
import { CheckboxField } from "./CheckBox.Field";
import { DynamicInputField } from "./DynamicInput.Field";
import { InputField } from "./Input.Field";
import { InputPasswordField } from "./InputPassword.Field";
import { SelectField } from "./Select.Field";
import { SubmitButton } from "./SubmitButton";
import { SwitchField } from "./Switch.Field";
@@ -12,12 +14,13 @@ export const { useAppForm } = createFormHook({
fieldComponents: {
InputField,
InputPasswordField,
//SelectField,
SelectField,
CheckboxField,
//DateField,
//TextArea,
//Searchable,
SwitchField,
DynamicInputField,
},
formComponents: { SubmitButton },
fieldContext,

View File

@@ -0,0 +1,24 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function notificationSubs(userId?: string) {
return queryOptions({
queryKey: ["notificationSubs"],
queryFn: () => fetch(userId),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async (userId?: string) => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 5000));
}
const { data } = await axios.get(
`/lst/api/notification/sub${userId ? `?userId=${userId}` : ""}`,
);
return data.data;
};

View File

@@ -0,0 +1,22 @@
import { keepPreviousData, queryOptions } from "@tanstack/react-query";
import axios from "axios";
export function notifications() {
return queryOptions({
queryKey: ["notifications"],
queryFn: () => fetch(),
staleTime: 5000,
refetchOnWindowFocus: true,
placeholderData: keepPreviousData,
});
}
const fetch = async () => {
if (window.location.hostname === "localhost") {
await new Promise((res) => setTimeout(res, 5000));
}
const { data } = await axios.get("/lst/api/notification");
return data.data;
};

View File

@@ -1,6 +1,6 @@
import type { Column } from "@tanstack/react-table";
import { ArrowDown, ArrowUp, Search } from "lucide-react";
import React, { useState } from "react";
import { useState } from "react";
import { Button } from "../../components/ui/button";
import {
DropdownMenu,

View File

@@ -0,0 +1,87 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "../../../components/ui/card";
import { useAppForm } from "../../../lib/formSutff";
import { notificationSubs } from "../../../lib/queries/notificationSubs";
import { notifications } from "../../../lib/queries/notifications";
export default function NotificationsSubCard({ user }: any) {
const { data } = useSuspenseQuery(notifications());
const { data: ns } = useSuspenseQuery(notificationSubs(user.id));
const form = useAppForm({
defaultValues: {
notificationId: "",
emails: [user.email],
},
onSubmit: async ({ value }) => {
const postD = { ...value, userId: user.id };
console.log(postD);
},
});
let n: any = [];
if (data) {
n = data.map((i: any) => ({
label: i.name,
value: i.id,
}));
}
console.log(ns);
return (
<div>
<Card className="p-3 w-128">
<CardHeader>
<CardTitle>Notifications</CardTitle>
<CardDescription>
All currently active notifications you can subscribe to. selecting a
notification will give you a brief description on how it works
</CardDescription>
</CardHeader>
<CardContent>
<form
onSubmit={(e) => {
e.preventDefault();
form.handleSubmit();
}}
>
<div>
<form.AppField name="notificationId">
{(field) => (
<field.SelectField
label="Notifications"
placeholder="Select Notification"
options={n}
/>
)}
</form.AppField>
</div>
<form.AppField name="emails" mode="array">
{(field) => (
<field.DynamicInputField
label="Notification Emails"
description="Add more email addresses for notification delivery."
inputType="email"
addLabel="Add Email"
//initialValue={session?.user.email}
/>
)}
</form.AppField>
<div className="flex justify-end mt-6">
<form.AppForm>
<form.SubmitButton>Subscribe</form.SubmitButton>
</form.AppForm>
</div>
</form>
</CardContent>
</Card>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import { createFileRoute, redirect } from "@tanstack/react-router";
import { Suspense } from "react";
import { toast } from "sonner";
import {
Card,
@@ -9,7 +10,9 @@ import {
} from "@/components/ui/card";
import { authClient, useSession } from "@/lib/auth-client";
import { useAppForm } from "@/lib/formSutff";
import { Spinner } from "../../components/ui/spinner";
import ChangePassword from "./-components/ChangePassword";
import NotificationsSubCard from "./-components/NotificationsSubCard";
export const Route = createFileRoute("/(auth)/user/profile")({
beforeLoad: async () => {
@@ -93,6 +96,26 @@ function RouteComponent() {
<div>
<ChangePassword />
</div>
<div>
<Suspense
fallback={
<Card className="p-3 w-96">
<CardHeader>
<CardTitle>Notifications</CardTitle>
</CardHeader>
<CardContent>
<div className="flex justify-center m-auto">
<div>
<Spinner className="size-32" />
</div>
</div>
</CardContent>
</Card>
}
>
{session && <NotificationsSubCard user={session.user} />}
</Suspense>
</div>
</div>
);
}

View File

@@ -3,7 +3,7 @@ import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import { Toaster } from "sonner";
import Header from "@/components/Header";
import { AppSidebar } from "@/components/Sidebar/sidebar";
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";
import { ThemeProvider } from "@/lib/theme-provider";
const RootLayout = () => (

View File

@@ -1,6 +1,5 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import axios from "axios";
import React from "react";
import { toast } from "sonner";
import { Card, CardDescription, CardHeader } from "../../../components/ui/card";
import { useAppForm } from "../../../lib/formSutff";
@@ -33,7 +32,9 @@ export default function FeatureCard({ item }: { item: Setting }) {
const { data } = await axios.patch(`/lst/api/settings/${item.name}`, {
value: value.value,
active: value.active ? "true" : "false",
});
}, {
withCredentials: true,
});
refetch();
toast.success(

View File

@@ -34,7 +34,7 @@ function Index() {
<p>
This is active in your plant today due to having warehousing activated
and new functions needed to be introduced, you should be still using LST
as you were before
as you were before.
</p>
<br></br>
<p>
@@ -50,7 +50,7 @@ function Index() {
rel="noopener"
>
<b>
<strong> Here</strong>
<strong> Here.</strong>
</b>
</a>
</p>