Skip to content

Commit

Permalink
Refactor search + Markdown preview + Bugfixes (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
gorillamoe authored May 18, 2024
1 parent 8a9dcef commit f07b6c2
Show file tree
Hide file tree
Showing 9 changed files with 1,493 additions and 108 deletions.
2 changes: 1 addition & 1 deletion docs/_coverpage.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# timetrack <small>3.1.0</small>
# timetrack <small>3.2.0</small>

> Simple desktop 🖥️ application to track your time ⏰ spent on different projects 🎉.
Expand Down
1,394 changes: 1,380 additions & 14 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "timetrack",
"productName": "timetrack",
"version": "3.1.0",
"version": "3.2.0",
"description": "Simple desktop application to track your time spent on different projects.",
"main": ".vite/build/main.js",
"scripts": {
Expand Down Expand Up @@ -72,7 +72,9 @@
"npm": "10.7.0",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-markdown": "9.0.1",
"react-redux": "9.1.2",
"remark-gfm": "4.0.0",
"remove": "0.1.5",
"sqlite": "5.1.1",
"sqlite3": "5.1.7"
Expand Down
2 changes: 1 addition & 1 deletion src/components/EditTaskDefinitionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { removeSelectedTaskDefinition } from './Store/slices/selectedTaskDefinit
interface BaseLayoutProps {
children?: ReactNode;
taskDefinition: DBTaskDefinition;
callback?: (status: boolean, editedTaskDefintionData?: DBTaskDefinition) => void;
callback?: (status: boolean, editedTaskDefinitionData?: DBTaskDefinition) => void;
}

export const EditTaskDefinitionModal: FC<BaseLayoutProps> = ({ callback, taskDefinition }) => {
Expand Down
67 changes: 55 additions & 12 deletions src/components/EditTaskModal.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,81 @@
import React, { FC } from 'react';
import React, { FC, useRef } from 'react';
import { connect } from 'react-redux';
import { RootState } from './Store';
import { TimeInputComponent } from './TimeInputComponent';
import { InfoboxComponent } from './InfoboxComponent';
import { useAppDispatch } from './Store/hooks';
import { replaceActiveTask } from './Store/slices/activeTasks';
import { replaceTask } from './Store/slices/tasks';

interface BaseLayoutProps {
activeTasks: ActiveTask[];
task: DBTask;
useRef: React.RefObject<HTMLDivElement>;
callback: (status: boolean) => void;
callback?: (status: boolean, editedTaskData?: DBTask) => void;
}

const Component: FC<BaseLayoutProps> = ({ activeTasks, callback, task, useRef }) => {
const onEditButtonClick = (evt: React.MouseEvent) => {
const Component: FC<BaseLayoutProps> = ({ activeTasks, callback, task }) => {
const ref = useRef<HTMLFormElement>(null)
const dispatch = useAppDispatch();

const onEditButtonClick = async (evt: React.MouseEvent) => {
evt.preventDefault();
if (callback) {
callback(true);
const form = ref.current;
const formData = new FormData(form);
const description = formData.get("description") as string
const seconds = formData.get("seconds") as string

const rpcResult = await window.electron.editTask({
name: task.name,
description,
project_name: task.project_name,
seconds: parseInt(seconds, 10),
date: task.date
})

if (rpcResult.success) {
const activeTask = activeTasks.find((at) => at.name === task.name && at.project_name === task.project_name && at.date === task.date)
if (activeTask) {
dispatch(replaceActiveTask({
name: rpcResult.name,
oldname: rpcResult.name,
project_name: rpcResult.project_name,
description: rpcResult.description,
date: rpcResult.date,
seconds: rpcResult.seconds,
isActive: activeTask.isActive
}))
}
dispatch(replaceTask({
name: rpcResult.name,
oldname: rpcResult.name,
seconds: rpcResult.seconds,
project_name: rpcResult.project_name,
date: rpcResult.date,
description: rpcResult.description,
}))
}

callback && callback(true, {
name: task.name,
project_name: task.project_name,
description,
seconds: parseInt(seconds),
date: task.date
})
}

const onCancelButtonClick = (evt: React.MouseEvent) => {
evt.preventDefault();
if (callback) {
callback(false);
}
callback && callback(false)
}

const activeTask = activeTasks.find(t => t.name === task.name && t.date === task.date && t.project_name === task.project_name)

return <>
<div className="modal is-active" ref={useRef}>
<div className="modal is-active">
<div className="modal-background"></div>
<div className="modal-card">
<form>
<form ref={ref}>
<header className="modal-card-head">
<p className="modal-card-title">Edit Task</p>
</header>
Expand Down
40 changes: 36 additions & 4 deletions src/components/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { FC, useRef, useState } from 'react';
import Markdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import moment from 'moment';
import { LoadingComponent } from './LoadingComponent';
import { EditProjectModal } from './EditProjectModal'
Expand All @@ -9,8 +11,8 @@ import { DeleteTaskDefinitionModal } from './DeleteTaskDefinitionModal'
import { RootState } from './Store';
import { connect } from 'react-redux';
import { InfoboxComponent } from './InfoboxComponent';
import { watch } from 'original-fs';
import { getHMSStringFromSeconds } from '@lib/Utils';
import { EditTaskDefinitionModal } from './EditTaskDefinitionModal';

type Props = {
activeTasks: ActiveTask[]
Expand Down Expand Up @@ -141,10 +143,24 @@ const SearchResultsTaskDefinitionsComponent: FC<Props> = ({ activeTasks, searchR
setModal(<DeleteTaskDefinitionModal taskDefinition={data} callback={(status) => delModalCallback(status, data)} />);
}

const editModalCallback = (status: boolean, data: DBTaskDefinition, editedData: DBTaskDefinition) => {
if (status) {
const f = (td: DBTaskDefinition) => {
if (td.name === data.name && td.project_name === data.project_name) {
return editedData;
}
return td;
}
searchResult.task_definitions = searchResult.task_definitions.map(f);
setSearchResults(searchResult);
}
setModal(null)
}

const showEditModal = (evt: React.MouseEvent<HTMLButtonElement>) => {
evt.preventDefault();
const data = JSON.parse(evt.currentTarget.dataset.data) as DBTaskDefinition;
// setModal(<EditTaskDefinitionModal task={data} callback={(status) => editModalCallback(status, data)} />);
setModal(<EditTaskDefinitionModal taskDefinition={data} callback={(status, editedData) => editModalCallback(status, data, editedData)} />);
}

type ButtonWrapperProps = {
Expand Down Expand Up @@ -222,10 +238,24 @@ const SearchResultsTasksComponent: FC<Props> = ({ activeTasks, searchResult, set
setModal(<DeleteTaskModal task={data} callback={(status) => delModalCallback(status, data)} />);
}

const editModalCallback = (status: boolean, data: DBTask) => {
if (status) {
const f = (t: DBTask) => {
if (t.name === data.name && t.project_name === data.project_name && t.date === data.date) {
return data;
}
return t;
}
searchResult.tasks = searchResult.tasks.map(f);
setSearchResults(searchResult);
}
setModal(null)
}

const showEditModal = (evt: React.MouseEvent<HTMLButtonElement>) => {
evt.preventDefault();
const data = JSON.parse(evt.currentTarget.dataset.data) as DBTask;
// setModal(<EditTaskModal task={data} callback={(status) => editModalCallback(status, data)} />);
setModal(<EditTaskModal task={data} callback={(status, editedData) => editModalCallback(status, editedData)} />);
}

type ButtonWrapperProps = {
Expand Down Expand Up @@ -272,7 +302,9 @@ const SearchResultsTasksComponent: FC<Props> = ({ activeTasks, searchResult, set
</div>
</div>
<div className="content">
{task.description}
{ task.description.length > 0 ?
<Markdown remarkPlugins={[remarkGfm]}>{task.description}</Markdown>
: null }
<br />
<div className="icon-text">
<span className="icon has-text-info">
Expand Down
87 changes: 15 additions & 72 deletions src/components/Tasks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FC, useEffect, useRef, useState } from 'react';
import moment from 'moment';
import { connect } from 'react-redux';
import { Datafetcher } from './../lib/Datafetcher';
import type { RootState } from './Store'
Expand All @@ -7,12 +8,12 @@ import { replaceTask, replaceTasks, appendTask, deleteTask } from './Store/slice
import { replaceTaskDefinitions } from './Store/slices/taskDefinitions'
import { setSelectedTask, removeSelectedTask } from './Store/slices/selectedTask'
import { appendActiveTask, replaceActiveTask } from './Store/slices/activeTasks'
import { ModalConfirm } from './ModalConfirm';
import { EditTaskModal } from './EditTaskModal';
import { TimerComponent } from './TimerComponent';
import { TimeInputComponent } from './TimeInputComponent';
import { InfoboxComponent } from './InfoboxComponent';
import clsx from 'clsx';
import { DeleteTaskModal } from './DeleteTaskModal';

type Props = {
selectedProject: {
Expand All @@ -30,9 +31,7 @@ const Component: FC<Props> = ({ selectedProject, activeTasks, tasks }) => {
const dispatch = useAppDispatch();
const tasksDefinitions = useAppSelector((state) => state.taskDefinitions.value)
const selectedTask = useAppSelector((state) => state.selectedTask.value)
const [useModalConfirm, setModalConfirm] = useState(null)
const [useModalEdit, setModalEdit] = useState(null)
const useModalEditRef = useRef(null)
const [modal, setModal] = useState(null)

const WrappedTimerComponent: FC<WrappedTimerComponentProps> = ({ task }) => {
const activeTask = activeTasks.find((at) => at.name === task.name && at.project_name === task.project_name && at.date === task.date)
Expand All @@ -54,7 +53,7 @@ const Component: FC<Props> = ({ selectedProject, activeTasks, tasks }) => {
description: formData.get('description') as string,
project_name: selectedProject.name as string,
seconds: parseInt(formData.get('seconds') as string),
date: new Date().toISOString().split('T')[0]
date: moment().local().format('YYYY-MM-DD')
}
const rpcResult = await window.electron.addTask({
name: task.name,
Expand Down Expand Up @@ -93,84 +92,29 @@ const Component: FC<Props> = ({ selectedProject, activeTasks, tasks }) => {
}
}

const onTaskEditCallback = async (status: boolean) => {
if (status) {
const form = useModalEditRef.current.querySelector('form') as HTMLFormElement;
const formData = new FormData(form);
const task = {
name: selectedTask.name,
description: formData.get('description') as string,
project_name: selectedTask.project_name as string,
seconds: parseInt(formData.get('seconds') as string, 10),
date: selectedTask.date
}
const rpcResult = await window.electron.editTask({
name: task.name,
description: task.description,
project_name: task.project_name,
seconds: task.seconds,
date: task.date
})

if (rpcResult.success) {
const activeTask = activeTasks.find((at) => at.name === task.name && at.project_name === task.project_name && at.date === task.date)
if (activeTask) {
dispatch(replaceActiveTask({
name: rpcResult.name,
oldname: rpcResult.name,
project_name: rpcResult.project_name,
description: rpcResult.description,
date: rpcResult.date,
seconds: rpcResult.seconds,
isActive: activeTask.isActive
}))
}
dispatch(replaceTask({
name: rpcResult.name,
oldname: rpcResult.name,
seconds: rpcResult.seconds,
project_name: rpcResult.project_name,
date: rpcResult.date,
description: rpcResult.description,
}))
}
}
setModalEdit(null)
const onTaskEditCallback = async () => {
setModal(null)
}

const onTaskEditClick = async (evt: React.MouseEvent) => {
evt.preventDefault();
const task = tasks.find((t) => t.name === selectedTask.name && t.project_name === selectedTask.project_name && t.date === selectedTask.date)
if (task) {
setModalEdit(<EditTaskModal callback={onTaskEditCallback} useRef={useModalEditRef} task={task} />)
setModal(<EditTaskModal callback={onTaskEditCallback} task={task} />)
}
}

const onConfirmCallback = async (status: boolean) => {
const onTaskDeleteCallback = async (status: boolean) => {
if (status) {
const task = tasks.find((t) => t.name === selectedTask.name && t.project_name === selectedTask.project_name && t.date === selectedTask.date)
if (task) {
// TODO make sure task is not running on the backend
const rpcResult = await window.electron.deleteTask({
name: task.name,
project_name: task.project_name,
date: task.date
})
if (rpcResult.success) {
dispatch(deleteTask({ name: task.name, project_name: task.project_name, date: task.date }));
dispatch(removeSelectedTask());
// TODO fix
// dirty hack to update
window.location.reload()
}
}
dispatch(removeSelectedTask());
}
setModalConfirm(null)
setModal(null)
}

const onTaskDeleteClick = async (evt: React.MouseEvent) => {
const onTaskDeleteClick = async (evt: React.MouseEvent<HTMLButtonElement>) => {
evt.preventDefault();
setModalConfirm(<ModalConfirm message="Are you sure you want to delete this task?" callback={onConfirmCallback} />)
const task = JSON.parse(evt.currentTarget.dataset.data as string);
setModal(<DeleteTaskModal task={task} callback={onTaskDeleteCallback} />)
}

const fetchTaskDefinitions = async () => {
Expand Down Expand Up @@ -211,7 +155,7 @@ const Component: FC<Props> = ({ selectedProject, activeTasks, tasks }) => {
return <>
<button className="button is-primary m-1" onClick={onTaskStartClick}>Start</button>
<button className="button is-warning m-1" onClick={onTaskEditClick}>Edit</button>
<button className="button is-danger m-1" onClick={onTaskDeleteClick}>Delete</button>
<button className="button is-danger m-1" data-data={JSON.stringify(selectedTask)} onClick={onTaskDeleteClick}>Delete</button>
</>
}
}
Expand All @@ -231,8 +175,7 @@ const Component: FC<Props> = ({ selectedProject, activeTasks, tasks }) => {

if (tasksDefinitions.length) {
return <>
{useModalConfirm}
{useModalEdit}
{modal}
<section className="section">
<h1 className="title">Tasks</h1>
<h2 className="subtitle">All available Tasks for a given project</h2>
Expand Down
3 changes: 1 addition & 2 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const getSearchResult = async (
.replace('all', 'active = 1 OR active = 0')
.replace('active', 'active = 1')
.replace('inactive', 'active = 0')
console.log('active_state', active_state)
const search_in = q.search_in
const project_name = q.task.project_name.replace(/\*/g, '%')
const task_name = q.task.task_name.replace(/\*/g, '%')
Expand Down Expand Up @@ -278,7 +277,7 @@ const getTasksToday = async (
project_name: string,
): Promise<DBTask[]> => {
const tasks = await db.all(
'SELECT * FROM tasks WHERE project_name = ? AND date=DATE("now")',
'SELECT * FROM tasks WHERE project_name = ? AND date=DATE("now", "localtime")',
project_name,
)
return tasks
Expand Down
2 changes: 1 addition & 1 deletion src/db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS tasks (
name TEXT NOT NULL DEFAULT 'default',
project_name TEXT NOT NULL,
description TEXT NULL DEFAULT NULL,
date DATE NOT NULL DEFAULT (DATE('now')),
date DATE NOT NULL DEFAULT (DATE('now','localtime')),
seconds INTEGER NOT NULL DEFAULT 0
);
CREATE UNIQUE INDEX tasks_name_date_pjn_IDX ON tasks (name,date,project_name);
Expand Down

0 comments on commit f07b6c2

Please sign in to comment.