Skip to content
This repository has been archived by the owner on Jan 20, 2024. It is now read-only.

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
tdemin committed Jan 10, 2020
2 parents f80141f + 2bb047d commit 9e75647
Show file tree
Hide file tree
Showing 14 changed files with 237 additions and 193 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.json
Expand Up @@ -110,7 +110,7 @@
"ignoreEnums": true,
"enforceConst": true,
"ignoreReadonlyClassProperties": true,
"ignore": [0, 1, 2]
"ignore": [0, 1, 2, 1000]
}
],
"@typescript-eslint/brace-style": ["error", "1tbs"],
Expand Down
17 changes: 9 additions & 8 deletions package.json
@@ -1,24 +1,25 @@
{
"name": "amber_web",
"version": "0.0.8",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/jest": "^24.X",
"@types/node": "^12.12.21",
"@types/react": "^16.9.17",
"@types/react-dom": "^16.9.4",
"@types/react-redux": "^7.1.5",
"@types/react-router-dom": "^5.1.3",
"axios": "^0.19.0",
"@types/node": "^13.X",
"@types/react": "^16.9.X",
"@types/react-dom": "^16.9.X",
"@types/react-redux": "^7.1.X",
"@types/react-router-dom": "^5.1.X",
"axios": "^0.19.1",
"bulma": "^0.8.0",
"date-fns": "^2.9.0",
"node-sass": "^4.13.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-localization": "^1.0.15",
"react-redux": "^7.1.3",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
"redux": "^4.0.4",
"redux": "^4.0.5",
"redux-devtools-extension": "^2.13.8",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
Expand Down
2 changes: 1 addition & 1 deletion src/const.ts
@@ -1,7 +1,7 @@
export const baseURI: string =
process.env.REACT_APP_APIURI || "https://amber.h.tdem.in/api/v0";

export const appVersion: string = "0.0.8";
export const appVersion: string = "0.1.0";
export const appFullName: string = "Amber Web";
export const appName: string = "amber_web";
export const appAuthor: string = "Timur Demin";
Expand Down
8 changes: 8 additions & 0 deletions src/helpers/datetime.ts
@@ -0,0 +1,8 @@
export const DateFormat = "yyyy-MM-dd";
export const TimeFormat = "HH:mm";

/**
* Converts a JS date to epoch timestamp.
* @param date Date in JS `Date` class
*/
export const dateTimeToUnixTime = (date: Date) => date.getTime();
4 changes: 4 additions & 0 deletions src/helpers/tasks.ts
Expand Up @@ -11,6 +11,8 @@ export const taskFromRecord = (task: TaskRecord): Task => {
newTask.Text = "text" in task ? (task.text as string) : "";
newTask.Completed = task.status !== 0;
newTask.LastMod = task.last_mod as number;
newTask.Deadline = "deadline" in task ? (task.deadline as number) : 0;
newTask.Reminder = "reminder" in task ? (task.reminder as number) : 0;
return newTask;
};

Expand All @@ -24,6 +26,8 @@ export const taskToRecord = (task: Task): TaskRecord => {
parent_id: task.PID,
text: task.Text,
status: task.Completed ? 1 : 0,
deadline: task.Deadline,
reminder: task.Reminder,
} as TaskRecord;
return record;
};
Expand Down
18 changes: 16 additions & 2 deletions src/reducers/tasks.ts
Expand Up @@ -2,8 +2,22 @@ import Actions from "../actions/list";
import { TaskAction } from "../typings/actions";
import { Task } from "../typings/tasks";

/** Sorts a task array in ascending order. Returns a new sorted array. */
const sort = (tasks: Task[]) => [...tasks.sort((a, b) => a.ID - b.ID)];
/** Sorts a task array in ascending order by:
* 1. Reminder date.
* 2. Deadline date.
* 3. ID.
* Returns a new sorted array. */
const sort = (tasks: Task[]) => [
...tasks.sort((a, b) => {
if (a.Reminder !== b.Reminder) {
return b.Reminder - a.Reminder;
}
if (a.Deadline !== b.Deadline) {
return b.Deadline - a.Deadline;
}
return a.ID - b.ID;
}),
];

export interface TaskState {
tasks: Task[];
Expand Down
6 changes: 4 additions & 2 deletions src/typings/tasks.ts
Expand Up @@ -4,9 +4,7 @@ export interface TaskRecord {
text?: string;
status: number;
last_mod?: number;
/** TODO: Not implemented yet. */
deadline?: number;
/** TODO: Not implemented yet. */
reminder?: number;
}

Expand All @@ -16,6 +14,8 @@ export class Task {
Text: string;
Completed: boolean;
LastMod: number;
Deadline: number;
Reminder: number;
/** Informational field for tasks that have been created offline, those are
* to be pushed to the server at the next sync. */
ToSync: boolean;
Expand All @@ -31,5 +31,7 @@ export class Task {
this.LastMod = Date.now();
this.ToSync = false;
this.ToRemove = false;
this.Deadline = 0;
this.Reminder = 0;
}
}
2 changes: 2 additions & 0 deletions src/views/assets/locales.ts
Expand Up @@ -34,6 +34,8 @@ export default new LocalizedStrings({
editor_textTp: "Text:",
editor_parentTp: "Parent:",
editor_parentNoParentVal: "No parent",
editor_deadline: "Deadline:",
editor_reminder: "Reminder:",
task_toggleBtnCompleted: "Completed",
task_toggleBtnPending: "Pending",
app_versionString: `${appFullName} v${appVersion} by ${appAuthor}`,
Expand Down
68 changes: 68 additions & 0 deletions src/views/components/dateTimePicker.tsx
@@ -0,0 +1,68 @@
import React from "react";
import parse from "date-fns/parse";
import format from "date-fns/format";

import {
dateTimeToUnixTime,
DateFormat,
TimeFormat,
} from "../../helpers/datetime";

interface Props {
dateRequired?: boolean;
timeRequired?: boolean;
initialValue?: number;
onChange?: (date: number) => void;
}
interface State {
date: Date;
}
export class DateTimePicker extends React.Component<Props, State> {
state = {
date: new Date(this.props.initialValue || 0),
};
componentDidUpdate = (_p: Props, prevState: State) => {
if (this.props.onChange) {
if (prevState.date !== this.state.date) {
this.props.onChange(dateTimeToUnixTime(this.state.date));
}
}
};
updateDate = (e: React.FormEvent<HTMLInputElement>) => {
// event input is a string of format like "2020-01-03"
// eslint-disable-next-line react/no-access-state-in-setstate
const date = parse(e.currentTarget.value, DateFormat, this.state.date);
this.setState({ date });
};
updateTime = (e: React.FormEvent<HTMLInputElement>) => {
// input is a string of format like "13:45"
// eslint-disable-next-line react/no-access-state-in-setstate
const date = parse(e.currentTarget.value, TimeFormat, this.state.date);
this.setState({ date });
};
inputInitValue = (date: Date, fmt: string): string => {
if (date.getTime()) {
return format(date, fmt);
}
return "";
};
// TODO: add an "Unset" button
render = () => (
<div>
<input
type="date"
onChange={this.updateDate}
value={this.inputInitValue(this.state.date, DateFormat)}
required={this.props.dateRequired}
/>
<input
type="time"
onChange={this.updateTime}
value={this.inputInitValue(this.state.date, TimeFormat)}
required={this.props.timeRequired}
/>
</div>
);
}

export default DateTimePicker;
60 changes: 47 additions & 13 deletions src/views/components/taskLine.tsx
@@ -1,22 +1,25 @@
import React from "react";
import { connect } from "react-redux";
import { ThunkDispatch } from "redux-thunk";
import { withRouter, RouteComponentProps as RCP } from "react-router";
import format from "date-fns/format";

import Level from "../components/bulma/level";
import Button from "../components/bulma/button";
import Link from "../components/link";

import { TaskAction } from "../../typings/actions";
import { Task } from "../../typings/tasks";
import { Dispatch } from "../../typings/react";

import { updateTask } from "../../actions/tasks";

import strings from "../assets/locales";

interface Props {
const mapDispatchToProps = (dispatch: Dispatch) => ({
update: (task: Task) => dispatch(updateTask(task)),
});

interface Props extends ReturnType<typeof mapDispatchToProps>, RCP {
level: number;
task: Task;
dispatch: ThunkDispatch<any, any, TaskAction>;
}
interface State {
task: Task;
Expand All @@ -35,22 +38,53 @@ class TaskLine extends React.Component<Props, State> {
toggleTask = (): void => {
const task: Task = { ...this.state.task };
task.Completed = !task.Completed;
this.props.dispatch(updateTask(task));
this.props.update(task);
};
// Pp expands to a localized date string that looks like this:
// "05/29/1453, 12:00 AM"
fmtDate = (date: number) => format(new Date(date), "Pp");
gotoEditor = () => this.props.history.push(`/task/${this.state.task.ID}`);
render = () => {
const { ID, Completed, ToRemove, Text } = this.state.task;
const {
ID,
Completed,
ToRemove,
Text,
Deadline,
Reminder,
} = this.state.task;
let classNames: string[] = ["taskLine"];
Completed && classNames.push("taskCompleted");
ToRemove && classNames.push("taskToRemove");
Deadline && classNames.push("taskHasDeadline");
Reminder && classNames.push("taskHasReminder");
return (
<Level level isMobile className={classNames.join(" ")}>
<Level levelItem levelLeft className="taskText">
<Link to={`/task/${ID}`}>
<span className="taskID">{`#${ID} - `}</span>
<Level
level
levelItem
levelLeft
className="taskData"
onClick={this.gotoEditor}
>
<Level levelItem className="taskTextWrapper">
<span className="taskID">{`#${ID}`}</span>
{/* eslint-disable react/jsx-no-literals */}
&nbsp;
<span className="taskText">{Text}</span>
</Link>
</Level>
<Level levelItem className="taskDates">
<span className="taskDeadline">
{`(D: ${this.fmtDate(Deadline)})`}
{/* eslint-disable react/jsx-no-literals */}
&nbsp;
</span>
<span className="taskReminder">
{`(R: ${this.fmtDate(Reminder)})`}
</span>
</Level>
</Level>
<Level levelItem levelRight>
<Level levelItem levelRight className="taskButton">
<Button
value={
Completed
Expand All @@ -65,4 +99,4 @@ class TaskLine extends React.Component<Props, State> {
};
}

export default connect()(TaskLine);
export default withRouter(connect(null, mapDispatchToProps)(TaskLine));
8 changes: 1 addition & 7 deletions src/views/components/taskSelect.tsx
@@ -1,15 +1,9 @@
import React, { HTMLAttributes } from "react";
import { connect } from "react-redux";

import { Store } from "../../typings/store";
import { Task } from "../../typings/tasks";

import strings from "../assets/locales";

const mapStateToProps = (state: Store) => ({
tasks: state.task.tasks,
});

interface Props extends HTMLAttributes<HTMLSelectElement> {
tasks: Task[];
current: Task;
Expand Down Expand Up @@ -38,4 +32,4 @@ const TaskSelect: React.FC<Props> = (props) => (
</div>
);

export default connect(mapStateToProps)(TaskSelect);
export default TaskSelect;

0 comments on commit 9e75647

Please sign in to comment.