Added job searching, sider keys, formatting on settings

This commit is contained in:
Patrick Fic
2020-10-19 13:03:57 -07:00
parent 2c696425b6
commit a11c44e444
29 changed files with 357 additions and 61 deletions

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,16 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text)\r\n RETURNS SETOF
jobs\r\n LANGUAGE plpgsql\r\n STABLE\r\nAS $function$ BEGIN if search = '' then
return query\r\nselect *\r\nfrom jobs j;\r\nelse return query\r\nSELECT *\r\nFROM
jobs j2\r\nWHERE ownr_fn ILIKE '%' || search || '%'\r\n or ownr_ln ILIKE
'%' || search || '%'\r\n \r\n or clm_no ILIKE '%' || search || '%'\r\n\r\nORDER
BY \r\n clm_no ILIKE '%' || search || '%'\r\n OR null,\r\n ownr_fn
ILIKE '%' || search || '%'\r\n OR NULL,\r\n ownr_ln ILIKE '%' || search
|| '%'\r\n OR NULL;\r\nend if;\r\nEND $function$;"
type: run_sql
- args:
name: search_jobs
schema: public
type: track_function

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,5 @@
- args:
cascade: true
read_only: false
sql: drop FUNCTION public.search_jobs;
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,16 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text, startDate date,
endDate date)\n RETURNS SETOF jobs\n LANGUAGE plpgsql\n STABLE\nAS $function$
BEGIN if search = '' then return query\nselect *\nfrom jobs j;\nelse return
query\nSELECT *\nFROM jobs j2\nWHERE ownr_fn ILIKE '%' || search || '%'\n or
ownr_ln ILIKE '%' || search || '%'\n \n or clm_no ILIKE '%' || search ||
'%'\n\nORDER BY \n clm_no ILIKE '%' || search || '%'\n OR null,\n ownr_fn
ILIKE '%' || search || '%'\n OR NULL,\n ownr_ln ILIKE '%' || search ||
'%'\n OR NULL;\nend if;\nEND $function$;"
type: run_sql
- args:
name: search_jobs
schema: public
type: track_function

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,13 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text, startDate date,
endDate date)\n RETURNS SETOF jobs\n LANGUAGE plpgsql\n STABLE\nAS $function$
BEGIN if search = '' then return query\nselect *\nfrom jobs j;\nelse return
query\nSELECT *\nFROM jobs j2\nWHERE \nclose_date between startDate and endDate
\ and\n(\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search
|| '%'\n \n or clm_no ILIKE '%' || search || '%')\n\nORDER BY \n clm_no
ILIKE '%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search ||
'%'\n OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\nend
if;\nEND $function$;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,18 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text, startDate date,
endDate date)\n RETURNS SETOF jobs\n LANGUAGE plpgsql\n STABLE\nAS $function$
BEGIN if search = '' then return query\nselect *\nfrom jobs j;\nelse\n\nif startDate
is null || endDate is null then \nreturn query\nSELECT *\nFROM jobs j2\nWHERE
\n\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search ||
'%'\n \n or clm_no ILIKE '%' || search || '%'\nORDER BY \n clm_no ILIKE
'%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search || '%'\n
\ OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\nelse \nreturn
query\nSELECT *\nFROM jobs j2\nWHERE \nclose_date between startDate and endDate
\ and\n(\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search
|| '%'\n \n or clm_no ILIKE '%' || search || '%')\n\nORDER BY \n clm_no
ILIKE '%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search ||
'%'\n OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\n\nend
if;\n\n\nend if;\nEND $function$;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,18 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text, startdate date,
enddate date)\n RETURNS SETOF jobs\n LANGUAGE plpgsql\n STABLE\nAS $function$
BEGIN if search = '' then return query\nselect *\nfrom jobs j;\nelse\n\nif (startDate
is null) || (endDate is null) then \nreturn query\nSELECT *\nFROM jobs j2\nWHERE
\n\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search ||
'%'\n \n or clm_no ILIKE '%' || search || '%'\nORDER BY \n clm_no ILIKE
'%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search || '%'\n
\ OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\nelse \nreturn
query\nSELECT *\nFROM jobs j2\nWHERE \nclose_date between startDate and endDate
\ and\n(\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search
|| '%'\n \n or clm_no ILIKE '%' || search || '%')\n\nORDER BY \n clm_no
ILIKE '%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search ||
'%'\n OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\n\nend
if;\n\n\nend if;\nEND $function$;"
type: run_sql

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,18 @@
- args:
cascade: true
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_jobs(search text, startdate date,
enddate date)\n RETURNS SETOF jobs\n LANGUAGE plpgsql\n STABLE\nAS $function$
BEGIN if search = '' then return query\nselect *\nfrom jobs j;\nelse\n\nif (startDate
is null) or (endDate is null) then \nreturn query\nSELECT *\nFROM jobs j2\nWHERE
\n\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search ||
'%'\n \n or clm_no ILIKE '%' || search || '%'\nORDER BY \n clm_no ILIKE
'%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search || '%'\n
\ OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\nelse \nreturn
query\nSELECT *\nFROM jobs j2\nWHERE \nclose_date between startDate and endDate
\ and\n(\nownr_fn ILIKE '%' || search || '%'\n or ownr_ln ILIKE '%' || search
|| '%'\n \n or clm_no ILIKE '%' || search || '%')\n\nORDER BY \n clm_no
ILIKE '%' || search || '%'\n OR null,\n ownr_fn ILIKE '%' || search ||
'%'\n OR NULL,\n ownr_ln ILIKE '%' || search || '%'\n OR NULL;\n\nend
if;\n\n\nend if;\nEND $function$;"
type: run_sql

View File

@@ -329,3 +329,7 @@ tables:
- type
- group
filter: {}
functions:
- function:
schema: public
name: search_jobs

View File

@@ -50,6 +50,17 @@ body {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #188fff;
}
.jobs-list-container {
height: 100%;
}
.jobs-list-infinite-container {
overflow-y: auto;
overflow-x: hidden;
height: 100%;
}
//Required for the tab with infinite loading
.ant-tabs-content {

View File

@@ -1,8 +1,8 @@
import { Skeleton, Typography } from "antd";
import Dinero from "dinero.js";
import React, { useMemo } from "react";
import { Cell, Pie, PieChart, ResponsiveContainer } from "recharts";
import ErrorResultAtom from "../error-result/error-result.atom";
import Dinero from "dinero.js";
import partTypeConverterAtom from "../part-type-converter/part-type-converter.atom";
export default function JobPartsGraphAtom({
job,
@@ -36,7 +36,7 @@ export default function JobPartsGraphAtom({
if (loading) return <Skeleton active />;
if (!job) return <ErrorResultAtom title="Error displaying job data." />;
console.log("data", data);
return (
<div
style={{

View File

@@ -14,8 +14,8 @@ const mapStateToProps = createStructuredSelector({
export function WatcherStatusAtom({ watcherStatus, watcherError }) {
return (
<div>
{watcherStatus}
<div style={{ color: watcherStatus === "Started" ? "green" : "tomato" }}>
<strong>{watcherStatus}</strong>
{watcherError && <Alert message={watcherError} />}
</div>
);

View File

@@ -0,0 +1,38 @@
import { SearchOutlined } from "@ant-design/icons";
import { Button, DatePicker, Form, Input } from "antd";
import React from "react";
export default function JobsSearchFieldsMolecule({ callSearchQuery }) {
const [form] = Form.useForm();
const handleFinish = (values) => {
callSearchQuery({
variables: {
search: values.search,
startDate: (values.dateRange && values.dateRange[0]) || null,
endDate: (values.dateRange && values.dateRange[1]) || null,
},
});
};
return (
<div>
<Form
autoComplete="new-password"
size="small"
onFinish={handleFinish}
form={form}
>
<Form.Item name="search">
<Input placeholder="Search by Claim # or Name" />
</Form.Item>
<Form.Item name="dateRange" rules={[{ type: "array" }]}>
<DatePicker.RangePicker />
</Form.Item>
<Button type="primary" htmlType="submit" onClick={() => form.submit()}>
<SearchOutlined />
Search
</Button>
</Form>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { Button, Input, Form, Select, InputNumber } from "antd";
import { Button, Input, Form, Select, InputNumber, Typography } from "antd";
import FormListMoveArrows from "../../atoms/form-list-move-arrows/form-list-move-arrows.atom";
import React from "react";
import LayoutFormRow from "../../atoms/layout-form-row/layout-form-row.atom";
@@ -7,6 +7,7 @@ import { DeleteFilled } from "@ant-design/icons";
export default function ShopSettingsFormMolecule({ form, saveLoading }) {
return (
<div>
<Typography.Title>Shop Settings</Typography.Title>
<Button
type="primary"
loading={saveLoading}
@@ -14,31 +15,33 @@ export default function ShopSettingsFormMolecule({ form, saveLoading }) {
>
Save
</Button>
<Form.Item
label="Shop Name"
name="shopname"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="accepted_ins_co"
label="Accepted Insurance Company Names (must be exactly as in estimating system)"
rules={[
{
required: true,
type: "array",
},
]}
>
<Select mode="tags" />
</Form.Item>
<LayoutFormRow grow>
<Form.Item
label="Shop Name"
name="shopname"
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="accepted_ins_co"
label="Accepted Insurance Company Names (must be exactly as in estimating system)"
rules={[
{
required: true,
type: "array",
},
]}
>
<Select mode="tags" />
</Form.Item>
</LayoutFormRow>
<Typography.Title level={4}>Group Definitions</Typography.Title>
<Form.List name={["targets"]}>
{(fields, { add, remove, move }) => {
return (

View File

@@ -1,4 +1,4 @@
import { List } from "antd";
import { List, Typography } from "antd";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -24,7 +24,7 @@ export function FilePathsList({ watchedPaths }) {
console.log("watchedPaths", watchedPaths);
return (
<div>
File Paths
<Typography.Title>Watcher File Paths</Typography.Title>
<List dataSource={watchedPaths || []} renderItem={FilepathItemMolecule} />
<FilepathAddMolecule />
</div>

View File

@@ -3,24 +3,12 @@ import { useQuery } from "@apollo/client";
import { Dropdown, List, Menu, Spin } from "antd";
import React, { useState } from "react";
import InfiniteScroll from "react-infinite-scroller";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_ALL_JOBS_PAGINATED } from "../../../graphql/jobs.queries";
import { setSelectedJobId } from "../../../redux/application/application.actions";
import { selectSelectedJobId } from "../../../redux/application/application.selectors";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import JobsListItemMolecule from "../../molecules/jobs-list-item/jobs-list-item.molecule";
import "./jobs-list-latest.organism.styles.scss";
const mapStateToProps = createStructuredSelector({
selectedJobId: selectSelectedJobId,
});
const mapDispatchToProps = (dispatch) => ({
setSelectedJobId: (jobId) => dispatch(setSelectedJobId(jobId)),
});
const limit = 20;
export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
export default function JobsTableOrganism() {
const [state, setState] = useState({ hasMore: true });
const { loading, error, data, refetch, fetchMore } = useQuery(
@@ -110,4 +98,3 @@ export function JobsTableOrganism({ selectedJobId, setSelectedJobId }) {
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobsTableOrganism);

View File

@@ -1,9 +0,0 @@
.jobs-list-container {
height: 100%;
}
.jobs-list-infinite-container {
overflow-y: auto;
overflow-x: hidden;
height: 100%;
}

View File

@@ -0,0 +1,102 @@
import { SyncOutlined } from "@ant-design/icons";
import { useLazyQuery } from "@apollo/client";
import { Dropdown, List, Menu, Spin } from "antd";
import React, { useState } from "react";
import InfiniteScroll from "react-infinite-scroller";
import { SEARCH_JOBS_PAGINATED } from "../../../graphql/jobs.queries";
import ErrorResultAtom from "../../atoms/error-result/error-result.atom";
import JobsListItemMolecule from "../../molecules/jobs-list-item/jobs-list-item.molecule";
import JobsSearchFieldsMolecule from "../../molecules/jobs-search-fields/jobs-search-fields.molecule";
const limit = 20;
export default function JobsTableOrganism() {
const [state, setState] = useState({ hasMore: true });
const [
callSearch,
{ loading, error, data, refetch, fetchMore },
] = useLazyQuery(SEARCH_JOBS_PAGINATED, {
variables: {
offset: 0,
limit: limit,
},
});
const menu = (
<Menu>
<Menu.Item onClick={() => refetch()}>
<SyncOutlined />
Reload
</Menu.Item>
</Menu>
);
const handleInfiniteOnLoad = (page) => {
if (fetchMore)
fetchMore({
variables: {
offset: limit * page,
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) {
console.log("No more results. Fetch More was empty.");
setState({ ...state, hasMore: false });
return prev;
}
const newCache = Object.assign({}, prev, {
jobs: [...prev.search_jobs, ...fetchMoreResult.search_jobs],
});
if (
newCache.jobs.length >= data &&
data.search_jobs_aggregate.aggregate.count
) {
console.log("No more results.");
setState({ ...state, hasMore: false });
}
return newCache;
},
});
};
if (error)
return (
<ErrorResultAtom
title="Error fetching Jobs data."
errorMessage={JSON.stringify(error)}
/>
);
return (
<div className="jobs-list-container">
<JobsSearchFieldsMolecule callSearchQuery={callSearch} />
<div className="jobs-list-infinite-container">
<InfiniteScroll
pageStart={0}
loadMore={handleInfiniteOnLoad}
hasMore={!loading && state.hasMore}
useWindow={false}
>
<Dropdown overlay={menu} trigger={["contextMenu"]}>
<List
bordered
dataSource={data ? data.search_jobs : []}
renderItem={(item) => <JobsListItemMolecule item={item} />}
>
{loading && state.hasMore && (
<div>
<Spin />
</div>
)}
</List>
</Dropdown>
</InfiniteScroll>
</div>
{`${data ? data.search_jobs.length : 0} jobs loaded. ${
data ? data.search_jobs_aggregate.aggregate.count : 0
} total jobs.`}
</div>
);
}

View File

@@ -8,15 +8,18 @@ import React from "react";
import { Link } from "react-router-dom";
import ipcTypes from "../../../ipc.types";
import SiderSignOut from "../../molecules/sider-sign-out/sider-sign-out.molecule";
import { useLocation } from "react-router-dom";
const { ipcRenderer } = window;
export default function SiderMenuOrganism() {
const { pathname } = useLocation();
return (
<Menu defaultSelectedKeys={["1"]} mode="inline">
<Menu.Item key="1" icon={<PieChartOutlined />}>
<Menu defaultSelectedKeys={[`/`]} selectedKeys={[pathname]} mode="inline">
<Menu.Item key="/" icon={<PieChartOutlined />}>
<Link to="/">Jobs</Link>
</Menu.Item>
<Menu.Item key="2" icon={<SettingFilled />}>
<Menu.Item key="/settings" icon={<SettingFilled />}>
<Link to="/settings">Settings</Link>
</Menu.Item>
<SiderSignOut />

View File

@@ -1,3 +1,4 @@
import { Typography } from "antd";
import React from "react";
import WatcherStatusAtom from "../../atoms/watcher-status/watcher-status.atom";
import WatcherStartMolecule from "../../molecules/watcher-start/watcher-start.molecule";
@@ -6,6 +7,7 @@ import WatcherStopMolecule from "../../molecules/watcher-stop/watcher-stop.molec
export default function WatcherManagerOrganism() {
return (
<div>
<Typography.Title level={4}>Watcher Status</Typography.Title>
<WatcherStatusAtom />
<WatcherStartMolecule />
<WatcherStopMolecule />

View File

@@ -4,6 +4,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import JobsDetailOrganism from "../../organisms/jobs-detail/jobs-detail.organism";
import JobsListOrganism from "../../organisms/jobs-list-latest/jobs-list-latest.organism";
import JobsListSearchOrganism from "../../organisms/jobs-list-search/jobs-list-search.organism";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({});
@@ -13,12 +14,12 @@ export function JobsPage() {
<div style={{ height: "100%" }}>
<Row gutter={[16, 16]} style={{ height: "100%" }}>
<Col span={6} style={{ height: "100%" }}>
<Tabs defaultActiveKey="search" style={{ height: "100%" }}>
<Tabs defaultActiveKey="latest" style={{ height: "100%" }}>
<Tabs.TabPane key="latest" tab="Latest" style={{ height: "100%" }}>
<JobsListOrganism />
</Tabs.TabPane>
<Tabs.TabPane key="search" tab="Search" style={{ height: "100%" }}>
Search
<JobsListSearchOrganism />
</Tabs.TabPane>
</Tabs>
</Col>

View File

@@ -1,12 +1,21 @@
import { Col, Row } from "antd";
import React from "react";
import FilePathsListOrganism from "../../organisms/filepaths-list/filepaths-list.organism";
import ShopSettingsOrganism from "../../organisms/shop-settings/shop-settings.organism";
import WatcherManagerOrganism from "../../organisms/watcher-manager/watcher-manager.organism";
export default function SettingsPage() {
return (
<div>
<FilePathsListOrganism />
<WatcherManagerOrganism />
<Row gutter={[16, 16]}>
<Col span={18}>
<FilePathsListOrganism />
</Col>
<Col span={6}>
<WatcherManagerOrganism />
</Col>
</Row>
<ShopSettingsOrganism />
</div>
);

View File

@@ -51,6 +51,40 @@ export const QUERY_ALL_JOBS_PAGINATED = gql`
}
`;
export const SEARCH_JOBS_PAGINATED = gql`
query SEARCH_JOBS_PAGINATED(
$offset: Int
$limit: Int
$search: String
$startDate: date
$endDate: date
) {
search_jobs(
offset: $offset
limit: $limit
args: { enddate: $endDate, search: $search, startdate: $startDate }
) {
ownr_fn
ownr_ln
v_vin
v_model_yr
v_model
v_makedesc
id
ins_co_nm
clm_no
updated_at
}
search_jobs_aggregate(
args: { enddate: $endDate, search: $search, startdate: $startDate }
) {
aggregate {
count(distinct: true)
}
}
}
`;
export const QUERY_JOB_BY_PK = gql`
query QUERY_JOB_BY_PK($jobId: uuid!) {
jobs_by_pk(id: $jobId) {