- Progress Commit

Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
Dave Richer
2024-03-20 18:18:24 -04:00
parent 27c24619c3
commit f31ae9ac6d
11 changed files with 488 additions and 98 deletions

View File

@@ -1,74 +1,136 @@
import {SyncOutlined} from "@ant-design/icons";
import {Button, Card, Input, Space, Table, Typography} from "antd";
import {Button, Card, Space, Switch, Table} from "antd";
import queryString from "query-string";
import React, {useState} from "react";
import React, {useCallback, useState} from "react";
import {useTranslation} from "react-i18next";
import {useLocation, useNavigate} from "react-router-dom";
import {pageLimit} from "../../utils/config";
import dayjs from "../../utils/day";
import {
CheckCircleFilled,
CheckCircleOutlined,
DeleteFilled,
DeleteOutlined, ExclamationCircleFilled,
SyncOutlined
} from "@ant-design/icons";
const DueDateRecord = ({dueDate}) => {
if (dueDate) {
const dueDateFormatted = dayjs(dueDate).format("YYYY-MM-DD");
const relativeDueDate = dayjs(dueDate).fromNow();
const today = dayjs().format("YYYY-MM-DD");
if (dueDateFormatted < today) {
return <div title={relativeDueDate} style={{color: 'red'}}>{dueDateFormatted}</div>;
} else {
return <div title={relativeDueDate}>{dueDateFormatted}</div>;
}
} else {
return <div>N/A</div>;
}
}
const PriorityLabel = ({priority}) => {
switch(priority) {
case 1:
return <div>
<ExclamationCircleFilled style={{color: 'red'}} /> High
</div>;
case 2:
return <div>
<ExclamationCircleFilled style={{ color: 'yellow' }} /> Medium
</div>;
case 3:
return <div>
<ExclamationCircleFilled style={{ color: 'green' }} /> Low
</div>;
default:
return <div>
<ExclamationCircleFilled style={{ color: 'black' }} /> None
</div>;
}
}
export default function TaskListComponent({
loading,
tasks,
total,
refetch,
toggleCompletedStatus,
}) {
const {t} = useTranslation();
const search = queryString.parse(useLocation().search);
// Extract Query Params
const {
page,
// sortcolumn, sortorder
sortcolumn,
sortorder,
deleted,
completed
} = search;
const history = useNavigate();
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {text: ""},
});
const {t} = useTranslation();
const columns = [
{
title: t("tasks.fields.title"),
dataIndex: "title",
key: "title",
sorter: true,
sortOrder: sortcolumn === "title" && sortorder,
},
{
title: t("tasks.fields.description"),
dataIndex: "description",
key: "description",
sorter: true,
sortOrder: sortcolumn === "description" && sortorder,
},
{
title: t("tasks.fields.due_date"),
dataIndex: "due_date",
key: "due_date",
},
{
title: t("tasks.fields.assigned_to"),
dataIndex: "assigned_to",
key: "assigned_to",
},
{
title: t("tasks.fields.completed"),
dataIndex: "completed",
key: "completed",
},
{
title: t("tasks.fields.completed_at"),
dataIndex: "completed_at",
key: "completed_at",
},
{
title: t("tasks.fields.remind_at"),
dataIndex: "remind_at",
key: "remind_at",
sorter: true,
sortOrder: sortcolumn === "due_date" && sortorder,
width: '5%',
render: (text, record) => <DueDateRecord dueDate={record.due_date} />,
},
{
title: t("tasks.fields.priority"),
dataIndex: "priority",
key: "priority",
sorter: true,
sortOrder: sortcolumn === "priority" && sortorder,
width: '5%',
render: (text, record) => <PriorityLabel priority={record.priority}
/>
},
{
title: t("tasks.fields.actions"),
key: "toggleCompleted",
width: '5%',
render: (text, record) => (
<Space direction='horizontal'>
<Button title={t('tasks.actions.toggle_completed')}
onClick={() => toggleCompletedStatus(record.id, record.completed)}>
{record.completed ? <CheckCircleOutlined/> :
<CheckCircleFilled/>}
</Button>
<Button title={t('tasks.actions.toggle_deleted')}
onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
{record.deleted ? <DeleteFilled/> : <DeleteOutlined/>}
</Button>
</Space>
),
},
];
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {text: ""},
});
// Columns definition remains the same
const handleTableChange = (pagination, filters, sorter) => {
setState({...state, filteredInfo: filters, sortedInfo: sorter});
search.page = pagination.current;
@@ -76,43 +138,49 @@ export default function TaskListComponent({
search.sortorder = sorter.order;
history({search: queryString.stringify(search)});
};
const handleSwitchChange = (param, value) => {
if (value) {
search[param] = "true";
} else {
delete search[param];
}
history({search: queryString.stringify(search)});
};
/**
* Extra actions for the tasks
* @type {Function}
*/
const tasksExtra = useCallback(() => {
return (
<Space direction='horizontal'>
<Switch
checkedChildren={<CheckCircleFilled/>}
unCheckedChildren={<CheckCircleOutlined/>}
title={t('tasks.titles.completed')}
checked={completed === "true"}
onChange={(value) => handleSwitchChange('completed', value)}
/>
<Switch
checkedChildren={<DeleteOutlined/>}
unCheckedChildren={<DeleteFilled/>}
title={t('tasks.titles.deleted')}
checked={deleted === "true"}
onChange={(value) => handleSwitchChange('deleted', value)}
/>
<Button title={t('tasks.titles.refresh')}
onClick={() => refetch()}>
<SyncOutlined/>
</Button>
</Space>
);
}, [refetch, deleted, completed]);
return (
<Card
title={t("menus.header.tasks")}
extra={
<Space wrap>
{search.search && (
<>
<Typography.Title level={4}>
{t("general.labels.searchresults", {search: search.search})}
</Typography.Title>
<Button
onClick={() => {
delete search.search;
history({search: queryString.stringify(search)});
}}
>
{t("general.actions.clear")}
</Button>
</>
)}
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Input.Search
placeholder={search.search || t("general.labels.search")}
onSearch={(value) => {
if (value?.length >= 3) {
search.search = value;
} else {
delete search.search;
}
history({search: queryString.stringify(search)});
}}
enterButton
/>
</Space>
}
extra={tasksExtra()}
>
<Table
loading={loading}

View File

@@ -1,21 +1,28 @@
import queryString from "query-string";
import {useLocation} from "react-router-dom";
import {useQuery} from "@apollo/client";
import {QUERY_ALL_TASKS_PAGINATED} from "../../graphql/tasks.queries.js";
import {useMutation, useQuery} from "@apollo/client";
import {
MUTATION_TOGGLE_TASK_COMPLETED, MUTATION_TOGGLE_TASK_DELETED,
QUERY_MY_TASKS_PAGINATED
} from "../../graphql/tasks.queries.js";
import {pageLimit} from "../../utils/config.js";
import AlertComponent from "../alert/alert.component.jsx";
import React from "react";
import TaskListComponent from "./task-list.component.jsx";
export default function TaskListContainer() {
export default function TaskListContainer({bodyshop, currentUser}) {
const searchParams = queryString.parse(useLocation().search);
const {page, sortcolumn, sortorder, search} = searchParams;
const {page, sortcolumn, sortorder, deleted, completed} = searchParams;
const {loading, error, data, refetch} = useQuery(
QUERY_ALL_TASKS_PAGINATED,
QUERY_MY_TASKS_PAGINATED,
{
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
bodyshop: bodyshop.id,
user: currentUser.email,
deleted: deleted === 'true',
completed: completed === "true",
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
@@ -30,6 +37,34 @@ export default function TaskListContainer() {
},
}
);
const [toggleTaskCompleted] = useMutation(MUTATION_TOGGLE_TASK_COMPLETED);
const toggleCompletedStatus = async (id, currentStatus) => {
const completed_at = !currentStatus ? new Date().toISOString() : null;
await toggleTaskCompleted({
variables: {
id: id,
completed: !currentStatus,
completed_at: completed_at
}
});
refetch();
};
const [toggleTaskDeleted] = useMutation(MUTATION_TOGGLE_TASK_DELETED);
const toggleDeletedStatus = async (id, currentStatus) => {
const deleted_at = !currentStatus ? new Date().toISOString() : null;
await toggleTaskDeleted({
variables: {
id: id,
deleted: !currentStatus,
deleted_at: deleted_at
}
});
refetch();
};
if (error) return <AlertComponent message={error.message} type="error"/>;
@@ -39,6 +74,8 @@ export default function TaskListContainer() {
tasks={data ? data.tasks : null}
total={data ? data.tasks_aggregate.aggregate.count : 0}
refetch={refetch}
toggleCompletedStatus={toggleCompletedStatus}
toggleDeletedStatus={toggleDeletedStatus}
/>
);
}

View File

@@ -1,5 +1,9 @@
import {gql} from "@apollo/client";
/**
* All tasks paginated query
* @type {DocumentNode}
*/
export const QUERY_ALL_TASKS_PAGINATED = gql`
query QUERY_ALL_TASKS_PAGINATED(
$offset: Int
@@ -30,10 +34,98 @@ export const QUERY_ALL_TASKS_PAGINATED = gql`
partsorderid
billid
}
tasks_aggregate(args: {}) {
tasks_aggregate {
aggregate {
count(distinct: true)
count
}
}
}
`;
/**
* My tasks paginated query
* @type {DocumentNode}
*/
export const QUERY_MY_TASKS_PAGINATED = gql`
query QUERY_TASKS_PAGINATED(
$offset: Int
$limit: Int
$user: String!
$bodyshop: uuid!
$deleted: Boolean
$completed: Boolean
$order: [tasks_order_by!]!
) {
tasks(
offset: $offset
limit: $limit
order_by: $order
where: {
assigned_to: {_eq: $user},
bodyshopid: {_eq: $bodyshop},
deleted: {_eq: $deleted},
completed: {_eq: $completed}
}
) {
id
created_at
updated_at
title
description
deleted
deleted_at
due_date
created_by
assigned_to
completed
completed_at
remind_at
priority
jobid
joblineid
partsorderid
billid
bodyshopid
}
tasks_aggregate(
where: {
assigned_to: {_eq: $user},
bodyshopid: {_eq: $bodyshop},
deleted: {_eq: $deleted},
completed: {_eq: $completed}
}
) {
aggregate {
count
}
}
}
`;
/**
* Toggle task completed mutation
* @type {DocumentNode}
*/
export const MUTATION_TOGGLE_TASK_COMPLETED = gql`
mutation MUTATION_TOGGLE_TASK_COMPLETED($id: uuid!, $completed: Boolean!, $completed_at: timestamptz) {
update_tasks_by_pk(pk_columns: {id: $id}, _set: {completed: $completed, completed_at: $completed_at}) {
id
completed
completed_at
}
}
`;
/**
* Toggle task deleted mutation
* @type {DocumentNode}
*/
export const MUTATION_TOGGLE_TASK_DELETED = gql`
mutation MUTATION_TOGGLE_TASK_DELETED($id: uuid!, $deleted: Boolean!, $deleted_at: timestamptz) {
update_tasks_by_pk(pk_columns: {id: $id}, _set: {deleted: $deleted, deleted_at: $deleted_at}) {
id
deleted
deleted_at
}
}
`;

View File

@@ -5,7 +5,7 @@ export default function TasksPageComponent({bodyshop, currentUser}) {
return (
<div>
<TaskListContainer />
<TaskListContainer bodyshop={bodyshop} currentUser={currentUser} />
</div>
);
}

View File

@@ -1,6 +1,6 @@
actions: [ ]
actions: []
custom_types:
enums: [ ]
input_objects: [ ]
objects: [ ]
scalars: [ ]
enums: []
input_objects: []
objects: []
scalars: []

View File

@@ -1 +1 @@
[ ]
[]

View File

@@ -1 +1 @@
[ ]
[]

View File

@@ -1 +1 @@
{ }
{}

View File

@@ -1 +1 @@
[ ]
[]

View File

@@ -1 +1 @@
[ ]
[]

View File

@@ -53,7 +53,7 @@
update_permissions:
- role: user
permission:
columns: [ ]
columns: []
filter:
jobline:
job:
@@ -569,6 +569,13 @@
table:
name: parts_orders
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: billid
table:
name: tasks
schema: public
insert_permissions:
- role: user
permission:
@@ -698,7 +705,7 @@
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: { }
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
@@ -818,6 +825,13 @@
table:
name: inventory
schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: ioevents
schema: public
- name: jobs
using:
foreign_key_constraint_on:
@@ -846,6 +860,13 @@
table:
name: phonebook
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: tasks
schema: public
- name: timetickets
using:
foreign_key_constraint_on:
@@ -1653,7 +1674,7 @@
columns:
- config
- id
filter: { }
filter: {}
limit: 1
- role: user
permission:
@@ -2491,7 +2512,7 @@
- effective_date
- end_date
- content
filter: { }
filter: {}
- table:
name: exportlog
schema: public
@@ -2675,6 +2696,13 @@
- table:
name: ioevents
schema: public
object_relationships:
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: user
using:
foreign_key_constraint_on: useremail
- table:
name: job_ar_schema
schema: public
@@ -2824,6 +2852,13 @@
table:
name: parts_order_lines
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: joblineid
table:
name: tasks
schema: public
insert_permissions:
- role: user
permission:
@@ -3311,6 +3346,13 @@
table:
name: scoreboard
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: jobid
table:
name: tasks
schema: public
- name: timetickets
using:
foreign_key_constraint_on:
@@ -4200,13 +4242,13 @@
interval_sec: 10
num_retries: 0
timeout_sec: 60
webhook: https://worktest.home.irony.online
webhook_from_env: HASURA_API_URL
headers:
- name: event-secret
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: { }
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/job/statustransition'
version: 2
@@ -4220,7 +4262,7 @@
webhook_from_env: HASURA_API_URL
request_transform:
method: POST
query_params: { }
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/record-handler/arms'
version: 2
@@ -4243,7 +4285,7 @@
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: { }
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
@@ -4256,13 +4298,13 @@
columns:
- key
- value
filter: { }
filter: {}
- role: user
permission:
columns:
- key
- value
filter: { }
filter: {}
- table:
name: messages
schema: public
@@ -4694,7 +4736,7 @@
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: { }
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
@@ -5008,6 +5050,13 @@
table:
name: parts_order_lines
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: partsorderid
table:
name: tasks
schema: public
insert_permissions:
- role: user
permission:
@@ -5302,7 +5351,7 @@
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: { }
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2
@@ -5623,6 +5672,129 @@
_eq: X-Hasura-User-Id
- active:
_eq: true
- table:
name: tasks
schema: public
object_relationships:
- name: bill
using:
foreign_key_constraint_on: billid
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: job
using:
foreign_key_constraint_on: jobid
- name: jobline
using:
foreign_key_constraint_on: joblineid
- name: parts_order
using:
foreign_key_constraint_on: partsorderid
- name: user
using:
foreign_key_constraint_on: assigned_to
- name: userByCreatedBy
using:
foreign_key_constraint_on: created_by
insert_permissions:
- role: user
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
select_permissions:
- role: user
permission:
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
allow_aggregations: true
update_permissions:
- role: user
permission:
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
- table:
name: timetickets
schema: public
@@ -5843,7 +6015,7 @@
- user:
authid:
_eq: X-Hasura-User-Id
check: { }
check: {}
- table:
name: tt_approval_queue
schema: public
@@ -6006,6 +6178,13 @@
table:
name: exportlog
schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: useremail
table:
name: ioevents
schema: public
- name: messages
using:
foreign_key_constraint_on:
@@ -6034,6 +6213,20 @@
table:
name: parts_orders
schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: assigned_to
table:
name: tasks
schema: public
- name: tasksByCreatedBy
using:
foreign_key_constraint_on:
column: created_by
table:
name: tasks
schema: public
- name: timetickets
using:
foreign_key_constraint_on:
@@ -6051,7 +6244,7 @@
insert_permissions:
- role: user
permission:
check: { }
check: {}
columns:
- authid
- email
@@ -6253,7 +6446,7 @@
value_from_env: EVENT_SECRET
request_transform:
method: POST
query_params: { }
query_params: {}
template_engine: Kriti
url: '{{$base_url}}/opensearch'
version: 2