QQQ-32 udpating styles on query; record list & view sections; record labels; apps in nav; updated breadcrumb; updated navigation

This commit is contained in:
2022-08-09 15:57:10 -05:00
parent 7cb3c5ee88
commit 74717a092a
38 changed files with 4156 additions and 967 deletions

View File

@ -1,16 +1,22 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {useState, useEffect, ReactNode} from "react";
@ -22,7 +28,6 @@ import breakpoints from "assets/theme/base/breakpoints";
import DashboardLayout from "examples/LayoutContainers/DashboardLayout";
import Navbar from "qqq/components/Navbar";
import Footer from "qqq/components/Footer";
import DashboardNavbar from "examples/Navbars/DashboardNavbar";
import MDBox from "../../../components/MDBox";
// Declaring props types for BaseLayout
@ -60,7 +65,7 @@ function BaseLayout({stickyNavbar, children}: Props): JSX.Element
return (
<DashboardLayout>
<Navbar />
<MDBox mt={stickyNavbar ? 3 : 10}>{children}</MDBox>
<MDBox mt={stickyNavbar ? 3 : 6}>{children}</MDBox>
<Footer />
</DashboardLayout>
);

View File

@ -1,3 +1,24 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {useAuth0} from "@auth0/auth0-react";
import React from "react";
import {Button} from "@mui/material";

View File

@ -1,57 +0,0 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
// Material Dashboard 2 PRO React TS components
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import MDAvatar from "components/MDAvatar";
// Declaring props types for CustomerCell
interface Props {
image?: string;
name: string;
color?:
| "transparent"
| "primary"
| "secondary"
| "info"
| "success"
| "warning"
| "error"
| "light"
| "dark";
}
function CustomerCell({image, name, color}: Props): JSX.Element
{
return (
<MDBox display="flex" alignItems="center">
<MDBox mr={1}>
<MDAvatar bgColor={color} src={image} alt={name} size="xs" />
</MDBox>
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{lineHeight: 0}}>
{name}
</MDTypography>
</MDBox>
);
}
// Declaring default props for CustomerCell
CustomerCell.defaultProps = {
image: "",
color: "dark",
};
export default CustomerCell;

View File

@ -1,45 +0,0 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
// Material Dashboard 2 PRO React TS components
import MDTypography from "components/MDTypography";
// Declaring props types for DefaultCell
interface Props {
value: string;
suffix?: string | boolean;
}
function DefaultCell({value, suffix}: Props): JSX.Element
{
return (
<MDTypography variant="caption" fontWeight="medium" color="text">
{value}
{suffix && (
<MDTypography variant="caption" fontWeight="medium" color="secondary">
&nbsp;&nbsp;
{suffix}
</MDTypography>
)}
</MDTypography>
);
}
// Declaring default props for DefaultCell
DefaultCell.defaultProps = {
suffix: "",
};
export default DefaultCell;

View File

@ -1,54 +0,0 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
// @mui material components
import Checkbox from "@mui/material/Checkbox";
// Material Dashboard 2 PRO React TS components
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import Link from "@mui/material/Link";
// Declaring props types for IdCell
interface Props {
id: string;
checked?: boolean;
}
function IdCell({id, checked}: Props): JSX.Element
{
const pathParts = window.location.pathname.split("/");
const tableName = pathParts[1];
const href = `/${tableName}/${id}`;
const link = <Link href={href}>{id}</Link>;
return (
<MDBox display="flex" alignItems="center">
<Checkbox defaultChecked={checked} />
<MDBox ml={1}>
<MDTypography variant="caption" fontWeight="medium" color="text">
{link}
</MDTypography>
</MDBox>
</MDBox>
);
}
// Declaring default props for IdCell
IdCell.defaultProps = {
checked: false,
};
export default IdCell;

View File

@ -1,57 +0,0 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
// @mui material components
import Icon from "@mui/material/Icon";
// Material Dashboard 2 PRO React TS components
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import MDButton from "components/MDButton";
// Declaring props types for StatusCell
interface Props {
icon: string;
color:
| "primary"
| "secondary"
| "info"
| "success"
| "warning"
| "error"
| "dark"
| "light"
| "white"
| "default";
status: string;
}
function StatusCell({icon, color, status}: Props): JSX.Element
{
return (
<MDBox display="flex" alignItems="center">
<MDBox mr={1}>
<MDButton variant="outlined" color={color} size="small" iconOnly circular>
<Icon sx={{fontWeight: "bold"}}>{icon}</Icon>
</MDButton>
</MDBox>
<MDTypography variant="caption" fontWeight="medium" color="text" sx={{lineHeight: 0}}>
{status}
</MDTypography>
</MDBox>
);
}
export default StatusCell;

View File

@ -1,193 +0,0 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
const selectData = {
gender: ["Male", "Female"],
birthDate: [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
],
days: [
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30",
],
years: [
"1900",
"1901",
"1902",
"1903",
"1904",
"1905",
"1906",
"1907",
"1908",
"1909",
"1910",
"1911",
"1912",
"1913",
"1914",
"1915",
"1915",
"1915",
"1916",
"1917",
"1918",
"1919",
"1920",
"1921",
"1922",
"1923",
"1924",
"1925",
"1926",
"1927",
"1928",
"1929",
"1930",
"1931",
"1932",
"1933",
"1934",
"1935",
"1936",
"1937",
"1938",
"1939",
"1940",
"1941",
"1942",
"1943",
"1944",
"1945",
"1946",
"1947",
"1948",
"1949",
"1950",
"1951",
"1952",
"1953",
"1954",
"1955",
"1956",
"1957",
"1958",
"1959",
"1960",
"1961",
"1962",
"1963",
"1964",
"1965",
"1966",
"1967",
"1968",
"1969",
"1970",
"1971",
"1972",
"1973",
"1974",
"1975",
"1976",
"1977",
"1978",
"1979",
"1980",
"1981",
"1982",
"1983",
"1984",
"1985",
"1986",
"1987",
"1988",
"1989",
"1990",
"1991",
"1992",
"1993",
"1994",
"1995",
"1996",
"1997",
"1998",
"1999",
"2000",
"2001",
"2002",
"2003",
"2004",
"2005",
"2006",
"2007",
"2008",
"2009",
"2010",
"2011",
"2012",
"2013",
"2014",
"2015",
"2016",
"2017",
"2018",
"2019",
"2020",
"2021",
],
skills: ["react", "vue", "angular", "svelte", "javascript"],
};
export default selectData;

View File

@ -1,13 +1,14 @@
// react components
import {useParams, useSearchParams} from "react-router-dom";
import {useParams, useNavigate, useLocation} from "react-router-dom";
import React, {useReducer, useState} from "react";
// misc components
import * as Yup from "yup";
import {Form, Formik} from "formik";
import {
Form, Formik, useFormik, useFormikContext,
} from "formik";
// qqq components
import {QController} from "@kingsrook/qqq-frontend-core/lib/controllers/QController";
import DynamicFormUtils from "qqq/components/QDynamicForm/utils/DynamicFormUtils";
import QDynamicForm from "qqq/components/QDynamicForm";
import {QFieldMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QFieldMetaData";
@ -20,45 +21,61 @@ import {Alert} from "@mui/material";
// Material Dashboard 2 PRO React TS components
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import MDButton from "../../../components/MDButton";
import QClient from "qqq/utils/QClient";
import {QTableMetaData} from "@kingsrook/qqq-frontend-core/lib/model/metaData/QTableMetaData";
import {QCancelButton, QSaveButton} from "qqq/components/QButtons";
import {QRecord} from "@kingsrook/qqq-frontend-core/lib/model/QRecord";
import Avatar from "@mui/material/Avatar";
import Icon from "@mui/material/Icon";
import QRecordSidebar from "qqq/components/QRecordSidebar";
import QTableUtils from "qqq/utils/QTableUtils";
// Declaring props types for EntityForm
interface Props
{
id?: string;
id?: string;
table?: QTableMetaData;
}
function EntityForm({id}: Props): JSX.Element
function EntityForm({table, id}: Props): JSX.Element
{
const qController = QClient.getInstance();
const {tableName} = useParams();
const tableNameParam = useParams().tableName;
const tableName = table === null ? tableNameParam : table.name;
const [validations, setValidations] = useState({});
const [initialValues, setInitialValues] = useState({} as { [key: string]: string });
const [formFields, setFormFields] = useState({});
const [formFields, setFormFields] = useState(null as Map<string, any>);
const [t1sectionName, setT1SectionName] = useState(null as string);
const [alertContent, setAlertContent] = useState("");
const [asyncLoadInited, setAsyncLoadInited] = useState(false);
const [formValues, setFormValues] = useState({} as { [key: string]: string });
const [tableMetaData, setTableMetaData] = useState(null);
const [tableMetaData, setTableMetaData] = useState(null as QTableMetaData);
const [record, setRecord] = useState(null as QRecord);
const [tableSections, setTableSections] = useState(null as any);
const [, forceUpdate] = useReducer((x) => x + 1, 0);
function getDynamicStepContent(formData: any): JSX.Element
const navigate = useNavigate();
const location = useLocation();
function getFormSection(values: any, touched: any, formFields: any, errors: any): JSX.Element
{
const {
formFields,
values,
errors,
touched,
} = formData;
const formData: any = {};
formData.values = values;
formData.touched = touched;
formData.errors = errors;
formData.formFields = {};
console.log(formFields);
for (let i = 0; i < formFields.length; i++)
{
formData.formFields[formFields[i].name] = formFields[i];
}
if (!Object.keys(formFields).length)
{
return <div>Loading...</div>;
}
return <QDynamicForm formData={formData} primaryKeyId={tableMetaData.primaryKeyField} />;
}
@ -70,20 +87,27 @@ function EntityForm({id}: Props): JSX.Element
const tableMetaData = await qController.loadTableMetaData(tableName);
setTableMetaData(tableMetaData);
/////////////////////////////////////////////////
// define the sections, e.g., for the left-bar //
/////////////////////////////////////////////////
const tableSections = QTableUtils.getSectionsForRecordSidebar(tableMetaData);
setTableSections(tableSections);
const fieldArray = [] as QFieldMetaData[];
const sortedKeys = [...tableMetaData.fields.keys()].sort();
sortedKeys.forEach((key) =>
{
const fieldMetaData = tableMetaData.fields.get(key);
if (fieldMetaData.isEditable)
{
fieldArray.push(fieldMetaData);
}
fieldArray.push(fieldMetaData);
});
/////////////////////////////////////////////////////////////////////////////////
// if doing an edit, fetch the record and pre-populate the form values from it //
/////////////////////////////////////////////////////////////////////////////////
if (id !== null)
{
const record = await qController.get(tableName, id);
setRecord(record);
tableMetaData.fields.forEach((fieldMetaData, key) =>
{
@ -92,20 +116,79 @@ function EntityForm({id}: Props): JSX.Element
setFormValues(formValues);
}
setInitialValues(initialValues);
/////////////////////////////////////////////////////////
// get formField and formValidation objects for Formik //
/////////////////////////////////////////////////////////
const {
dynamicFormFields,
formValidations,
} = DynamicFormUtils.getFormData(fieldArray);
setInitialValues(initialValues);
setFormFields(dynamicFormFields);
setValidations(Yup.object()
.shape(formValidations));
/////////////////////////////////////
// group the formFields by section //
/////////////////////////////////////
const dynamicFormFieldsBySection = new Map<string, any>();
let t1sectionName;
for (let i = 0; i < tableSections.length; i++)
{
const section = tableSections[i];
const sectionDynamicFormFields: any[] = [];
for (let j = 0; j < section.fieldNames.length; j++)
{
const fieldName = section.fieldNames[j];
const field = tableMetaData.fields.get(fieldName);
if (id !== null || field.isEditable)
{
sectionDynamicFormFields.push(dynamicFormFields[fieldName]);
}
}
if (sectionDynamicFormFields.length === 0)
{
////////////////////////////////////////////////////////////////////////////////////////////////
// in case there are no active fields in this section, remove it from the tableSections array //
////////////////////////////////////////////////////////////////////////////////////////////////
tableSections.splice(i, 1);
i--;
}
else
{
dynamicFormFieldsBySection.set(section.name, sectionDynamicFormFields);
}
//////////////////////////////////////
// capture the tier1 section's name //
//////////////////////////////////////
if (section.tier === "T1")
{
t1sectionName = section.name;
}
}
setT1SectionName(t1sectionName);
setFormFields(dynamicFormFieldsBySection);
setValidations(Yup.object().shape(formValidations));
forceUpdate();
})();
}
const handleCancelClicked = () =>
{
if (id !== null)
{
const path = `${location.pathname.replace(/\/edit$/, "")}`;
navigate(path, {replace: true});
}
else
{
const path = `${location.pathname.replace(/\/create$/, "")}`;
navigate(path, {replace: true});
}
};
const handleSubmit = async (values: any, actions: any) =>
{
actions.setSubmitting(true);
@ -117,9 +200,8 @@ function EntityForm({id}: Props): JSX.Element
.update(tableName, id, values)
.then((record) =>
{
window.location.href = `/${tableName}/${record.values.get(
tableMetaData.primaryKeyField,
)}?updateSuccess=true`;
const path = `${location.pathname.replace(/\/edit$/, "")}?updateSuccess=true`;
navigate(path);
})
.catch((error) =>
{
@ -132,9 +214,8 @@ function EntityForm({id}: Props): JSX.Element
.create(tableName, values)
.then((record) =>
{
window.location.href = `/${tableName}/${record.values.get(
tableMetaData.primaryKeyField,
)}?createSuccess=true`;
const path = `${location.pathname.replace(/create$/, record.values.get(tableMetaData.primaryKeyField))}?createSuccess=true`;
navigate(path);
})
.catch((error) =>
{
@ -144,8 +225,20 @@ function EntityForm({id}: Props): JSX.Element
})();
};
const formTitle = id != null ? `Edit ${tableMetaData?.label} (${id})` : `Create New ${tableMetaData?.label}`;
const formId = id != null ? `edit-${tableMetaData?.label}-form` : `create-${tableMetaData?.label}-form`;
let formTitle = "";
if (tableMetaData)
{
if (id == null)
{
formTitle = `Create new ${tableMetaData?.label}`;
}
else if (record != null)
{
formTitle = `Edit ${tableMetaData?.label}: ${record?.recordLabel}`;
}
}
const formId = id != null ? `edit-${tableMetaData?.name}-form` : `create-${tableMetaData?.name}-form`;
return (
<MDBox mb={3}>
@ -155,62 +248,91 @@ function EntityForm({id}: Props): JSX.Element
<MDBox mb={3}>
<Alert severity="error">{alertContent}</Alert>
</MDBox>
) : (
""
)}
<Card id="edit-form-container" sx={{overflow: "visible"}}>
<MDBox p={3}>
<MDTypography variant="h5">{formTitle}</MDTypography>
</MDBox>
<MDBox pb={3} px={3}>
<Grid key="fields-grid" container spacing={3}>
<Formik
initialValues={initialValues}
validationSchema={validations}
onSubmit={handleSubmit}
>
{({
values,
errors,
touched,
isSubmitting,
}) => (
<Form id={formId} autoComplete="off">
<MDBox p={3} width="100%">
{/***************************************************************************
** step content - e.g., the appropriate form or other screen for the step **
***************************************************************************/}
{getDynamicStepContent({
values,
touched,
formFields,
errors,
})}
<Grid key="buttonGrid" container spacing={3}>
<MDBox mt={5} ml="auto">
<MDButton type="submit" variant="gradient" color="dark" size="small">
save
{" "}
{tableMetaData?.label}
</MDButton>
</MDBox>
</Grid>
) : ("")}
</Grid>
</Grid>
<Grid container spacing={3}>
<Grid item xs={12} lg={3}>
<QRecordSidebar tableSections={tableSections} />
</Grid>
<Grid item xs={12} lg={9}>
<Formik
initialValues={initialValues}
validationSchema={validations}
onSubmit={handleSubmit}
>
{({
values,
errors,
touched,
isSubmitting,
}) => (
<Form id={formId} autoComplete="off">
<MDBox pb={3} pt={0}>
<Card id={`${t1sectionName}`} sx={{overflow: "visible"}}>
<MDBox display="flex" p={3} pb={1}>
<MDBox mr={1.5}>
<Avatar sx={{bgcolor: "rgb(26, 115, 232)"}}>
<Icon>
{tableMetaData?.iconName}
</Icon>
</Avatar>
</MDBox>
</Form>
)}
</Formik>
</Grid>
</MDBox>
</Card>
<MDBox display="flex" alignItems="center">
<MDTypography variant="h5">{formTitle}</MDTypography>
</MDBox>
</MDBox>
{
t1sectionName && formFields ? (
<MDBox pb={3} px={3}>
<MDBox p={3} width="100%">
{getFormSection(values, touched, formFields.get(t1sectionName), errors)}
</MDBox>
</MDBox>
) : null
}
</Card>
</MDBox>
{tableSections && formFields ? tableSections.map((section: any) => (section.name !== t1sectionName
? (
<MDBox key={`edit-card-${section.name}`} pb={3}>
<Card id={section.name} sx={{overflow: "visible"}}>
<MDTypography variant="h5" p={3} pb={1}>
{section.label}
</MDTypography>
<MDBox pb={3} px={3}>
<MDBox p={3} width="100%">
{
getFormSection(values, touched, formFields.get(section.name), errors)
}
</MDBox>
</MDBox>
</Card>
</MDBox>
) : null)) : null}
<MDBox component="div" p={3}>
<Grid container justifyContent="flex-end" spacing={3}>
<QCancelButton onClickHandler={handleCancelClicked} />
<QSaveButton />
</Grid>
</MDBox>
</Form>
)}
</Formik>
</Grid>
</Grid>
</MDBox>
);
}
// Declaring default props for DefaultCell
EntityForm.defaultProps = {
id: null,
table: null,
};
export default EntityForm;

View File

@ -1,17 +1,23 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// @mui material components
import Link from "@mui/material/Link";
@ -60,6 +66,9 @@ function Footer({company, links}: Props): JSX.Element
justifyContent="space-between"
alignItems="center"
px={1.5}
style={{
position: "fixed", bottom: "0px", zIndex: -1, marginBottom: "10px",
}}
>
<MDBox
display="flex"

View File

@ -1,7 +1,28 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {useState, useEffect} from "react";
// react-router components
import {useLocation, Link} from "react-router-dom";
import {useLocation} from "react-router-dom";
// @material-ui core components
import AppBar from "@mui/material/AppBar";
@ -16,7 +37,6 @@ import MDInput from "components/MDInput";
import MDBadge from "components/MDBadge";
// Material Dashboard 2 PRO React TS examples components
import Breadcrumbs from "examples/Breadcrumbs";
import NotificationItem from "examples/Items/NotificationItem";
// Custom styles for Navbar
@ -38,6 +58,7 @@ import {
} from "context";
// qqq
import QBreadcrumbs from "qqq/components/QBreadcrumbs";
// Declaring prop types for Navbar
interface Props
@ -106,9 +127,11 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
onClose={handleCloseMenu}
sx={{mt: 2}}
>
<NotificationItem icon={<Icon>email</Icon>} title="Check new messages" />
<NotificationItem icon={<Icon>email</Icon>} title="0 messages available" />
{/*
<NotificationItem icon={<Icon>podcasts</Icon>} title="Manage Podcast sessions" />
<NotificationItem icon={<Icon>shopping_cart</Icon>} title="Payment successfully completed" />
*/}
</Menu>
);
@ -145,7 +168,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
>
<Toolbar sx={navbarContainer}>
<MDBox color="inherit" mb={{xs: 1, md: 0}} sx={(theme) => navbarRow(theme, {isMini})}>
<Breadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
<QBreadcrumbs icon="home" title={breadcrumbTitle} route={route} light={light} />
<IconButton sx={navbarDesktopMenu} onClick={handleMiniSidenav} size="small" disableRipple>
<Icon fontSize="medium" sx={iconsStyle}>
{miniSidenav ? "menu_open" : "menu"}
@ -184,7 +207,7 @@ function Navbar({absolute, light, isMini}: Props): JSX.Element
sx={navbarIconButton}
onClick={handleOpenMenu}
>
<MDBadge badgeContent={9} color="error" size="xs" circular>
<MDBadge badgeContent={0} color="error" size="xs" circular>
<Icon sx={iconsStyle}>notifications</Icon>
</MDBadge>
</IconButton>

View File

@ -1,16 +1,22 @@
/**
=========================================================
* Material Dashboard 2 PRO React TS - v1.0.0
=========================================================
* Product Page: https://www.creative-tim.com/product/material-dashboard-2-pro-react-ts
* Copyright 2022 Creative Tim (https://www.creative-tim.com)
Coded by www.creative-tim.com
=========================================================
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// @mui material components

View File

@ -0,0 +1,105 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ReactNode} from "react";
// @mui material components
import Card from "@mui/material/Card";
import Divider from "@mui/material/Divider";
import Icon from "@mui/material/Icon";
// Material Dashboard 2 PRO React TS components
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
interface Props {
color?: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "light" | "dark";
title: string;
percentage?: {
color: "primary" | "secondary" | "info" | "success" | "warning" | "error" | "dark" | "white";
amount: string | number;
label: string;
};
icon: ReactNode;
[key: string]: any;
}
function ProcessLinkCard({
color, title, percentage, icon,
}: Props): JSX.Element
{
return (
<Card>
<MDBox display="flex" justifyContent="space-between" pt={1} px={2}>
<MDBox
variant="gradient"
bgColor={color}
color={color === "light" ? "dark" : "white"}
coloredShadow={color}
borderRadius="xl"
display="flex"
justifyContent="center"
alignItems="center"
width="4rem"
height="4rem"
mt={-3}
>
<Icon fontSize="medium" color="inherit">
{icon}
</Icon>
</MDBox>
<MDBox textAlign="right" lineHeight={1.25}>
<MDTypography variant="button" fontWeight="bold" color="text">
{title}
</MDTypography>
</MDBox>
</MDBox>
<Divider />
<MDBox pb={2} px={2}>
<MDTypography component="p" variant="button" color="text" display="flex">
<MDTypography
component="span"
variant="button"
fontWeight="bold"
color={percentage.color}
>
{percentage.amount}
</MDTypography>
Click here to run the process called
{" "}
{title}
.
{percentage.label}
</MDTypography>
</MDBox>
</Card>
);
}
ProcessLinkCard.defaultProps = {
color: "info",
percentage: {
color: "success",
text: "",
label: "",
},
};
export default ProcessLinkCard;

View File

@ -0,0 +1,133 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ReactNode} from "react";
import {Link} from "react-router-dom";
import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
import Icon from "@mui/material/Icon";
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
interface Props {
icon: ReactNode;
title: string;
route: string | string[];
light?: boolean;
[key: string]: any;
}
const ucFirst = (input: string): string =>
{
if (!input)
{
return (input);
}
return (input.substring(0, 1).toUpperCase() + input.substring(1));
};
const routeToLabel = (route: string): string =>
{
const label = ucFirst(route.replace(".", " ").replace("-", " ").replace("_", " ").replace(/([A-Z])/g, " $1"));
return (label);
};
function QBreadcrumbs({
icon, title, route, light,
}: Props): JSX.Element
{
const routes: string[] | any = route.slice(0, -1);
let pageTitle = "Nutrifresh One";
const fullRoutes: string[] = [];
let accumulatedPath = "";
for (let i = 0; i < routes.length; i++)
{
accumulatedPath = `${accumulatedPath}/${routes[i]}`;
fullRoutes.push(accumulatedPath);
pageTitle = `${routeToLabel(routes[i])} | ${pageTitle}`;
}
document.title = `${ucFirst(title)} | ${pageTitle}`;
return (
<MDBox mr={{xs: 0, xl: 8}}>
<MuiBreadcrumbs
sx={{
"& .MuiBreadcrumbs-separator": {
color: ({palette: {white, grey}}) => (light ? white.main : grey[600]),
},
}}
>
<Link to="/">
<MDTypography
component="span"
variant="body2"
color={light ? "white" : "dark"}
opacity={light ? 0.8 : 0.5}
sx={{lineHeight: 0}}
>
<Icon>{icon}</Icon>
</MDTypography>
</Link>
{fullRoutes.map((fullRoute: string) => (
<Link to={fullRoute} key={fullRoute}>
<MDTypography
component="span"
variant="button"
fontWeight="regular"
textTransform="capitalize"
color={light ? "white" : "dark"}
opacity={light ? 0.8 : 0.5}
sx={{lineHeight: 0}}
>
{routeToLabel(fullRoute.replace(/.*\//, ""))}
</MDTypography>
</Link>
))}
<MDTypography
variant="button"
fontWeight="regular"
textTransform="capitalize"
color={light ? "white" : "dark"}
sx={{lineHeight: 0}}
>
{routeToLabel(title)}
</MDTypography>
</MuiBreadcrumbs>
<MDTypography
fontWeight="bold"
textTransform="capitalize"
variant="h6"
color={light ? "white" : "dark"}
noWrap
>
{routeToLabel(title)}
</MDTypography>
</MDBox>
);
}
QBreadcrumbs.defaultProps = {
light: false,
};
export default QBreadcrumbs;

View File

@ -0,0 +1,126 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import MDBox from "components/MDBox";
import {Link} from "react-router-dom";
import MDButton from "components/MDButton";
import Icon from "@mui/material/Icon";
import React from "react";
// eslint-disable import/prefer-default-export
const standardWidth = "150px";
export function QCreateNewButton(): JSX.Element
{
return (
<MDBox ml={3} mr={2} width={standardWidth}>
<Link to="create">
<MDButton variant="gradient" color="info" fullWidth startIcon={<Icon>add</Icon>}>
Create New
</MDButton>
</Link>
</MDBox>
);
}
export function QSaveButton(): JSX.Element
{
return (
<MDBox ml={3} width={standardWidth}>
<MDButton type="submit" variant="gradient" color="info" size="small" fullWidth startIcon={<Icon>save</Icon>}>
Save
</MDButton>
</MDBox>
);
}
interface QDeleteButtonProps
{
onClickHandler: any
}
export function QDeleteButton({onClickHandler}: QDeleteButtonProps): JSX.Element
{
return (
<MDBox ml={3} mr={3}>
<MDBox width={standardWidth}>
<MDButton variant="gradient" color="primary" size="small" onClick={onClickHandler} fullWidth startIcon={<Icon>delete</Icon>}>
Delete
</MDButton>
</MDBox>
</MDBox>
);
}
export function QEditButton(): JSX.Element
{
return (
<MDBox>
<Link to="edit">
<MDBox width={standardWidth}>
<MDButton variant="gradient" color="dark" size="small" fullWidth startIcon={<Icon>edit</Icon>}>
Edit
</MDButton>
</MDBox>
</Link>
</MDBox>
);
}
interface QActionsMenuButtonProps
{
isOpen: boolean;
onClickHandler: any;
}
export function QActionsMenuButton({isOpen, onClickHandler}: QActionsMenuButtonProps): JSX.Element
{
return (
<MDBox width={standardWidth}>
<MDButton
variant={isOpen ? "contained" : "outlined"}
color="dark"
onClick={onClickHandler}
fullWidth
>
actions&nbsp;
<Icon>keyboard_arrow_down</Icon>
</MDButton>
</MDBox>
);
}
interface QCancelButtonProps
{
onClickHandler: any;
}
export function QCancelButton({onClickHandler}: QCancelButtonProps): JSX.Element
{
return (
<MDBox ml="auto" width={standardWidth}>
<MDButton type="button" variant="outlined" color="dark" size="small" fullWidth startIcon={<Icon>cancel</Icon>} onClick={onClickHandler}>
Cancel
</MDButton>
</MDBox>
);
}

View File

@ -77,10 +77,6 @@ function QDynamicForm(props: Props): JSX.Element
&& Object.keys(formFields).map((fieldName: any) =>
{
const field = formFields[fieldName];
if (primaryKeyId && fieldName === primaryKeyId)
{
return null;
}
if (values[fieldName] === undefined)
{
values[fieldName] = "";
@ -109,17 +105,18 @@ function QDynamicForm(props: Props): JSX.Element
// todo? inputProps={{ autoComplete: "" }}
// todo? placeholder={password.placeholder}
// todo? success={!errors[fieldName] && touched[fieldName]}
return (
<Grid item xs={12} sm={6} key={fieldName}>
<QDynamicFormField
type={field.type}
label={field.label}
isEditable={field.isEditable}
name={fieldName}
value={values[fieldName]}
error={errors[fieldName] && touched[fieldName]}
bulkEditMode={bulkEditMode}
bulkEditSwitchChangeHandler={bulkEditSwitchChanged}
success={!errors[fieldName] && touched[fieldName]}
/>
</Grid>
);

View File

@ -83,6 +83,7 @@ class DynamicFormUtils
name: field.name,
label: label,
isRequired: field.isRequired,
isEditable: field.isEditable,
type: fieldType,
// todo invalidMsg: "Zipcode is not valid (e.g. 70000).",
});

View File

@ -36,24 +36,33 @@ interface Props
{
label: string;
name: string;
type: string;
isEditable?: boolean;
[key: string]: any;
bulkEditMode?: boolean;
bulkEditSwitchChangeHandler?: any
}
function QDynamicFormField({
label, name, bulkEditMode, bulkEditSwitchChangeHandler, ...rest
label, name, bulkEditMode, bulkEditSwitchChangeHandler, type, isEditable, ...rest
}: Props): JSX.Element
{
const [switchChecked, setSwitchChecked] = useState(false);
const [isDisabled, setIsDisabled] = useState(bulkEditMode);
const [isDisabled, setIsDisabled] = useState(!isEditable || bulkEditMode);
const inputLabelProps = {};
if (type.toLowerCase().match("(date|time)"))
{
// @ts-ignore
inputLabelProps.shrink = true;
}
const field = () => (
<>
<Field {...rest} name={name} as={MDInput} variant="standard" label={label} fullWidth disabled={isDisabled} />
<Field {...rest} name={name} type={type} as={MDInput} variant="standard" label={label} InputLabelProps={inputLabelProps} fullWidth disabled={isDisabled} />
<MDBox mt={0.75}>
<MDTypography component="div" variant="caption" color="error" fontWeight="regular">
{!isDisabled && <ErrorMessage name={name} />}
{!isDisabled && <div className="fieldErrorMessage"><ErrorMessage name={name} /></div>}
</MDTypography>
</MDBox>
</>
@ -100,6 +109,7 @@ function QDynamicFormField({
QDynamicFormField.defaultProps = {
bulkEditMode: false,
isEditable: true,
bulkEditSwitchChangeHandler: () =>
{},
};

View File

@ -0,0 +1,84 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2022. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import React, {ReactNode} from "react";
import {Link} from "react-router-dom";
import {Breadcrumbs as MuiBreadcrumbs} from "@mui/material";
import Icon from "@mui/material/Icon";
import MDBox from "components/MDBox";
import MDTypography from "components/MDTypography";
import Card from "@mui/material/Card";
import {Theme} from "@mui/material/styles";
interface Props {
tableSections: any;
light?: boolean;
}
function QRecordSidebar({tableSections, light}: Props): JSX.Element
{
return (
<Card sx={{borderRadius: ({borders: {borderRadius}}) => borderRadius.lg, position: "sticky", top: "1%"}}>
<MDBox component="ul" display="flex" flexDirection="column" p={2} m={0} sx={{listStyle: "none"}}>
{
tableSections ? tableSections.map(({icon, label, name}: any, key: number) => (
<MDBox key={`section-${name}`} component="li" pt={key === 0 ? 0 : 1}>
<MDTypography
component="a"
href={`#${name}`}
variant="button"
fontWeight="regular"
sx={({
borders: {borderRadius}, functions: {pxToRem}, palette: {light}, transitions,
}: Theme) => ({
display: "flex",
alignItems: "center",
borderRadius: borderRadius.md,
padding: `${pxToRem(10)} ${pxToRem(16)}`,
transition: transitions.create("background-color", {
easing: transitions.easing.easeInOut,
duration: transitions.duration.shorter,
}),
"&:hover": {
backgroundColor: light.main,
},
})}
>
<MDBox mr={1.5} lineHeight={1} color="black">
<Icon fontSize="small">{icon}</Icon>
</MDBox>
{label}
</MDTypography>
</MDBox>
)) : null
}
</MDBox>
</Card>
);
}
QRecordSidebar.defaultProps = {
light: false,
};
export default QRecordSidebar;