Task#state -> Task#status (enum)

This commit is contained in:
0x1eef 2023-12-24 17:32:13 -03:00
parent 828e9dea15
commit d475e83602
10 changed files with 99 additions and 31 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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>

View file

@ -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">

View file

@ -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) => {

View file

@ -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;