From 78f778f63804e9106a222697a1ccff9cba62ef27 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 19:48:26 -0500 Subject: [PATCH 01/14] add: document class page --- .../division-codes/dc-table-dialog.tsx | 9 ++ .../document-class-table-dialog.tsx | 130 ++++++++++++++++++ .../document-class-table-edit-dialog.tsx | 113 +++++++++++++++ client/src/components/ui/form.tsx | 18 ++- client/src/lib/validations/BillingSchema.ts | 11 ++ .../pages/billing/DocumentClassification.tsx | 74 ++++++++++ client/src/routing/AppRoutes.tsx | 12 ++ client/src/types/billing.ts | 16 ++- client/src/types/index.ts | 1 + server/billing/api.py | 10 +- server/billing/models.py | 3 +- server/reports/helpers.py | 10 ++ 12 files changed, 391 insertions(+), 16 deletions(-) create mode 100644 client/src/components/document-class/document-class-table-dialog.tsx create mode 100644 client/src/components/document-class/document-class-table-edit-dialog.tsx create mode 100644 client/src/pages/billing/DocumentClassification.tsx diff --git a/client/src/components/division-codes/dc-table-dialog.tsx b/client/src/components/division-codes/dc-table-dialog.tsx index 4c2e002fb..7305fa407 100644 --- a/client/src/components/division-codes/dc-table-dialog.tsx +++ b/client/src/components/division-codes/dc-table-dialog.tsx @@ -101,6 +101,9 @@ export function DCForm({ placeholder="Select Cash Account" description="The Cash Account associated with the Division Code" isClearable + hasPopoutWindow + popoutLink="/accounting/gl-accounts" + popoutLinkLabel="GL Account" />
@@ -115,6 +118,9 @@ export function DCForm({ placeholder="Select AP Account" description="The AP Account associated with the Division Code" isClearable + hasPopoutWindow + popoutLink="/accounting/gl-accounts" + popoutLinkLabel="GL Account" />
@@ -129,6 +135,9 @@ export function DCForm({ placeholder="Select Expense Account" description="The Expense Account associated with the Revenue Code" isClearable + hasPopoutWindow + popoutLink="/accounting/gl-accounts" + popoutLinkLabel="GL Account" />
diff --git a/client/src/components/document-class/document-class-table-dialog.tsx b/client/src/components/document-class/document-class-table-dialog.tsx new file mode 100644 index 000000000..76456d89a --- /dev/null +++ b/client/src/components/document-class/document-class-table-dialog.tsx @@ -0,0 +1,130 @@ +/* + * COPYRIGHT(c) 2023 MONTA + * + * This file is part of Monta. + * + * The Monta software is licensed under the Business Source License 1.1. You are granted the right + * to copy, modify, and redistribute the software, but only for non-production use or with a total + * of less than three server instances. Starting from the Change Date (November 16, 2026), the + * software will be made available under version 2 or later of the GNU General Public License. + * If you use the software in violation of this license, your rights under the license will be + * terminated automatically. The software is provided "as is," and the Licensor disclaims all + * warranties and conditions. If you use this license's text or the "Business Source License" name + * and trademark, you must comply with the Licensor's covenants, which include specifying the + * Change License as the GPL Version 2.0 or a compatible license, specifying an Additional Use + * Grant, and not modifying the license in any other way. + */ + +import { InputField } from "@/components/common/fields/input"; +import { TextareaField } from "@/components/common/fields/textarea"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Form, FormControl, FormGroup } from "@/components/ui/form"; +import { useCustomMutation } from "@/hooks/useCustomMutation"; +import { documentClassSchema } from "@/lib/validations/BillingSchema"; +import { DocumentClassificationFormValues as FormValues } from "@/types/billing"; +import { TableSheetProps } from "@/types/tables"; +import { yupResolver } from "@hookform/resolvers/yup"; +import React from "react"; +import { Control, useForm } from "react-hook-form"; + +export function DocumentClassForm({ + control, +}: { + control: Control; +}) { + return ( +
+ + + + + + + + +
+ ); +} + +export function DocumentClassDialog({ onOpenChange, open }: TableSheetProps) { + const [isSubmitting, setIsSubmitting] = React.useState(false); + + const { control, reset, handleSubmit } = useForm({ + resolver: yupResolver(documentClassSchema), + defaultValues: { + name: "", + description: "", + }, + }); + + const mutation = useCustomMutation( + control, + { + method: "POST", + path: "/document_classifications/", + successMessage: "Document Classification created successfully.", + queryKeysToInvalidate: ["document-classification-table-data"], + closeModal: true, + errorMessage: "Failed to create new document classification.", + }, + () => setIsSubmitting(false), + reset, + ); + + const onSubmit = (values: FormValues) => { + setIsSubmitting(true); + mutation.mutate(values); + }; + + return ( + + + + Create New Document Classification + + + Please fill out the form below to create a new Document + Classification. + +
+ + + + + +
+
+ ); +} diff --git a/client/src/components/document-class/document-class-table-edit-dialog.tsx b/client/src/components/document-class/document-class-table-edit-dialog.tsx new file mode 100644 index 000000000..aff90f61f --- /dev/null +++ b/client/src/components/document-class/document-class-table-edit-dialog.tsx @@ -0,0 +1,113 @@ +/* + * COPYRIGHT(c) 2023 MONTA + * + * This file is part of Monta. + * + * The Monta software is licensed under the Business Source License 1.1. You are granted the right + * to copy, modify, and redistribute the software, but only for non-production use or with a total + * of less than three server instances. Starting from the Change Date (November 16, 2026), the + * software will be made available under version 2 or later of the GNU General Public License. + * If you use the software in violation of this license, your rights under the license will be + * terminated automatically. The software is provided "as is," and the Licensor disclaims all + * warranties and conditions. If you use this license's text or the "Business Source License" name + * and trademark, you must comply with the Licensor's covenants, which include specifying the + * Change License as the GPL Version 2.0 or a compatible license, specifying an Additional Use + * Grant, and not modifying the license in any other way. + */ + +import { useCustomMutation } from "@/hooks/useCustomMutation"; +import { formatDate } from "@/lib/date"; +import { documentClassSchema } from "@/lib/validations/BillingSchema"; +import { useTableStore } from "@/stores/TableStore"; +import { + DocumentClassification, + DocumentClassificationFormValues as FormValues, +} from "@/types/billing"; +import { TableSheetProps } from "@/types/tables"; +import { yupResolver } from "@hookform/resolvers/yup"; +import React from "react"; +import { useForm } from "react-hook-form"; +import { Button } from "../ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "../ui/dialog"; +import { DocumentClassForm } from "@/components/document-class/document-class-table-dialog"; + +function DocumentClassEditForm({ + documentClass, +}: { + documentClass: DocumentClassification; +}) { + const [isSubmitting, setIsSubmitting] = React.useState(false); + const { control, reset, handleSubmit } = useForm({ + resolver: yupResolver(documentClassSchema), + defaultValues: { + name: documentClass.name, + description: documentClass.description, + }, + }); + + const mutation = useCustomMutation( + control, + { + method: "PUT", + path: `/document_classifications/${documentClass.id}/`, + successMessage: "Document Classification updated successfully.", + queryKeysToInvalidate: ["document-classification-table-data"], + closeModal: true, + errorMessage: "Failed to update document classification.", + }, + () => setIsSubmitting(false), + reset, + ); + + const onSubmit = (values: FormValues) => { + setIsSubmitting(true); + mutation.mutate(values); + }; + + return ( +
+ + + + + + ); +} + +export function DocumentClassEditDialog({ + onOpenChange, + open, +}: TableSheetProps) { + const [documentClass] = useTableStore.use( + "currentRecord", + ) as DocumentClassification[]; + + return ( + + + + {documentClass && documentClass.name} + + + Last updated on {documentClass && formatDate(documentClass.modified)} + + {documentClass && ( + + )} + + + ); +} diff --git a/client/src/components/ui/form.tsx b/client/src/components/ui/form.tsx index 1e663a130..d218b4b0c 100644 --- a/client/src/components/ui/form.tsx +++ b/client/src/components/ui/form.tsx @@ -1,3 +1,19 @@ +/* + * COPYRIGHT(c) 2023 MONTA + * + * This file is part of Monta. + * + * The Monta software is licensed under the Business Source License 1.1. You are granted the right + * to copy, modify, and redistribute the software, but only for non-production use or with a total + * of less than three server instances. Starting from the Change Date (November 16, 2026), the + * software will be made available under version 2 or later of the GNU General Public License. + * If you use the software in violation of this license, your rights under the license will be + * terminated automatically. The software is provided "as is," and the Licensor disclaims all + * warranties and conditions. If you use this license's text or the "Business Source License" name + * and trademark, you must comply with the Licensor's covenants, which include specifying the + * Change License as the GPL Version 2.0 or a compatible license, specifying an Additional Use + * Grant, and not modifying the license in any other way. + */ import * as React from "react"; import { cn } from "@/lib/utils"; @@ -36,7 +52,7 @@ export const FormControl = React.forwardRef< React.HTMLAttributes >(({ className, ...props }, ref) => { return ( -
+
); diff --git a/client/src/lib/validations/BillingSchema.ts b/client/src/lib/validations/BillingSchema.ts index 3e9305fef..864694505 100644 --- a/client/src/lib/validations/BillingSchema.ts +++ b/client/src/lib/validations/BillingSchema.ts @@ -21,6 +21,7 @@ import { AccessorialChargeFormValues, BillingControlFormValues, ChargeTypeFormValues, + DocumentClassificationFormValues, } from "@/types/billing"; import { AutoBillingCriteriaChoicesProps, @@ -64,6 +65,16 @@ export const chargeTypeSchema: ObjectSchema = .notRequired(), }); +export const documentClassSchema: ObjectSchema = + Yup.object().shape({ + name: Yup.string() + .max(10, "Name must be less than 10 characters.") + .required("Name is required"), + description: Yup.string() + .max(100, "Description must be less than 100 characters.") + .notRequired(), + }); + export const billingControlSchema: ObjectSchema = Yup.object().shape({ removeBillingHistory: Yup.boolean().required( diff --git a/client/src/pages/billing/DocumentClassification.tsx b/client/src/pages/billing/DocumentClassification.tsx new file mode 100644 index 000000000..42a15e28e --- /dev/null +++ b/client/src/pages/billing/DocumentClassification.tsx @@ -0,0 +1,74 @@ +/* + * COPYRIGHT(c) 2023 MONTA + * + * This file is part of Monta. + * + * The Monta software is licensed under the Business Source License 1.1. You are granted the right + * to copy, modify, and redistribute the software, but only for non-production use or with a total + * of less than three server instances. Starting from the Change Date (November 16, 2026), the + * software will be made available under version 2 or later of the GNU General Public License. + * If you use the software in violation of this license, your rights under the license will be + * terminated automatically. The software is provided "as is," and the Licensor disclaims all + * warranties and conditions. If you use this license's text or the "Business Source License" name + * and trademark, you must comply with the Licensor's covenants, which include specifying the + * Change License as the GPL Version 2.0 or a compatible license, specifying an Additional Use + * Grant, and not modifying the license in any other way. + */ + +import { Checkbox } from "@/components/common/fields/checkbox"; +import { DataTable } from "@/components/common/table/data-table"; +import { DataTableColumnHeader } from "@/components/common/table/data-table-column-header"; +import { DocumentClassDialog } from "@/components/document-class/document-class-table-dialog"; +import { DocumentClassification } from "@/types/billing"; +import { ColumnDef } from "@tanstack/react-table"; +import { DocumentClassEditDialog } from "@/components/document-class/document-class-table-edit-dialog"; + +const columns: ColumnDef[] = [ + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + className="translate-y-[2px]" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + className="translate-y-[2px]" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: "name", + header: ({ column }) => ( + + ), + }, + { + accessorKey: "description", + header: "Description", + }, +]; + +export default function DocumentClassificationPage() { + return ( + + ); +} diff --git a/client/src/routing/AppRoutes.tsx b/client/src/routing/AppRoutes.tsx index a334b3404..f7ba38521 100644 --- a/client/src/routing/AppRoutes.tsx +++ b/client/src/routing/AppRoutes.tsx @@ -65,6 +65,9 @@ const ShipmentTypePage = lazy(() => import("../pages/shipment/ShipmentType")); const LocationPage = lazy(() => import("../pages/dispatch/Location")); const TrailerPage = lazy(() => import("../pages/equipment/Trailer")); const TractorPage = lazy(() => import("../pages/equipment/Tractor")); +const DocumentClassPage = lazy( + () => import("../pages/billing/DocumentClassification"), +); const AdminPage = lazy(() => import("../pages/admin/Dashboard")); export type RouteObjectWithPermission = RouteObject & { @@ -175,6 +178,15 @@ export const routes: RouteObjectWithPermission[] = [ element: , permission: "view_chargetype", }, + { + title: "Document Classifications", + group: "billing", + subMenu: "configuration files", + path: "/billing/document-classes", + description: "Manage document classifications", + element: , + permission: "view_documentclassification", + }, { title: "Accessorial Charges", group: "billing", diff --git a/client/src/types/billing.ts b/client/src/types/billing.ts index da98a462e..0a9522209 100644 --- a/client/src/types/billing.ts +++ b/client/src/types/billing.ts @@ -15,13 +15,13 @@ * Grant, and not modifying the license in any other way. */ +import { StatusChoiceProps } from "@/types/index"; +import { BaseModel } from "@/types/organization"; import { AutoBillingCriteriaChoicesProps, FuelMethodChoicesProps, OrderTransferCriteriaChoicesProps, } from "@/utils/apps/billing"; -import { BaseModel } from "@/types/organization"; -import { StatusChoiceProps } from "@/types/index"; /** Types for Billing Control */ export interface BillingControl extends BaseModel { @@ -140,8 +140,14 @@ export interface BillingHistory extends BaseModel { } /** Types for Document Classification */ -export type DocumentClassification = { +export interface DocumentClassification extends BaseModel { id: string; name: string; - description: string; -}; + description?: string | null; +} + +/** Types for Document Classification */ +export type DocumentClassificationFormValues = Omit< + DocumentClassification, + "id" | "organization" | "created" | "modified" +>; diff --git a/client/src/types/index.ts b/client/src/types/index.ts index 97bf53e5f..2b0275e27 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -53,6 +53,7 @@ export type QueryKeys = | "delayCodes" | "division-code-table-data" | "divisionCodes" + | "document-classification-table-data" | "documentClassifications" | "depots" | "emailControl" diff --git a/server/billing/api.py b/server/billing/api.py index 1a5b41870..c3e5ece24 100644 --- a/server/billing/api.py +++ b/server/billing/api.py @@ -322,8 +322,7 @@ def get_queryset(self) -> QuerySet[models.AccessorialCharge]: class DocumentClassificationViewSet(viewsets.ModelViewSet): - """ - A viewset for viewing and editing document classifications in the system. + """A viewset for viewing and editing document classifications in the system. The viewset provides default operations for creating, updating, and deleting document classifications, as well as listing and retrieving document classifications. @@ -342,12 +341,7 @@ class to convert the document classification instances to and from JSON-formatte def get_queryset(self) -> QuerySet[models.DocumentClassification]: queryset = self.queryset.filter( organization_id=self.request.user.organization_id # type: ignore - ).only( - "id", - "name", - "organization_id", - "description", - ) + ).all() return queryset diff --git a/server/billing/models.py b/server/billing/models.py index fcb9549bb..11cbaeffd 100644 --- a/server/billing/models.py +++ b/server/billing/models.py @@ -436,7 +436,7 @@ class DocumentClassification(GenericModel): ) name = models.CharField( _("Name"), - max_length=150, + max_length=10, help_text=_("Document classification name"), ) description = models.TextField( @@ -452,7 +452,6 @@ class Meta: verbose_name = _("Document Classification") verbose_name_plural = _("Document Classifications") - ordering = ["name"] db_table = "document_classification" db_table_comment = ( "Stores document classification information for related organization." diff --git a/server/reports/helpers.py b/server/reports/helpers.py index 3c78236c0..28a94e466 100644 --- a/server/reports/helpers.py +++ b/server/reports/helpers.py @@ -372,4 +372,14 @@ {"value": "modified", "label": "Modified"}, ], }, + "DocumentClassification": { + "app_label": "billing", + "allowed_fields": [ + {"value": "organization__name", "label": "Organization Name"}, + {"value": "name", "label": "Name"}, + {"value": "description", "label": "Description"}, + {"value": "created", "label": "Created"}, + {"value": "modified", "label": "Modified"}, + ], + }, } From 572d497fc8c4adea7d7cdf13cb4f8e2745ceb234 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 19:48:43 -0500 Subject: [PATCH 02/14] add: new props to react-select --- client/src/types/react-select-extension.d.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/types/react-select-extension.d.ts b/client/src/types/react-select-extension.d.ts index acdfb9a2a..6d49796a9 100644 --- a/client/src/types/react-select-extension.d.ts +++ b/client/src/types/react-select-extension.d.ts @@ -16,7 +16,6 @@ */ import { Options } from "react-select"; // eslint-disable-line import/no-unassigned-import -import type { } from "react-select/base"; // eslint-disable-line import/no-unassigned-import type _ = Options; // Added just so auto sort import won't remove the import @@ -26,42 +25,43 @@ declare module "react-select/base" { IsMulti extends boolean, Group extends GroupBase
diff --git a/client/src/components/customer/customer-rule-profile-form.tsx b/client/src/components/customer/customer-rule-profile-form.tsx index 94620b07c..f07923b41 100644 --- a/client/src/components/customer/customer-rule-profile-form.tsx +++ b/client/src/components/customer/customer-rule-profile-form.tsx @@ -66,6 +66,9 @@ export function CustomerRuleProfileForm({ isLoading={isDocumentClassLoading} placeholder="Select Document Classification" description="Select the state or region for the customer." + hasPopoutWindow + popoutLink="#" // TODO: Change once Document Classification is added. + popoutLinkLabel="Document Classification" />
diff --git a/client/src/components/customer/delivery-slots-form.tsx b/client/src/components/customer/delivery-slots-form.tsx index 2d25a68ad..c58fd32f8 100644 --- a/client/src/components/customer/delivery-slots-form.tsx +++ b/client/src/components/customer/delivery-slots-form.tsx @@ -106,6 +106,9 @@ export function DeliverySlotForm({ isClearable={false} menuPlacement="bottom" menuPosition="fixed" + hasPopoutWindow + popoutLink="/dispatch/locations/" // TODO: Change once Document Classification is added. + popoutLinkLabel="Location" /> diff --git a/client/src/components/fleet-codes/fleet-code-table-dialog.tsx b/client/src/components/fleet-codes/fleet-code-table-dialog.tsx index 166200654..45fb1bd36 100644 --- a/client/src/components/fleet-codes/fleet-code-table-dialog.tsx +++ b/client/src/components/fleet-codes/fleet-code-table-dialog.tsx @@ -131,6 +131,9 @@ export function FleetCodeForm({ placeholder="Select Manager" description="User who manages the Fleet Code" isClearable + hasPopoutWindow + popoutLink="#" // TODO: Change once Document Classification is added. + popoutLinkLabel="User" /> diff --git a/client/src/components/location/location-comments-form.tsx b/client/src/components/location/location-comments-form.tsx index 0180fe1bb..8c6898b4e 100644 --- a/client/src/components/location/location-comments-form.tsx +++ b/client/src/components/location/location-comments-form.tsx @@ -67,6 +67,9 @@ export function LocationCommentForm({ isFetchError={isCommentTypeError} placeholder="Comment Type" description="Specify the category of the comment from the available options." + popoutLink="/dispatch/comment-types/" + hasPopoutWindow + popoutLinkLabel="Comment Type" /> diff --git a/client/src/components/location/location-info-form.tsx b/client/src/components/location/location-info-form.tsx index 637292c96..393940251 100644 --- a/client/src/components/location/location-info-form.tsx +++ b/client/src/components/location/location-info-form.tsx @@ -109,11 +109,12 @@ export function LocationInfoForm({ options={selectLocationCategories} isFetchError={isError} isLoading={isLoading} - hasPopoutWindow - popoutLink="/dispatch/location-categories/" isClearable placeholder="Select Location Category" description="Choose the category that best describes the location's function or type." + hasPopoutWindow + popoutLink="/dispatch/location-categories/" + popoutLinkLabel="Location Category" /> @@ -129,6 +130,9 @@ export function LocationInfoForm({ isLoading={isDepotsLoading} placeholder="Select Depot" description="Select the depot or main hub that this location is associated with." + hasPopoutWindow + popoutLink="#" + popoutLinkLabel="Depot" /> diff --git a/client/src/components/revenue-codes/rc-table-dialog.tsx b/client/src/components/revenue-codes/rc-table-dialog.tsx index 82345ffa2..24f7134fc 100644 --- a/client/src/components/revenue-codes/rc-table-dialog.tsx +++ b/client/src/components/revenue-codes/rc-table-dialog.tsx @@ -88,6 +88,9 @@ export function RCForm({ placeholder="Select Expense Account" description="The Expense Account associated with the Revenue Code" isClearable + hasPopoutWindow + popoutLink="/accounting/gl-accounts" + popoutLinkLabel="GL Account" />
@@ -102,6 +105,9 @@ export function RCForm({ placeholder="Select Revenue Account" description="The Revneue Account associated with the Revenue Code" isClearable + hasPopoutWindow + popoutLink="/accounting/gl-accounts" + popoutLinkLabel="GL Account" />
diff --git a/client/src/components/tractors/tractor-table-dialog.tsx b/client/src/components/tractors/tractor-table-dialog.tsx index 945aa82c5..ba60220a3 100644 --- a/client/src/components/tractors/tractor-table-dialog.tsx +++ b/client/src/components/tractors/tractor-table-dialog.tsx @@ -126,6 +126,7 @@ export function TractorForm({ description="Select the equipment type of the tractor, to categorize it based on its functionality and usage." popoutLink="/equipment/equipment-types/" hasPopoutWindow + popoutLinkLabel="Equipment Type" /> @@ -139,8 +140,9 @@ export function TractorForm({ placeholder="Select Manufacturer" description="Select the manufacturer of the tractor, to categorize it based on its functionality and usage." isClearable - popoutLink="/equipment/equipment-manufacturers/" hasPopoutWindow + popoutLink="/equipment/equipment-manufacturers/" + popoutLinkLabel="Equipment Manufacturer" /> @@ -204,6 +206,7 @@ export function TractorForm({ hasPopoutWindow popoutLink="/dispatch/fleet-codes/" isClearable + popoutLinkLabel="Fleet Code" /> @@ -217,6 +220,9 @@ export function TractorForm({ placeholder="Select Primary Worker" description="Select the primary worker assigned to the tractor." isClearable + hasPopoutWindow + popoutLink="#" + popoutLinkLabel="Worker" /> @@ -230,6 +236,9 @@ export function TractorForm({ placeholder="Select Secondary Worker" description="Select the secondary worker assigned to the tractor." isClearable + hasPopoutWindow + popoutLink="#" + popoutLinkLabel="Worker" /> diff --git a/client/src/components/trailers/trailer-table-dialog.tsx b/client/src/components/trailers/trailer-table-dialog.tsx index 2f7947d4e..7f5efa8e4 100644 --- a/client/src/components/trailers/trailer-table-dialog.tsx +++ b/client/src/components/trailers/trailer-table-dialog.tsx @@ -120,6 +120,7 @@ export function TrailerForm({ isClearable={false} popoutLink="/equipment/equipment-types/" hasPopoutWindow + popoutLinkLabel="Equipment Types" /> @@ -133,8 +134,9 @@ export function TrailerForm({ placeholder="Select Manufacturer" description="Select the manufacturer of the trailer, to categorize it based on its functionality and usage." isClearable={false} - popoutLink="/equipment/equipment-manufacturers/" hasPopoutWindow + popoutLink="/equipment/equipment-manufacturers/" + popoutLinkLabel="Equipment Manufacturer" /> @@ -195,6 +197,7 @@ export function TrailerForm({ description="Select the code that identifies the trailer within your fleet." hasPopoutWindow popoutLink="/dispatch/fleet-codes/" + popoutLinkLabel="Fleet Code" isClearable /> From 90dd95812af58ff990f02db21cebf279c42630f0 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 19:51:12 -0500 Subject: [PATCH 06/14] add: popout link label to select comp. --- client/src/components/common/fields/select-components.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/components/common/fields/select-components.tsx b/client/src/components/common/fields/select-components.tsx index 3aac9255a..0a7a1fbb9 100644 --- a/client/src/components/common/fields/select-components.tsx +++ b/client/src/components/common/fields/select-components.tsx @@ -176,7 +176,7 @@ export function MenuList({ return ( {child} @@ -200,7 +200,6 @@ export function NoOptionsMessage({ formError?: string; popoutLink?: string; hasPopoutWindow?: boolean; - label: string; }; }) { const { popoutLink, hasPopoutWindow } = props.selectProps || {}; @@ -215,7 +214,7 @@ export function NoOptionsMessage({

{popoutLink && hasPopoutWindow && ( )} From c78f31162b6737fec4531418bc4f9154e17ce4e7 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 19:51:24 -0500 Subject: [PATCH 07/14] fix: charge type edit form props --- .../charge-types/charge-type-edit-dialog.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/client/src/components/charge-types/charge-type-edit-dialog.tsx b/client/src/components/charge-types/charge-type-edit-dialog.tsx index c90dfde3b..9fc651c8c 100644 --- a/client/src/components/charge-types/charge-type-edit-dialog.tsx +++ b/client/src/components/charge-types/charge-type-edit-dialog.tsx @@ -19,7 +19,10 @@ import { useCustomMutation } from "@/hooks/useCustomMutation"; import { formatDate } from "@/lib/date"; import { chargeTypeSchema } from "@/lib/validations/BillingSchema"; import { useTableStore } from "@/stores/TableStore"; -import { ChargeTypeFormValues as FormValues } from "@/types/billing"; +import { + ChargeType, + ChargeTypeFormValues as FormValues, +} from "@/types/billing"; import { TableSheetProps } from "@/types/tables"; import { yupResolver } from "@hookform/resolvers/yup"; import React from "react"; @@ -35,11 +38,7 @@ import { } from "../ui/dialog"; import { ChargeTypeForm } from "./charge-type-dialog"; -function ChargeTypeEditForm({ - chargeType, -}: { - chargeType: Record; -}) { +function ChargeTypeEditForm({ chargeType }: { chargeType: ChargeType }) { const [isSubmitting, setIsSubmitting] = React.useState(false); const { control, reset, handleSubmit } = useForm({ resolver: yupResolver(chargeTypeSchema), From 9539960d8b2ee25448e59dc8705a310c686d629b Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 21:02:31 -0500 Subject: [PATCH 08/14] change: fleet code to max len 10 --- server/dispatch/models.py | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/server/dispatch/models.py b/server/dispatch/models.py index 413ebceae..78756695e 100644 --- a/server/dispatch/models.py +++ b/server/dispatch/models.py @@ -282,31 +282,6 @@ class FleetCode(GenericModel): A FleetCode instance represents a code used to identify a fleet of vehicles for service incidents. This allows for tracking and reporting on specific fleets of vehicles, including their revenue goals, deadhead goals, and mileage goals. - - Attributes: - code (CharField): Fleet code for the service incident. - Has a max length of 4 characters, is the primary key and unique, with help text of "Fleet code for the - service incident.". - description (CharField): Description for the fleet code. - Has a max length of 100 characters and help text of "Description for the fleet code.". - status (ChoiceField): Whether the fleet code is active. - Has a default value of True and help text of "Is the fleet code active.". - revenue_goal (DecimalField): Revenue goal for the fleet code. - Has a maximum of 10 digits, 2 decimal places, a default value of 0.00, and help text of "Revenue goal for - the fleet code.". - deadhead_goal (DecimalField): Deadhead goal for the fleet code. - Has a maximum of 10 digits, 2 decimal places, a default value of 0.00, and help text of "Deadhead goal for - the fleet code.". - mileage_goal (DecimalField): Mileage goal for the fleet code. - Has a maximum of 10 digits, 2 decimal places, a default value of 0.00, and help text of "Mileage goal for - the fleet code.". - - Methods: - __str__(self) -> str: - Returns a string representation of the fleet code, wrapped to a maximum of 50 characters. - - get_absolute_url(self) -> str: - Returns the URL for this object's detail view. """ id = models.UUIDField( @@ -323,7 +298,7 @@ class FleetCode(GenericModel): ) code = models.CharField( _("Fleet Code"), - max_length=4, + max_length=10, help_text=_("Fleet code for the service incident."), ) description = models.CharField( From d9d70db81d073671ce076ab717f3a81a8c0a97a6 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 21:02:35 -0500 Subject: [PATCH 09/14] Update react-select-extension.d.ts --- client/src/types/react-select-extension.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/types/react-select-extension.d.ts b/client/src/types/react-select-extension.d.ts index 6d49796a9..b85945934 100644 --- a/client/src/types/react-select-extension.d.ts +++ b/client/src/types/react-select-extension.d.ts @@ -15,7 +15,7 @@ * Grant, and not modifying the license in any other way. */ -import { Options } from "react-select"; // eslint-disable-line import/no-unassigned-import +import { GroupBase, Options } from "react-select"; // eslint-disable-line import/no-unassigned-import type _ = Options; // Added just so auto sort import won't remove the import From 0e4cd755f16e43282452b2b268d55d83a1eb13fb Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 21:02:57 -0500 Subject: [PATCH 10/14] fix: fleet code fields being required when not --- client/src/lib/validations/DispatchSchema.ts | 47 ++++++++++++++------ client/src/types/dispatch.ts | 8 ++-- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/client/src/lib/validations/DispatchSchema.ts b/client/src/lib/validations/DispatchSchema.ts index e0d87319c..f2c07fdba 100644 --- a/client/src/lib/validations/DispatchSchema.ts +++ b/client/src/lib/validations/DispatchSchema.ts @@ -1,3 +1,19 @@ +/* + * COPYRIGHT(c) 2023 MONTA + * + * This file is part of Monta. + * + * The Monta software is licensed under the Business Source License 1.1. You are granted the right + * to copy, modify, and redistribute the software, but only for non-production use or with a total + * of less than three server instances. Starting from the Change Date (November 16, 2026), the + * software will be made available under version 2 or later of the GNU General Public License. + * If you use the software in violation of this license, your rights under the license will be + * terminated automatically. The software is provided "as is," and the Licensor disclaims all + * warranties and conditions. If you use this license's text or the "Business Source License" name + * and trademark, you must comply with the Licensor's covenants, which include specifying the + * Change License as the GPL Version 2.0 or a compatible license, specifying an Additional Use + * Grant, and not modifying the license in any other way. + */ import { validateDecimal } from "@/lib/utils"; /* * COPYRIGHT(c) 2023 MONTA @@ -15,7 +31,6 @@ import { validateDecimal } from "@/lib/utils"; * Change License as the GPL Version 2.0 or a compatible license, specifying an Additional Use * Grant, and not modifying the license in any other way. */ - import * as Yup from "yup"; import { ObjectSchema } from "yup"; import { @@ -68,41 +83,45 @@ export const fleetCodeSchema: ObjectSchema = status: Yup.string().required("Status is required"), code: Yup.string() .required("Name is required") - .max(4, "Code cannot be more than 4 characters"), + .max(10, "Code cannot be more than 10 characters"), revenueGoal: Yup.string() - .required("Revenue Goal is required") + .notRequired() + .nullable() .test( "is-decimal", "Revenue Goal must be a decimal with no more than two decimal places", (value) => { - if (value !== undefined && value !== null) { - return validateDecimal(value, 2); + if (value === undefined || value === null || value === "") { + return true; // Passes validation for null, undefined, or empty string } - return false; + return validateDecimal(value, 2); }, ), deadheadGoal: Yup.string() - .required("Deadhead Goal is required") + .notRequired() + .nullable() .test( "is-decimal", "Deadhead Goal must be a decimal with no more than two decimal places", (value) => { - if (value !== undefined && value !== null) { - return validateDecimal(value, 2); + console.info("value", value); + if (value === undefined || value === null || value === "") { + return true; // Passes validation for null, undefined, or empty string } - return false; + return validateDecimal(value, 2); }, ), mileageGoal: Yup.string() - .required("Mileage Goal is required") + .notRequired() + .nullable() .test( "is-decimal", "Mileage Goal must be a decimal with no more than two decimal places", (value) => { - if (value !== undefined && value !== null) { - return validateDecimal(value, 2); + if (value === undefined || value === null || value === "") { + return true; // Passes validation for null, undefined, or empty string } - return false; + return validateDecimal(value, 2); }, ), description: Yup.string() diff --git a/client/src/types/dispatch.ts b/client/src/types/dispatch.ts index fc1e5ecc6..c80f5b770 100644 --- a/client/src/types/dispatch.ts +++ b/client/src/types/dispatch.ts @@ -49,7 +49,7 @@ export interface DelayCode extends BaseModel { export type DelayCodeFormValues = Omit< DelayCode, - "organization" | "businessUnit" | "created" | "modified" + "id" | "organization" | "businessUnit" | "created" | "modified" >; export interface FleetCode extends BaseModel { @@ -65,7 +65,7 @@ export interface FleetCode extends BaseModel { export type FleetCodeFormValues = Omit< FleetCode, - "organization" | "businessUnit" | "created" | "modified" + "id" | "organization" | "businessUnit" | "created" | "modified" >; export interface CommentType extends BaseModel { @@ -87,8 +87,8 @@ export interface Rate extends BaseModel { isActive: boolean; rateNumber: string; customer?: string | null; - effectiveDate: Date; - expirationDate: Date; + effectiveDate: string; + expirationDate: string; commodity?: string | null; orderType?: string | null; equipmentType?: string | null; From 74d2b06a1b6818dc15a0aba558879ac5cc947ba6 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 21:03:13 -0500 Subject: [PATCH 11/14] chore: update comments and remove unused function --- client/src/lib/utils.ts | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/client/src/lib/utils.ts b/client/src/lib/utils.ts index acfac07c5..23daad0e8 100644 --- a/client/src/lib/utils.ts +++ b/client/src/lib/utils.ts @@ -14,7 +14,7 @@ * Change License as the GPL Version 2.0 or a compatible license, specifying an Additional Use * Grant, and not modifying the license in any other way. */ -import { clsx, type ClassValue } from "clsx"; +import { type ClassValue, clsx } from "clsx"; import { RefObject, useEffect } from "react"; import { twMerge } from "tailwind-merge"; @@ -24,8 +24,8 @@ export function cn(...inputs: ClassValue[]) { /** * Formats a date string into a human readable format - * @param dateStr - The date string to format * @returns {string} + * @param str */ export function upperFirst(str: string): string { if (!str) return ""; @@ -68,9 +68,9 @@ export function truncateText(str: string, length: number): string { /** * Returns a random integer between min (inclusive) and max (inclusive). - * @param min - The minimum value to return - * @param max - The maximum value to return * @returns {number} + * @param ref + * @param handler */ export const useClickOutside = ( ref: RefObject, @@ -174,6 +174,11 @@ type PopoutWindowParams = { * Opens a new window with the given path and query params * @param path - The path to open the new window to * @param incomingQueryParams - The query params to pass to the new window + * @param width - The width of the new window + * @param height - The height of the new window + * @param left - The left position of the new window + * @param top - The top position of the new window + * @param hideHeader - Whether or not to hide the header of the new window * @returns {void} */ export function PopoutWindow( @@ -219,14 +224,3 @@ export const cleanObject = (obj: Record): Record => { }); return cleanedObj; }; - -/** - * Converts a camelCase string to a readable string - * @param str - * @returns {string} - */ -export const convertCamelCaseToReadable = (str: string): string => { - return str - .replace(/([A-Z])/g, " $1") - .replace(/^./, (str) => str.toUpperCase()); -}; From 28d714ece3a5d340faa405f6baa95bfa2d7481c0 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 21:03:28 -0500 Subject: [PATCH 12/14] change: fleet code to new form components --- .../fleet-codes/fleet-code-table-dialog.tsx | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/client/src/components/fleet-codes/fleet-code-table-dialog.tsx b/client/src/components/fleet-codes/fleet-code-table-dialog.tsx index 45fb1bd36..7837c34be 100644 --- a/client/src/components/fleet-codes/fleet-code-table-dialog.tsx +++ b/client/src/components/fleet-codes/fleet-code-table-dialog.tsx @@ -37,6 +37,8 @@ import { TableSheetProps } from "@/types/tables"; import { yupResolver } from "@hookform/resolvers/yup"; import React from "react"; import { Control, useForm } from "react-hook-form"; +import { Form, FormControl, FormGroup } from "@/components/ui/form"; +import { cleanObject } from "@/lib/utils"; export function FleetCodeForm({ control, @@ -48,9 +50,9 @@ export function FleetCodeForm({ const { selectUsersData, isLoading, isError } = useUsers(open); return ( -
-
-
+
+ + -
-
+ + -
-
-
+ + +
-
-
+ + -
-
+ + -
-
+ + -
-
+ + -
-
-
+ + + ); } @@ -171,8 +171,10 @@ export function FleetCodeDialog({ onOpenChange, open }: TableSheetProps) { ); const onSubmit = (values: FormValues) => { + const cleanedValues = cleanObject(values); + setIsSubmitting(true); - mutation.mutate(values); + mutation.mutate(cleanedValues); }; return ( From 939cd94bcb3fa006fd933b56ec126897b3871695 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 21:03:39 -0500 Subject: [PATCH 13/14] change: react-select --- client/src/components/common/fields/async-select-input.tsx | 5 ++++- client/src/components/common/fields/select-input.tsx | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/client/src/components/common/fields/async-select-input.tsx b/client/src/components/common/fields/async-select-input.tsx index 1a494e8ef..1dbe6b30a 100644 --- a/client/src/components/common/fields/async-select-input.tsx +++ b/client/src/components/common/fields/async-select-input.tsx @@ -29,7 +29,7 @@ import { ValueProcessor, } from "@/components/common/fields/select-components"; import { cn } from "@/lib/utils"; -import { UseControllerProps, useController } from "react-hook-form"; +import { useController, UseControllerProps } from "react-hook-form"; import { GroupBase, OptionsOrGroups, Props } from "react-select"; import AsyncSelect from "react-select/async"; @@ -49,6 +49,9 @@ interface AsyncSelectInputProps> options: OptionsOrGroups>; hasContextMenu?: boolean; isFetchError?: boolean; + hasPopoutWindow?: boolean; + popoutLink?: string; + popoutLinkLabel?: string; } /** diff --git a/client/src/components/common/fields/select-input.tsx b/client/src/components/common/fields/select-input.tsx index 12df3a31d..5e2abb09a 100644 --- a/client/src/components/common/fields/select-input.tsx +++ b/client/src/components/common/fields/select-input.tsx @@ -53,6 +53,7 @@ interface SelectInputProps> isFetchError?: boolean; hasPopoutWindow?: boolean; // Set to true to open the popout window popoutLink?: string; // Link to the popout page + popoutLinkLabel?: string; // Label for the popout link } /** From 6b1bed8682afaf367289cbc0421d6345917e25c0 Mon Sep 17 00:00:00 2001 From: Eric Moss Date: Tue, 19 Dec 2023 21:03:42 -0500 Subject: [PATCH 14/14] Update customer-table-sub.tsx --- .../customer/customer-table-sub.tsx | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/client/src/components/customer/customer-table-sub.tsx b/client/src/components/customer/customer-table-sub.tsx index aee38d28d..58989065f 100644 --- a/client/src/components/customer/customer-table-sub.tsx +++ b/client/src/components/customer/customer-table-sub.tsx @@ -15,7 +15,10 @@ * Grant, and not modifying the license in any other way. */ -import { Button } from "@/components/ui/button"; +import { + BoolStatusBadge, + DataNotFound, +} from "@/components/common/table/data-table-components"; import { Table, TableBody, @@ -24,17 +27,19 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; -import { truncateText } from "@/lib/utils"; +import { cn, truncateText } from "@/lib/utils"; import { useCustomerFormStore } from "@/stores/CustomerStore"; import { useTableStore } from "@/stores/TableStore"; import { Customer } from "@/types/customer"; +import { CircleBackslashIcon, PersonIcon } from "@radix-ui/react-icons"; import { Row } from "@tanstack/react-table"; import React from "react"; import { - BoolStatusBadge, - DataNotFound, -} from "@/components/common/table/data-table-components"; -import { CircleBackslashIcon, PersonIcon } from "@radix-ui/react-icons"; + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "../ui/tooltip"; const daysOfWeek = [ "Monday", // 0 @@ -62,6 +67,9 @@ function CustomerContactTable({

Customer Contacts

+
+ Payable Contact +
@@ -70,7 +78,6 @@ function CustomerContactTable({ Email Title Phone - Payable Contact? @@ -85,15 +92,30 @@ function CustomerContactTable({ - - {truncateText(contact.name, 20)} + + + + + {truncateText(contact.name, 20)} + + + {contact.isPayableContact + ? "This is the payable contact" + : "This is not the payable contact"} + + + {contact.email} - {contact.title} - {contact.phone} - + {truncateText(contact.title as string, 20)} + {contact.phone} )) ) : ( @@ -119,7 +141,7 @@ function DeliverySlotTable({ }) { return (
-

+

Delivery Slots