Task#state
-> Task#status
(enum)
This commit is contained in:
parent
828e9dea15
commit
d475e83602
10 changed files with 99 additions and 31 deletions
|
@ -0,0 +1,12 @@
|
|||
class ChangeStateToStatus < ActiveRecord::Migration[7.1]
|
||||
def up
|
||||
rename_column :tasks, :state, :status
|
||||
Twenty::Task.where(status: "open").update_all(status: '0')
|
||||
Twenty::Task.where(status: "closed").update_all(status: '2')
|
||||
change_column :tasks, :status, :integer
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrrversibleMigration
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class ChangeStatusDefault < ActiveRecord::Migration[7.1]
|
||||
def up
|
||||
change_column :tasks, :status, :integer, default: 0
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrrversibleMigration
|
||||
end
|
||||
end
|
|
@ -3,24 +3,21 @@
|
|||
class Twenty::Task < Twenty::Model
|
||||
self.table_name = "tasks"
|
||||
|
||||
STATUS = {ready: 0, in_progress: 1, complete: 2}
|
||||
enum :status, STATUS, default: :ready
|
||||
|
||||
##
|
||||
# Validations
|
||||
validates :title, presence: true
|
||||
validates :content, presence: true
|
||||
validates :state, inclusion: {in: %w[open closed]}
|
||||
validates :project, presence: true
|
||||
|
||||
##
|
||||
# Associations
|
||||
belongs_to :project, class_name: "Twenty::Project"
|
||||
|
||||
##
|
||||
# Scopes
|
||||
scope :open, -> { where(state: "open") }
|
||||
scope :closed, -> { where(state: "closed") }
|
||||
|
||||
def to_json(options = {})
|
||||
{id:, title:, content:, state:,
|
||||
{id:, title:, content:, status:,
|
||||
project_id:, created_at:, updated_at:}.to_json(options)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,4 +10,11 @@ class Twenty::Servlet < WEBrick::HTTPServlet::AbstractServlet
|
|||
require_relative "servlet/mixin/response_mixin"
|
||||
extend ServerMixin
|
||||
include ResponseMixin
|
||||
|
||||
def parse_body(req, only: [], except: [])
|
||||
body = JSON.parse(req.body)
|
||||
body = only.size > 0 ? body.slice(*only) : body
|
||||
body = except.size > 0 ? body.except(*except) : body
|
||||
body
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ class Twenty::Servlet::Tasks < Twenty::Servlet
|
|||
def do_GET(req, res)
|
||||
case req.path_info
|
||||
when ""
|
||||
tasks = Twenty::Task.open.order(updated_at: :desc)
|
||||
tasks = Twenty::Task.ready.order(updated_at: :desc)
|
||||
ok(res, tasks:)
|
||||
when %r{\A/([\d]+)/?\z}
|
||||
task = Twenty::Task.find_by(id: $1)
|
||||
|
@ -22,7 +22,8 @@ class Twenty::Servlet::Tasks < Twenty::Servlet
|
|||
def do_POST(req, res)
|
||||
case req.path_info
|
||||
when ""
|
||||
task = Twenty::Task.new(JSON.parse(req.body))
|
||||
body = parse_body(req.body, only: ["title", "content", "project_id"])
|
||||
task = Twenty::Task.new(body)
|
||||
if task.save
|
||||
ok(res, task:)
|
||||
else
|
||||
|
@ -39,10 +40,10 @@ class Twenty::Servlet::Tasks < Twenty::Servlet
|
|||
def do_PUT(req, res)
|
||||
case req.path_info
|
||||
when ""
|
||||
body = JSON.parse(req.body)
|
||||
id = body.delete("id")
|
||||
body = parse_body(req, except: ["id"])
|
||||
id = parse_body(req, only: ["id"]).fetch("id", nil)
|
||||
task = Twenty::Task.find_by(id:)
|
||||
task.update(body) ? ok(res, task:) : not_found(res)
|
||||
task?.update(body) ? ok(res, task:) : not_found(res)
|
||||
else
|
||||
not_found(res)
|
||||
end
|
||||
|
|
|
@ -4,11 +4,13 @@ ul.items {
|
|||
list-style-type: none;
|
||||
|
||||
li.item {
|
||||
margin: 0 0 15px 0;
|
||||
display: flex;
|
||||
background: #f4f0ec;
|
||||
border: #cfcfc4 1px solid;
|
||||
&:hover {
|
||||
background: $gray1;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
display: flex;
|
||||
border-bottom: 1px solid $gray1;
|
||||
padding: 10px;
|
||||
flex-wrap: wrap;
|
||||
.top, .bottom {
|
||||
|
@ -20,6 +22,16 @@ ul.items {
|
|||
grid-gap: 5px;
|
||||
}
|
||||
}
|
||||
.top {
|
||||
a {
|
||||
$blue: #008cff;
|
||||
color: darken($blue, 10%);
|
||||
&:active, &:visited, &:link, &:hover {
|
||||
color: darken($blue, 10%);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
.bottom {
|
||||
margin: 10px 0 0 0;
|
||||
font-size: small;
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import React from "react";
|
||||
|
||||
export function TrashIcon({ onClick }: { onClick: () => unknown }) {
|
||||
export function TrashIcon({
|
||||
onClick,
|
||||
}: {
|
||||
onClick: (e: React.MouseEvent) => void;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
height="512"
|
||||
viewBox="0 0 128 128"
|
||||
width="512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={onClick}
|
||||
onClick={e => onClick(e)}
|
||||
className="trash icon"
|
||||
>
|
||||
<g>
|
||||
|
@ -20,13 +24,17 @@ export function TrashIcon({ onClick }: { onClick: () => unknown }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function DoneIcon({ onClick }: { onClick: () => unknown }) {
|
||||
export function DoneIcon({
|
||||
onClick,
|
||||
}: {
|
||||
onClick: (e: React.MouseEvent) => void;
|
||||
}) {
|
||||
return (
|
||||
<svg
|
||||
enable-background="new 0 0 24 24"
|
||||
enableBackground="new 0 0 24 24"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={onClick}
|
||||
onClick={e => onClick(e)}
|
||||
className="done icon"
|
||||
>
|
||||
<switch>
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useTasks } from "/hooks/useTasks";
|
|||
import { useDestroyTask } from "/hooks/useDestroyTask";
|
||||
import { TrashIcon, DoneIcon } from "/components/Icons";
|
||||
import { DateTime } from "luxon";
|
||||
import { Task } from "/types/schema";
|
||||
import { Task, TASK_COMPLETE } from "/types/schema";
|
||||
import { useUpsertTask } from "/hooks/useUpsertTask";
|
||||
import classnames from "classnames";
|
||||
|
||||
|
@ -40,7 +40,7 @@ export function Tasks() {
|
|||
};
|
||||
const onComplete = (task: Task) => {
|
||||
const action = () =>
|
||||
upsertTask({ input: { id: task.id, state: "closed" } });
|
||||
upsertTask({ input: { id: task.id, status: TASK_COMPLETE } });
|
||||
perform(action, { on: task, tasks, setTask: setCompletedTask });
|
||||
};
|
||||
|
||||
|
@ -58,15 +58,30 @@ export function Tasks() {
|
|||
const wasDestroyed = task === destroyedTask;
|
||||
const wasCompleted = task === completedTask;
|
||||
const classes = { completed: wasCompleted, removed: wasDestroyed };
|
||||
const editHref = `/tasks/edit#id=${task.id}`;
|
||||
return (
|
||||
<li className={classnames("item", classes)} key={key}>
|
||||
<li
|
||||
onClick={() => (location.href = editHref)}
|
||||
className={classnames("item", classes)}
|
||||
key={key}
|
||||
>
|
||||
<div className="top">
|
||||
<a href={`/tasks/edit#id=${task.id}`}>
|
||||
<a href={editHref}>
|
||||
<span className="item title">{task.title}</span>
|
||||
</a>
|
||||
<div className="actions">
|
||||
<DoneIcon onClick={() => onComplete(task)} />
|
||||
<TrashIcon onClick={() => onDestroy(task)} />
|
||||
<DoneIcon
|
||||
onClick={(e: React.MouseEvent) => [
|
||||
e.stopPropagation(),
|
||||
onComplete(task),
|
||||
]}
|
||||
/>
|
||||
<TrashIcon
|
||||
onClick={(e: React.MouseEvent) => [
|
||||
e.stopPropagation(),
|
||||
onDestroy(task),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bottom">
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { TASK_STATUS } from "/types/schema";
|
||||
|
||||
type Params = {
|
||||
id?: number;
|
||||
state?: "open" | "closed";
|
||||
status?: TASK_STATUS,
|
||||
title?: string;
|
||||
content?: string;
|
||||
projectId?: number;
|
||||
|
@ -8,8 +10,8 @@ type Params = {
|
|||
|
||||
export function useUpsertTask() {
|
||||
const normalize = (input: Params) => {
|
||||
const { id, title, content, state, projectId } = input;
|
||||
return { id, title, content, state, project_id: projectId };
|
||||
const { id, title, content, status, projectId } = input;
|
||||
return { id, title, content, status, project_id: projectId };
|
||||
};
|
||||
return function ({ input }: { input: Params }) {
|
||||
return new Promise((accept, reject) => {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
export const TASK_READY = "ready";
|
||||
export const TASK_IN_PROGRESS = "in_progress";
|
||||
export const TASK_COMPLETE = "complete";
|
||||
export type TASK_STATUS = "ready" | "in_progress" | "complete";
|
||||
|
||||
export type Project = {
|
||||
id: number;
|
||||
name: string;
|
||||
|
@ -8,7 +13,7 @@ export type Task = {
|
|||
id: number;
|
||||
title: string;
|
||||
content: string;
|
||||
state: "open" | "closed";
|
||||
status: TASK_STATUS;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
project_id: number;
|
||||
|
|
Loading…
Reference in a new issue