Add project priorities
This commit is contained in:
parent
71d0834ff5
commit
768e5cbcf8
8 changed files with 101 additions and 21 deletions
|
|
@ -5,6 +5,9 @@ import ConfirmDialog from "../Shared/ConfirmDialog";
|
|||
import { useToast } from "../Shared/ToastContext";
|
||||
import TagInput from "../Tag/TagInput";
|
||||
import useFetchTags from "../../hooks/useFetchTags";
|
||||
import PriorityDropdown from "../Shared/PriorityDropdown";
|
||||
import { PriorityType } from "../../entities/Task";
|
||||
import Switch from "../Shared/Switch";
|
||||
|
||||
interface ProjectModalProps {
|
||||
isOpen: boolean;
|
||||
|
|
@ -30,12 +33,19 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
area_id: null,
|
||||
active: true,
|
||||
tags: [],
|
||||
priority: "low",
|
||||
}
|
||||
);
|
||||
|
||||
const [tags, setTags] = useState<string[]>(project?.tags?.map(tag => tag.name) || []);
|
||||
const [tags, setTags] = useState<string[]>(
|
||||
project?.tags?.map((tag) => tag.name) || []
|
||||
);
|
||||
|
||||
const { tags: availableTags, isLoading: isTagsLoading, isError: isTagsError } = useFetchTags();
|
||||
const {
|
||||
tags: availableTags,
|
||||
isLoading: isTagsLoading,
|
||||
isError: isTagsError,
|
||||
} = useFetchTags();
|
||||
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
|
|
@ -49,7 +59,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
...project,
|
||||
tags: project.tags || [],
|
||||
});
|
||||
setTags(project.tags?.map(tag => tag.name) || []);
|
||||
setTags(project.tags?.map((tag) => tag.name) || []);
|
||||
} else {
|
||||
setFormData({
|
||||
name: "",
|
||||
|
|
@ -127,7 +137,7 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
}, []);
|
||||
|
||||
const handleSubmit = () => {
|
||||
onSave({ ...formData, tags: tags.map(name => ({ name })) });
|
||||
onSave({ ...formData, tags: tags.map((name) => ({ name })) });
|
||||
showSuccessToast(
|
||||
project
|
||||
? "Project updated successfully!"
|
||||
|
|
@ -154,7 +164,14 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
setTimeout(() => {
|
||||
onClose();
|
||||
setIsClosing(false);
|
||||
}, 300);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleToggleActive = () => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
active: !prev.active,
|
||||
}));
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
|
@ -232,6 +249,18 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
></textarea>
|
||||
</div>
|
||||
|
||||
<div className="pb-3">
|
||||
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-3">
|
||||
Priority
|
||||
</label>
|
||||
<PriorityDropdown
|
||||
value={formData.priority || "medium"}
|
||||
onChange={(value: PriorityType) =>
|
||||
setFormData({ ...formData, priority: value })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Tags */}
|
||||
<div className="pb-3">
|
||||
<label className="block text-xs font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
|
|
@ -269,13 +298,9 @@ const ProjectModal: React.FC<ProjectModalProps> = ({
|
|||
|
||||
{/* Active Checkbox */}
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="active"
|
||||
name="active"
|
||||
checked={formData.active}
|
||||
onChange={handleChange}
|
||||
className="h-5 w-5 appearance-none border border-gray-300 rounded-md bg-white dark:bg-gray-700 checked:bg-blue-600 checked:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
<Switch
|
||||
isChecked={formData.active}
|
||||
onToggle={handleToggleActive}
|
||||
/>
|
||||
<label
|
||||
htmlFor="active"
|
||||
|
|
|
|||
30
app/frontend/components/Shared/Switch.tsx
Normal file
30
app/frontend/components/Shared/Switch.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Switch.tsx
|
||||
import React from 'react';
|
||||
|
||||
interface SwitchProps {
|
||||
isChecked: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
const Switch: React.FC<SwitchProps> = ({ isChecked, onToggle }) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center space-x-2"
|
||||
>
|
||||
<div
|
||||
className={`w-12 h-6 flex items-center rounded-full p-1 cursor-pointer transition-all duration-300 ${
|
||||
isChecked ? 'bg-blue-600' : 'bg-gray-300'
|
||||
}`}
|
||||
onClick={onToggle}
|
||||
>
|
||||
<div
|
||||
className={`bg-white w-4 h-4 rounded-full shadow-md transform transition-transform duration-300 ${
|
||||
isChecked ? 'translate-x-6' : ''
|
||||
}`}
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Switch;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { Area } from "./Area";
|
||||
import { Tag } from "./Tag";
|
||||
import { PriorityType } from "./Task";
|
||||
|
||||
export interface Project {
|
||||
id?: number;
|
||||
|
|
@ -10,4 +11,5 @@ export interface Project {
|
|||
area?: Area;
|
||||
area_id?: number | null;
|
||||
tags?: Tag[];
|
||||
priority?: PriorityType;
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ class Project < ActiveRecord::Base
|
|||
has_many :notes, dependent: :destroy
|
||||
has_and_belongs_to_many :tags
|
||||
|
||||
enum priority: { low: 0, medium: 1, high: 2 }
|
||||
|
||||
scope :with_incomplete_tasks, -> { joins(:tasks).where.not(tasks: { status: Task.statuses[:done] }).distinct }
|
||||
scope :with_complete_tasks, -> { joins(:tasks).where(tasks: { status: Task.statuses[:done] }).distinct }
|
||||
|
||||
|
|
|
|||
|
|
@ -67,12 +67,15 @@ class Sinatra::Application
|
|||
halt 400, { error: 'Invalid JSON format.' }.to_json
|
||||
end
|
||||
|
||||
project_data['priority'] = Project.priorities[project_data['priority']] if project_data['priority'].is_a?(String)
|
||||
|
||||
project = current_user.projects.new(
|
||||
name: project_data['name'],
|
||||
description: project_data['description'] || '',
|
||||
area_id: project_data['area_id'],
|
||||
active: true,
|
||||
pin_to_sidebar: false
|
||||
pin_to_sidebar: false,
|
||||
priority: project_data['priority']
|
||||
)
|
||||
|
||||
if project.save
|
||||
|
|
@ -102,7 +105,8 @@ class Sinatra::Application
|
|||
description: project_data['description'],
|
||||
area_id: project_data['area_id'],
|
||||
active: project_data['active'],
|
||||
pin_to_sidebar: project_data['pin_to_sidebar']
|
||||
pin_to_sidebar: project_data['pin_to_sidebar'],
|
||||
priority: project_data ['priority']
|
||||
)
|
||||
|
||||
if project.save
|
||||
|
|
|
|||
5
db/migrate/20241126095028_add_priority_to_projects.rb
Normal file
5
db/migrate/20241126095028_add_priority_to_projects.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class AddPriorityToProjects < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :projects, :priority, :integer
|
||||
end
|
||||
end
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_11_21_113756) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2024_11_26_095028) do
|
||||
create_table "areas", force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.integer "user_id", null: false
|
||||
|
|
@ -47,6 +47,7 @@ ActiveRecord::Schema[7.1].define(version: 2024_11_21_113756) do
|
|||
t.text "description"
|
||||
t.boolean "active", default: false
|
||||
t.boolean "pin_to_sidebar", default: false
|
||||
t.integer "priority"
|
||||
t.index ["area_id"], name: "index_projects_on_area_id"
|
||||
t.index ["user_id"], name: "index_projects_on_user_id"
|
||||
end
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue