diff --git a/twenty-backend/lib/twenty-backend/migration/6_change_state_to_status.rb b/twenty-backend/lib/twenty-backend/migration/6_change_state_to_status.rb new file mode 100644 index 0000000..54e2064 --- /dev/null +++ b/twenty-backend/lib/twenty-backend/migration/6_change_state_to_status.rb @@ -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 diff --git a/twenty-backend/lib/twenty-backend/migration/7_change_status_default.rb b/twenty-backend/lib/twenty-backend/migration/7_change_status_default.rb new file mode 100644 index 0000000..7296106 --- /dev/null +++ b/twenty-backend/lib/twenty-backend/migration/7_change_status_default.rb @@ -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 diff --git a/twenty-backend/lib/twenty-backend/model/task.rb b/twenty-backend/lib/twenty-backend/model/task.rb index d587342..ac07a51 100644 --- a/twenty-backend/lib/twenty-backend/model/task.rb +++ b/twenty-backend/lib/twenty-backend/model/task.rb @@ -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 diff --git a/twenty-backend/lib/twenty-backend/servlet.rb b/twenty-backend/lib/twenty-backend/servlet.rb index f00bf3e..1c631d9 100644 --- a/twenty-backend/lib/twenty-backend/servlet.rb +++ b/twenty-backend/lib/twenty-backend/servlet.rb @@ -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 diff --git a/twenty-backend/lib/twenty-backend/servlet/tasks.rb b/twenty-backend/lib/twenty-backend/servlet/tasks.rb index 87f9a96..91d1346 100644 --- a/twenty-backend/lib/twenty-backend/servlet/tasks.rb +++ b/twenty-backend/lib/twenty-backend/servlet/tasks.rb @@ -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 diff --git a/twenty-frontend/src/css/_items.scss b/twenty-frontend/src/css/_items.scss index 75011be..0a8768f 100644 --- a/twenty-frontend/src/css/_items.scss +++ b/twenty-frontend/src/css/_items.scss @@ -4,11 +4,13 @@ ul.items { list-style-type: none; li.item { - margin: 0 0 15px 0; + &:hover { + background: $gray1; + border-radius: 10px; + cursor: pointer; + } display: flex; - background: #f4f0ec; - border: #cfcfc4 1px solid; - border-radius: 10px; + 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; diff --git a/twenty-frontend/src/js/components/Icons.tsx b/twenty-frontend/src/js/components/Icons.tsx index 72f3e18..44f677d 100644 --- a/twenty-frontend/src/js/components/Icons.tsx +++ b/twenty-frontend/src/js/components/Icons.tsx @@ -1,13 +1,17 @@ import React from "react"; -export function TrashIcon({ onClick }: { onClick: () => unknown }) { +export function TrashIcon({ + onClick, +}: { + onClick: (e: React.MouseEvent) => void; +}) { return ( onClick(e)} className="trash icon" > @@ -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 ( onClick(e)} className="done icon" > diff --git a/twenty-frontend/src/js/components/Tasks.tsx b/twenty-frontend/src/js/components/Tasks.tsx index 5bd8d7b..ff26f37 100644 --- a/twenty-frontend/src/js/components/Tasks.tsx +++ b/twenty-frontend/src/js/components/Tasks.tsx @@ -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 ( -
  • +
  • (location.href = editHref)} + className={classnames("item", classes)} + key={key} + >
    - + {task.title}
    - onComplete(task)} /> - onDestroy(task)} /> + [ + e.stopPropagation(), + onComplete(task), + ]} + /> + [ + e.stopPropagation(), + onDestroy(task), + ]} + />
    diff --git a/twenty-frontend/src/js/hooks/useUpsertTask.ts b/twenty-frontend/src/js/hooks/useUpsertTask.ts index 067be45..5bc57f6 100644 --- a/twenty-frontend/src/js/hooks/useUpsertTask.ts +++ b/twenty-frontend/src/js/hooks/useUpsertTask.ts @@ -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) => { diff --git a/twenty-frontend/src/js/types/schema.ts b/twenty-frontend/src/js/types/schema.ts index e0bc868..7c2797c 100644 --- a/twenty-frontend/src/js/types/schema.ts +++ b/twenty-frontend/src/js/types/schema.ts @@ -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;