Skip to content

Commit

Permalink
🥭 Create sprint completed
Browse files Browse the repository at this point in the history
  • Loading branch information
HeeManSu committed Jul 11, 2024
1 parent 28e8d88 commit c9c91ea
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 41 deletions.
27 changes: 25 additions & 2 deletions mango-frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions mango-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"react": "^18.3.1",
"react-day-picker": "^8.10.1",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.24.1",
"tailwind-merge": "^2.4.0",
Expand Down
4 changes: 0 additions & 4 deletions mango-frontend/src/components/Create Issue/CreateIssue.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,10 @@ export function CreateIssue({ isOpen, onClose }: CreateIssueProps) {
const resultAction = await dispatch(createIssue(issue));

if (createIssue.fulfilled.match(resultAction)) {
try {
toast({
title: "Issue Created",
description: "A new issue has been successfully created.",
});
} catch (error) {
console.log(error)
}
onClose();
} else {
toast({
Expand Down
84 changes: 59 additions & 25 deletions mango-frontend/src/components/Create Sprint/CreateSprint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,95 @@ import {
} from "@/components/ui/dialog"
import { Textarea } from "@/components/ui/textarea"
import DatePicker from './DatePicker'
import { isBefore, startOfToday } from 'date-fns'
import { useDispatch } from 'react-redux'
import { AppDispatch } from '@/redux/store'
import { createSprintDataType } from '@/types'
import { clearState, createSprint } from '@/redux/slices/sprintSlice'
import toast from 'react-hot-toast'
import useInput from '@/hooks/useInput'

interface CreateSprintProps {
isOpen: boolean;
onClose: () => void;
}

export type InputReturnType = [
string,
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void,
() => void
];

const useInput = (initialValue: string): InputReturnType => {
const [value, setValue] = React.useState(initialValue);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
setValue(e.target.value);
};

const reset = () => setValue(initialValue);
return [value, handleChange, reset] as const;
};

const CreateSprint: React.FC<CreateSprintProps> = ({ isOpen, onClose }) => {

const [title, handleTitleChange, resetTitle] = useInput('');
const [name, handleNameChange, resetName] = useInput('');
const [description, handleDescriptionChange, resetDescription] = useInput('');
const [startDate, setStartDate] = React.useState<Date | null>(null);
const [endDate, setEndDate] = React.useState<Date | null>(null);
const [status, setStatus] = React.useState<'ongoing' | 'upcoming' | 'completed'>('ongoing');
const today = startOfToday();
const dispatch = useDispatch<AppDispatch>();

// const { message, error } = useSelector((state: RootState) => state.sprints);

// console.log("message: ", message);
// console.log("error: ", error)

console.log(title);
console.log(description)
console.log(startDate);
console.log(endDate)
React.useEffect(() => {
if (startDate && isBefore(today, startDate)) {
setStatus('upcoming');
}
}, [startDate, today]);

const calculateDays = (): number | null => {
if (startDate && endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
const diffInTime = end.getTime() - start.getTime();
const diffInDays = Math.ceil(diffInTime / (1000 * 3600 * 24) + 1);
return diffInDays;
}
return null;
};

console.log(calculateDays())

const handleClose = (): void => {
resetTitle();
resetName();
resetDescription();
setStartDate(null)
setEndDate(null)
onClose();
}

const handleSubmit = async (e: React.FormEvent): Promise<void> => {
e.preventDefault();

const sprint: createSprintDataType = {
name,
description,
start_date: startDate,
end_date: endDate,
status
};
const resultAction = await dispatch(createSprint(sprint));
if (createSprint.fulfilled.match(resultAction)) {
toast.success(resultAction?.payload?.message);
dispatch(clearState());
} else if (createSprint.rejected.match(resultAction)) {
toast.error('Failed to create Sprint');
} else {
toast.error('Unexpected error occurred');
}
onClose();
}

return (
<div>
<Dialog open={isOpen} onOpenChange={handleClose}>
<DialogContent className="">
<DialogHeader>
<div className='w-fit flex gap-3 items-center'>
<div className='border-2 cursor-pointer p-[2px] rounded-md text-[15px]'>✨Atlas</div>
<div className='border cursor-pointer hover:bg-blue-50 p-[1.5px] rounded-md text-[14px]'>✨ &nbsp;Atlas</div>
<DialogTitle>Create Cycle</DialogTitle>
</div>
<DialogDescription />
<div className='pt-2 flex flex-col gap-3'>
<Input id='title' type={'text'} placeholder='Title...' value={title} onChange={handleTitleChange} />
<Input id='name' type={'text'} placeholder='Title...' value={name} onChange={handleNameChange} />
<Textarea placeholder='Description' value={description} onChange={handleDescriptionChange} />
</div>
<div className='pt-[10px]'>
Expand All @@ -86,7 +120,7 @@ const CreateSprint: React.FC<CreateSprintProps> = ({ isOpen, onClose }) => {
</Button>
</DialogClose>
<Button type="button" variant="secondary" size={'md'}
onClick={handleClose}
onClick={handleSubmit}
className='bg-blue-500 hover:bg-blue-500 text-white'
>
Create Issue
Expand Down
6 changes: 3 additions & 3 deletions mango-frontend/src/components/Sprints/Sprints.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { Separator } from "@/components/ui/separator"
import { useDispatch, useSelector } from 'react-redux'
import { AppDispatch, RootState } from '@/redux/store'
import { fetchSprints } from '@/redux/slices/sprintSlice'
import { clearError, clearMessage, fetchSprints } from '@/redux/slices/sprintSlice'
import { UseDialog } from '@/hooks/useDialog'
import CreateSprint from '../Create Sprint/CreateSprint'

Expand All @@ -19,14 +19,14 @@ const Sprints: React.FC = () => {
if (!isMounted.current) {
const fetchSprintsAsync = async () => {
await dispatch(fetchSprints());
clearMessage();
clearError()
}
fetchSprintsAsync();
isMounted.current = true;
}
}, [dispatch]);

console.log(sprints)

return (
<>
<div className='w-full'>
Expand Down
4 changes: 2 additions & 2 deletions mango-frontend/src/components/ui/toaster.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
ToastProvider,
ToastTitle,
ToastViewport,
} from "src/components/ui/toast"
import { useToast } from "src/components/ui/use-toast"
} from "@/components/ui/toast"
import { useToast } from "@/components/ui/use-toast"

export function Toaster() {
const { toasts } = useToast()
Expand Down
2 changes: 1 addition & 1 deletion mango-frontend/src/components/ui/use-toast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "src/components/ui/toast"
} from "@/components/ui/toast"

const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
Expand Down
20 changes: 20 additions & 0 deletions mango-frontend/src/hooks/useInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// src/hooks/useInput.ts
import React from 'react';

export type InputReturnType = [
string,
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void,
() => void
];

const useInput = (initialValue: string): InputReturnType => {
const [value, setValue] = React.useState(initialValue);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>): void => {
setValue(e.target.value);
};

const reset = () => setValue(initialValue);
return [value, handleChange, reset] as const;
};

export default useInput;
2 changes: 2 additions & 0 deletions mango-frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import App from './App.tsx'
import './index.css'
import { Provider } from 'react-redux';
import { store } from './redux/store.ts';
import { Toaster } from 'react-hot-toast';
// document.documentElement.classList.add('dark');

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}>
<App />
<Toaster />
</Provider>

</React.StrictMode>,
Expand Down
38 changes: 34 additions & 4 deletions mango-frontend/src/redux/slices/sprintSlice.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FetchSprintsPayload, SprintState } from "@/types";
import { createSprintDataType, createSprintPayloadType, FetchSprintsPayload, SprintState } from "@/types";
import { createAsyncThunk, createSlice, PayloadAction } from "@reduxjs/toolkit";
import axios from "axios";
const server = "http://localhost:8082/api/v1";
Expand All @@ -7,7 +7,8 @@ const initialState: SprintState = {
sprints: [],
status: 'idle',
error: null,
message: null
message: null,
sprint: null,
};

export const fetchSprints = createAsyncThunk('fetchSprints', async () => {
Expand All @@ -21,6 +22,19 @@ export const fetchSprints = createAsyncThunk('fetchSprints', async () => {
}
});

export const createSprint = createAsyncThunk<createSprintPayloadType, createSprintDataType>('createSprint', async (sprintData) => {
try {
const { data } = await axios.post(`${server}/sprints/create`, sprintData, {
headers: {
'Content-Type': 'application/json'
}
});
return data;
} catch (error) {
throw new Error('unable to create new sprint')
}
})

const sprintsSlice = createSlice({
name: 'sprints',
initialState,
Expand All @@ -31,6 +45,9 @@ const sprintsSlice = createSlice({
clearError(state) {
state.error = null;
},
clearState() {
return initialState;
}
},
extraReducers: (builder) => {
builder
Expand All @@ -45,10 +62,23 @@ const sprintsSlice = createSlice({
.addCase(fetchSprints.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message || 'Failed to fetch sprints';
});
})
.addCase(createSprint.pending, (state) => {
state.status = 'loading';
})
.addCase(createSprint.fulfilled, (state, action: PayloadAction<createSprintPayloadType>) => {
state.status = 'succeeded';
state.sprint = action.payload.sprint;
state.sprints.unshift(action.payload.sprint)
state.message = action.payload.message;
})
.addCase(createSprint.rejected, (state) => {
state.status = 'failed';
state.error = 'Failed to create issue';
})
},
});

export const { clearMessage, clearError } = sprintsSlice.actions;
export const { clearMessage, clearError, clearState } = sprintsSlice.actions;

export default sprintsSlice.reducer;
15 changes: 15 additions & 0 deletions mango-frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ export interface Sprint {
organizationId: number;
}

export interface createSprintPayloadType {
success: boolean;
message: string;
sprint: Sprint;
}

export interface createSprintDataType {
name: string;
description?: string;
start_date: Date | null;
end_date: Date | null;
status: 'ongoing' | 'upcoming' | 'completed';
}

export interface FetchSprintsPayload {
sprints: Sprint[];
message: string;
Expand All @@ -18,6 +32,7 @@ export interface SprintState {
status: 'idle' | 'loading' | 'succeeded' | 'failed';
error: string | null;
message: string | null;
sprint: Sprint | null;
}

export interface Issue {
Expand Down

0 comments on commit c9c91ea

Please sign in to comment.