Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

allow multiple subtasks within projects in view #211

Merged
merged 1 commit into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/src/components/Modal Management/Layout/View.js
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ const View = ({
showDetails ? 'max-h-[500px] py-3' : 'max-h-0'
} overflow-hidden`}
>
<div className="px-4 space-y-1.5 text-sm text-gray-600 dark:text-gray-300">
<div className="px-4 space-y-1.5 text-sm text-gray-600 dark:text-gray-300 max-h-[450px] overflow-y-auto">
{!isCompleted && (
<div className="flex items-center justify-between border-b border-gray-200 dark:border-gray-700 pb-2 mb-2">
<button
Expand Down
198 changes: 196 additions & 2 deletions client/src/components/Modal Management/Projects/ProjectView.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import React, { useState, useEffect } from 'react';
import { formatDeadline, isOverdue, calculateOverduePenalty } from '../../../utils/tasks/tasksUtils';
import ShareCode from './ShareCode';
import { Users } from 'lucide-react';
import { Users, Plus, X, Dumbbell, Target } from 'lucide-react';

const ProjectView = ({ task, isCompleted, updateTask, collaborationManager, userId }) => {
const [collaborators, setCollaborators] = useState([]);
const [showAddSubtask, setShowAddSubtask] = useState(false);
const [newSubtask, setNewSubtask] = useState({
name: '',
difficulty: 50,
importance: 50,
completed: false
});
const [error, setError] = useState('');

useEffect(() => {
if (task.isShared && task.sharedWith) {
Expand Down Expand Up @@ -49,6 +57,189 @@ const ProjectView = ({ task, isCompleted, updateTask, collaborationManager, user
});
};

const handleAddSubtask = async () => {
if (!newSubtask.name.trim()) {
setError('Task name is required');
return;
}

const updatedTask = {
...task,
subtasks: [...task.subtasks, newSubtask],
experience: task.experience + calculateSubtaskXP(newSubtask)
};

try {
if (task.isShared) {
// Sync with server first
await collaborationManager.updateSharedProjectDetails(task.id, updatedTask);
}

// Update local state after successful sync (or if not shared)
updateTask(task.id, updatedTask);
setNewSubtask({ name: '', difficulty: 50, importance: 50, completed: false });
setShowAddSubtask(false);
setError('');
} catch (error) {
console.error('[ERROR] Failed to add subtask:', error);
setError('Failed to add subtask. Please try again.');
}
};

const calculateSubtaskXP = (subtask) => {
return ((parseInt(subtask.difficulty) + parseInt(subtask.importance) + 20) * 5 +
parseInt((parseInt(subtask.difficulty) * parseInt(subtask.importance)) / 20));
};

const renderAddSubtaskInterface = () => {
if (!showAddSubtask) {
return (
<button
onClick={() => setShowAddSubtask(true)}
className="w-full py-2 flex items-center justify-center gap-2
text-sm text-gray-500 dark:text-gray-400
hover:bg-gray-50 dark:hover:bg-gray-700/50
rounded-lg transition-colors"
>
<Plus className="w-4 h-4" />
<span>Add Subtask</span>
</button>
);
}

return (
<div className="p-3">
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-3">
{/* Task Name Input */}
<div className="flex-1 w-full sm:w-auto">
<input
type="text"
placeholder="New subtask name"
value={newSubtask.name}
onChange={(e) => {
setNewSubtask({ ...newSubtask, name: e.target.value });
setError('');
}}
className="w-full px-3 py-1.5 bg-white dark:bg-gray-700
border border-gray-300 dark:border-gray-600
rounded-lg text-sm text-gray-900 dark:text-gray-200"
/>
{error && (
<p className="absolute text-xs text-red-600 dark:text-red-400 mt-1">
{error}
</p>
)}
</div>

{/* Mobile: Single Combined Slider */}
<div className="sm:hidden w-full bg-gray-50 dark:bg-gray-800/50 p-2 rounded-lg">
<div className="flex items-center gap-2">
<Dumbbell className="w-3.5 h-3.5 text-gray-400 dark:text-gray-500" />
<input
type="range"
min="0"
max="100"
value={(parseInt(newSubtask.difficulty) + parseInt(newSubtask.importance)) / 2}
onChange={(e) => {
const value = Number(e.target.value);
setNewSubtask({
...newSubtask,
difficulty: value,
importance: value
});
}}
className="flex-1 h-1 appearance-none bg-gray-200 dark:bg-gray-700 rounded-full
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5
[&::-webkit-slider-thumb]:h-2.5 [&::-webkit-slider-thumb]:rounded-full
[&::-webkit-slider-thumb]:bg-blue-500 dark:[&::-webkit-slider-thumb]:bg-blue-400"
/>
<span className="text-sm font-medium text-blue-600 dark:text-blue-400 min-w-[32px] text-right">
{calculateSubtaskXP(newSubtask)}xp
</span>
</div>
</div>

{/* Desktop: Separate Sliders */}
<div className="hidden sm:flex items-center gap-4 bg-gray-50 dark:bg-gray-800/50 px-3 rounded-lg w-[300px] h-[34px]">
<div className="flex items-center gap-2">
<Dumbbell className="w-3.5 h-3.5 text-gray-400 dark:text-gray-500" />
<input
type="range"
min="0"
max="100"
value={newSubtask.difficulty}
onChange={(e) => setNewSubtask({ ...newSubtask, difficulty: Number(e.target.value) })}
className="w-16 h-1 appearance-none bg-gray-200 dark:bg-gray-700 rounded-full
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5
[&::-webkit-slider-thumb]:h-2.5 [&::-webkit-slider-thumb]:rounded-full
[&::-webkit-slider-thumb]:bg-blue-500 dark:[&::-webkit-slider-thumb]:bg-blue-400"
/>
<span className="text-[10px] font-medium text-gray-500 dark:text-gray-400 w-4 text-right">
{Math.round(newSubtask.difficulty/10)}
</span>
</div>

<div className="flex items-center gap-2">
<Target className="w-3.5 h-3.5 text-gray-400 dark:text-gray-500" />
<input
type="range"
min="0"
max="100"
value={newSubtask.importance}
onChange={(e) => setNewSubtask({ ...newSubtask, importance: Number(e.target.value) })}
className="w-16 h-1 appearance-none bg-gray-200 dark:bg-gray-700 rounded-full
[&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5
[&::-webkit-slider-thumb]:h-2.5 [&::-webkit-slider-thumb]:rounded-full
[&::-webkit-slider-thumb]:bg-blue-500 dark:[&::-webkit-slider-thumb]:bg-blue-400"
/>
<span className="text-[10px] font-medium text-gray-500 dark:text-gray-400 w-4 text-right">
{Math.round(newSubtask.importance/10)}
</span>
</div>

<div className="ml-1 flex items-center">
<span className="text-sm font-medium text-blue-600 dark:text-blue-400">
{calculateSubtaskXP(newSubtask)}
</span>
</div>
</div>

{/* Action Buttons */}
<div className="flex gap-2 w-full sm:w-auto justify-end">
<button
onClick={handleAddSubtask}
className="flex-1 sm:flex-initial p-1.5 rounded-lg bg-blue-500/10 hover:bg-blue-500/20
text-blue-600 dark:text-blue-400 transition-colors flex items-center justify-center"
>
<Plus className="w-4 h-4" />
</button>
<button
onClick={() => {
setShowAddSubtask(false);
setError('');
}}
className="flex-1 sm:flex-initial p-1.5 rounded-lg bg-red-500/10 hover:bg-red-500/20
text-red-600 dark:text-red-400 transition-colors flex items-center justify-center"
>
<X className="w-4 h-4" />
</button>
</div>
</div>
</div>
);
};

useEffect(() => {
let cleanup;
if (task.isShared) {
// Start syncing the project
cleanup = collaborationManager.startProjectSync(task.id);
}
return () => {
if (cleanup) cleanup();
};
}, [task.id, task.isShared, collaborationManager]);

return (
<div className="space-y-4">
{/* Progress Bar and Share Button */}
Expand Down Expand Up @@ -108,7 +299,7 @@ const ProjectView = ({ task, isCompleted, updateTask, collaborationManager, user
</div>
)}

{/* Subtasks List */}
{/* Subtasks List with Add Button */}
<div className="border dark:border-gray-700 rounded-lg overflow-hidden">
<div className="divide-y divide-gray-200 dark:divide-gray-700">
{task.subtasks.map((subtask, index) => (
Expand Down Expand Up @@ -149,6 +340,9 @@ const ProjectView = ({ task, isCompleted, updateTask, collaborationManager, user
</div>
</div>
))}

{/* Subtask Interface */}
{!isCompleted && renderAddSubtaskInterface()}
</div>
</div>

Expand Down
Loading