Clockify tasks support added

This commit is contained in:
2022-05-23 19:17:52 -03:00
parent 8651b061c6
commit 324e4bdee5
10 changed files with 245 additions and 142 deletions

View File

@@ -3,9 +3,13 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@fortawesome/react-fontawesome": "^0.1.18",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"@uiball/loaders": "^1.2.6",
"firebase": "^9.1.0",
"react": "^17.0.2",
"react-countdown": "^2.3.2",

View File

@@ -3,8 +3,7 @@
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Raleway:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Rambla:wght@400;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;700&family=Raleway:wght@400;700&family=Rambla:wght@400;700&display=swap" rel="stylesheet">
<meta charset="utf-8" />
<link rel="icon" href="./img/favicon.ico" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

View File

@@ -1,27 +1,28 @@
import './clockify-task-form.css'
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { getAuth, onAuthStateChanged } from "firebase/auth";
import { doc, getDoc, getFirestore } from "firebase/firestore";
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { firebase } from '../../../Firebase/firebase';
import Loading from "../../../components/Loading/Loading";
const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, taskName, setTaskName, workspaceID, setWorkspaceID, projectID, setProjectID, darkMode}) => {
import {faCheck, faPlus} from '@fortawesome/free-solid-svg-icons'
import { Ring } from '@uiball/loaders'
const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, taskName, setTaskName, projectID, setProjectID, darkMode, taskID, setTaskID, clockifyData, changeClockifyData}) => {
const auth = getAuth()
const [userUID, setUserUID] = useState('')
const [workspaces, setWorkspaces] = useState([])
const [workspacesReady, setWorkspacesReady] = useState(false)
const [projects, setProjects] = useState([])
const [projectsDone, setProjectsDone] = useState(false)
const descriptionInput = useRef("")
const [loading, setLoading] = useState(true)
async function getApiKey() {
let newTask = useRef("")
const [addingNewTask, setAddingNewTask] = useState(false)
async function getApiKey(userUID) {
try {
@@ -39,25 +40,22 @@ const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, tas
} catch (error) {
console.log(error);
}
setLoading(false)
}
React.useEffect(() => {
if (signedIn) {
onAuthStateChanged(auth, async (user) => {
if (user) {
if (user && user.uid) {
setUserUID(user.uid)
if (user.uid) {
await getApiKey()
setLoading(false)
}
await getApiKey(user.uid)
setLoading(false)
} else {
return (<></>)
}
@@ -67,7 +65,7 @@ const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, tas
setLoading(false)
}
}, [signedIn, getApiKey])
}, [signedIn])
async function makeRequestWorkspaces(apiClockify) {
try {
@@ -86,7 +84,7 @@ const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, tas
return await data
} catch (error) {
console.log(error);
}
}
@@ -95,20 +93,17 @@ const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, tas
const data = await makeRequestWorkspaces(key)
if (data.code !== 1000) {
let workspacesCopy = []
let workspaces = []
await data.forEach(workspace => {
workspacesCopy.push(workspace)
workspaces.push(workspace)
});
setWorkspaces(workspacesCopy)
setWorkspacesReady(true)
setLoading(false)
changeClockifyData({workspaces: workspaces})
}
}
async function requestProjects(e) {
const getProjects = async (e) => {
try {
const request = {
@@ -119,32 +114,70 @@ const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, tas
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/${e}/projects`, request)
const data = response.json()
const data = await response.json()
console.log(data);
changeClockifyData({projects: data})
return data
} catch (error) {
console.log(error);
}
}
const defineProjects = async (e) => {
if (e === 0) {
setProjectsDone(false)
setProjects([])
async function getTasks(projectID) {
if (projectID === "0") {
changeClockifyData({projectID: undefined})
changeClockifyData({tasks: undefined})
return
}
setWorkspaceID(e)
const data = await requestProjects(e)
setProjects(data)
setProjectsDone(true)
try {
const request = {
method: "GET",
headers: {
'X-Api-Key': apiKey,
"content-type": "application/json"
}
}
const response = await fetch(`https://api.clockify.me/api/v1/workspaces/${clockifyData.workspaceID}/projects/${projectID}/tasks`, request)
const data = await response.json()
changeClockifyData({tasks: data})
} catch (error) {
console.log(error);
}
}
const selectProject = (e) => {
async function addNewTask() {
setProjectID(e)
setAddingNewTask("loading")
try {
const url = `https://api.clockify.me/api/v1/workspaces/${clockifyData.workspaceID}/projects/${clockifyData.projectID}/tasks`
const request = {
method: "POST",
body: JSON.stringify({
name: newTask.current.value
}),
headers: {
'X-Api-Key': apiKey,
"content-type": "application/json",
}
}
await fetch(url, request)
newTask.current.value = ""
await getTasks(clockifyData.projectID)
} catch (error) {
console.log(error);
}
setAddingNewTask(false)
}
return (
@@ -155,41 +188,107 @@ const ClockifyTaskForm = ({timerOn, setTimerOn, signedIn, apiKey, setApiKey, tas
:
<div className={darkMode ? 'clockify-task-form-container dark-mode-container' : 'clockify-tasks-display-container'}>
{
userUID ?
<div className={`clockify-task-form ${timerOn || !workspacesReady ? "disabled" : ""}`}>
<select onChange={(e) => {defineProjects(e.target.value)}} className='workspace-selector'>
clockifyData.workspaces ?
<div className={`clockify-task-form ${(timerOn || !clockifyData.workspaces) && "disabled"}`}>
<select
onChange={(e) => {
changeClockifyData({workspaceID: e.target.value})
getProjects(e.target.value)
}}
className='workspace-selector'
>
<option value="0">Select a Workspace</option>
{
workspacesReady ?
workspaces.map( (workspace) => {
clockifyData.workspaces &&
clockifyData.workspaces.map((workspace) => {
return <option value={workspace.id} key={workspace.id}>{workspace.name}</option>
})
: null
}
</select>
<select onChange={(e) => {selectProject(e.target.value)}} className={workspaceID !== 0 ? 'project-selector' : 'project-selector disabled'}>
<select
onChange={(e) => {
changeClockifyData({projectID: e.target.value})
getTasks(e.target.value)
}}
className={`project-selector ${(!clockifyData.workspaceID) && 'disabled'}`}
>
<option value="0">Select a Project</option>
{
projectsDone && projects.length !== 0 ?
projects.map((project) => (
clockifyData.projects &&
clockifyData.projects.map((project) => (
!project.archived ?
<option value={project.id} key={project.id}>{project.name}</option>
<option value={project.id} key={project.id} style={{color: project.color}}>{project.name}</option>
: null
))
: null
}
</select>
<select
onChange={(e) => {
if (e.target.value === "0") {
changeClockifyData({taskID: undefined})
} else {
changeClockifyData({taskID: e.target.value})
}
}}
className={`project-selector ${(!clockifyData.projectID || (clockifyData.tasks && clockifyData.tasks.length === 0)) && 'disabled'}`}
>
<option value="0">Select a Task</option>
{
clockifyData.tasks &&
clockifyData.tasks.map((task) => (
task.status !== "DONE" &&
<option value={task.id} key={task.id} >{task.name}</option>
))
}
</select>
<button
className={`add-task ${!clockifyData.projectID && 'disabled'}`}
onClick={() => {
if (addingNewTask === "loading") {
return
}
if (!addingNewTask) {
setAddingNewTask(true)
return
} else {
addNewTask()
}
}}
>
{
addingNewTask === false ?
<FontAwesomeIcon icon={faPlus} />
: addingNewTask === true ?
<FontAwesomeIcon icon={faCheck} />
: addingNewTask === "loading" &&
<Ring size={20} color="#fff" />
}
</button>
<>
{
addingNewTask &&
<input
type="text"
ref={newTask}
placeholder="Set new task name"
/>
}
</>
<input
type="text"
onChange={(e) => {setTaskName(e.target.value)}}
value={taskName}
ref={descriptionInput}
onChange={(e) => changeClockifyData({description: e.target.value})}
placeholder="Add task description"
className={projectID !== 0 ? null: 'disabled'}
className={!clockifyData.projectID && 'disabled'}
onKeyPress={event => {
if (event.key === 'Enter') {
setTimerOn(true)
}
}}

View File

@@ -4,32 +4,39 @@
top: 60vh;
left: 2.5vw;
}
.clockify-task-form input::-moz-placeholder {
font-family: "Arial", sans-serif;
}
.clockify-task-form input:-ms-input-placeholder {
font-family: "Arial", sans-serif;
}
.clockify-task-form select, .clockify-task-form select option, .clockify-task-form input, .clockify-task-form input::placeholder {
font-family: "Arial", sans-serif;
}
.clockify-task-form.loading-container {
width: 20vw;
height: auto;
background: none;
}
.clockify-task-form.disabled {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.clockify-task-form .workspace-selector, .clockify-task-form .project-selector {
width: 15vw;
height: 3vw;
height: 5vh;
}
.clockify-task-form .workspace-selector.disabled, .clockify-task-form .project-selector.disabled {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
.clockify-task-form .add-task {
width: 3.5vh;
height: 3.5vh;
margin-left: 0.5vh;
display: inline;
background-color: var(--main-text-color);
border-radius: 100%;
color: #fff;
cursor: pointer;
}
.clockify-task-form .add-task:hover {
filter: brightness(90%);
}
.clockify-task-form .add-task:active {
filter: brightness(110%);
}
.clockify-task-form select {
overflow: hidden;
@@ -44,14 +51,14 @@
padding: 1px 2px;
box-sizing: border-box;
}
.clockify-task-form input[type=text].disabled {
.clockify-task-form .disabled {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
opacity: 0.3;
}
@media (max-width: 991.98px) {

View File

@@ -1 +1 @@
{"version":3,"sources":["clockify-task-form.scss","clockify-task-form.css"],"names":[],"mappings":"AAAA;EAEI,WAAA;EACA,kBAAA;EAEA,SAAA;EACA,WAAA;ACDJ;ADGI;EACI,WAAA;EACA,YAAA;EACA,gBAAA;ACDR;ADII;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACFR;ADKI;EACI,WAAA;EACA,WAAA;ACHR;ADKQ;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACHZ;ADOI;EACI,gBAAA;EACA,mBAAA;EACA,uBAAA;ACLR;ADQI;EACI,WAAA;EACA,WAAA;EAEA,eAAA;EAEA,aAAA;EACA,gBAAA;EAEA,sBAAA;ACTR;ADWQ;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACTZ;;ADcA;EAEI;IACI,iBAAA;IACA,WAAA;IAEA,aAAA;IACA,6BAAA;IAEA,gBAAA;ECdN;EDgBM;IACI,UAAA;IACA,YAAA;ECdV;EDiBM;IACI,WAAA;IACA,WAAA;ECfV;AACF;ADoBA;EAEI;IACI,iBAAA;IACA,WAAA;IAEA,aAAA;IACA,sBAAA;IACA,mBAAA;ECpBN;EDsBM;IACI,UAAA;IACA,YAAA;ECpBV;EDuBM;IACI,UAAA;IACA,WAAA;IACA,eAAA;ECrBV;AACF","file":"clockify-task-form.css"}
{"version":3,"sources":["clockify-task-form.scss","clockify-task-form.css"],"names":[],"mappings":"AAAA;EAEI,WAAA;EACA,kBAAA;EAEA,SAAA;EACA,WAAA;ACDJ;ADGI;EACI,gCAAA;ACDR;ADAI;EACI,gCAAA;ACDR;ADAI;EACI,gCAAA;ACDR;ADII;EACI,WAAA;EACA,YAAA;EACA,gBAAA;ACFR;ADKI;EACI,WAAA;EACA,WAAA;ACHR;ADMI;EACI,YAAA;EACA,aAAA;EAEA,kBAAA;EAEA,eAAA;EAEA,wCAAA;EACA,mBAAA;EAEA,WAAA;EAEA,eAAA;ACTR;ADWQ;EACI,uBAAA;ACTZ;ADYQ;EACI,wBAAA;ACVZ;ADcI;EACI,gBAAA;EACA,mBAAA;EACA,uBAAA;ACZR;ADeI;EACI,WAAA;EACA,WAAA;EAEA,eAAA;EAEA,aAAA;EACA,gBAAA;EAEA,sBAAA;AChBR;ADmBI;EACI,yBAAA;KAAA,sBAAA;MAAA,qBAAA;UAAA,iBAAA;EACA,eAAA;EACA,oBAAA;EACA,YAAA;ACjBR;;ADqBA;EAEI;IACI,iBAAA;IACA,WAAA;IAEA,aAAA;IACA,6BAAA;IAEA,gBAAA;ECrBN;EDuBM;IACI,UAAA;IACA,YAAA;ECrBV;EDwBM;IACI,WAAA;IACA,WAAA;ECtBV;AACF;AD2BA;EAEI;IACI,iBAAA;IACA,WAAA;IAEA,aAAA;IACA,sBAAA;IACA,mBAAA;EC3BN;ED6BM;IACI,UAAA;IACA,YAAA;EC3BV;ED8BM;IACI,UAAA;IACA,WAAA;IACA,eAAA;EC5BV;AACF","file":"clockify-task-form.css"}

View File

@@ -6,28 +6,42 @@
top: 60vh;
left: 2.5vw;
select, select option, input, input::placeholder {
font-family: 'Arial', sans-serif;
}
&.loading-container {
width: 20vw;
height: auto;
background: none;
}
&.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.workspace-selector, .project-selector {
width: 15vw;
height: 3vw;
height: 5vh;
}
&.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
.add-task {
width: 3.5vh;
height: 3.5vh;
margin-left: 0.5vh;
display: inline;
background-color: var(--main-text-color);
border-radius: 100%;
color: #fff;
cursor: pointer;
&:hover {
filter: brightness(90%);
}
&:active {
filter: brightness(110%);
}
}
@@ -47,13 +61,13 @@
padding: 1px 2px;
box-sizing: border-box;
}
&.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 30%;
}
.disabled {
user-select: none;
cursor: initial;
pointer-events: none;
opacity: 0.3;
}
}

View File

@@ -7,10 +7,15 @@ const Main = ({signedIn, darkMode, setKonamiCodeActive, KonamiCodeActive, notifi
const [timerOn, setTimerOn] = useState(false)
const [apiKey, setApiKey] = useState('')
const [taskName, setTaskName] = useState('')
const [workspaceID, setWorkspaceID] = useState(0)
const [projectID, setProjectID] = useState(0)
const [clockifyData, setClockifyData] = useState({})
function changeClockifyData(obj) {
setClockifyData(clockifyData => ({
...clockifyData,
...obj
}))
}
return (
<>
@@ -24,16 +29,8 @@ const Main = ({signedIn, darkMode, setKonamiCodeActive, KonamiCodeActive, notifi
apiKey={apiKey}
setApiKey={setApiKey}
taskName={taskName}
setTaskName={setTaskName}
workspaceID={workspaceID}
setWorkspaceID={setWorkspaceID}
projectID={projectID}
setProjectID={setProjectID}
darkMode={darkMode}
clockifyData={clockifyData}
changeClockifyData={changeClockifyData}
/>
<Pomodoro
signedIn={signedIn}
@@ -42,16 +39,7 @@ const Main = ({signedIn, darkMode, setKonamiCodeActive, KonamiCodeActive, notifi
apiKey={apiKey}
taskName={taskName}
setTaskName={setTaskName}
workspaceID={workspaceID}
setWorkspaceID={setWorkspaceID}
projectID={projectID}
setProjectID={setProjectID}
darkMode={darkMode}
clockifyData={clockifyData}
setKonamiCodeActive = {setKonamiCodeActive}
KonamiCodeActive= {KonamiCodeActive}

View File

@@ -27,18 +27,19 @@ const Pomodoro = (props) => {
async function uploadToClockifyTimer() {
if (!props.workspaceID && !props.projectID) {
if (!props.clockifyData.workspaceID && !props.clockifyData.projectID) {
return
}
try {
const url = `https://api.clockify.me/api/v1/workspaces/${props.workspaceID}/time-entries`
const url = `https://api.clockify.me/api/v1/workspaces/${props.clockifyData.workspaceID}/time-entries`
const body = {
start: startTime,
end: endTime,
projectId: props.projectID,
description: props.taskName
description: props.clockifyData.description,
projectId: props.clockifyData.projectID,
taskId: props.clockifyData.taskID ? props.clockifyData.taskID : "",
}
const headers = {
@@ -55,6 +56,7 @@ const Pomodoro = (props) => {
await fetch(url, request)
} catch (error) {
console.log(error);
}
}
@@ -68,7 +70,7 @@ const Pomodoro = (props) => {
})
return (
<div className={props.darkMode ? 'main-pomodoro-container dark-mode-component' : 'main-pomodoro-container'}>
<div className='main-pomodoro-container' >
<div className="main-pomodoro">
<PomodoroTimer
@@ -79,14 +81,6 @@ const Pomodoro = (props) => {
stats={stats}
setStats={setStats}
workspaceID={props.workspaceID}
setWorkspaceID={props.setWorkspaceID}
projectID={props.projectID}
setProjectID={props.setProjectID}
apiKey={props.apiKey}
startTime={startTime}
setStartTime={setStartTime}

View File

@@ -5,7 +5,7 @@
position: absolute;
top: 29vh;
left: 75vw;
box-shadow: 1px 6px 15px #00000020;
box-shadow: 1px 6px 15px rgba(0, 0, 0, 0.1254901961);
z-index: 50;
transition: 0.2s ease-in-out;
}
@@ -109,6 +109,4 @@
.style-selector .style-selection-container .style-container {
width: 100%;
}
}
/*# sourceMappingURL=style-selector.css.map */
}/*# sourceMappingURL=style-selector.css.map */

View File

@@ -1 +1 @@
{"version":3,"sourceRoot":"","sources":["style-selector.scss"],"names":[],"mappings":"AAAA;EAEI;EAEA;EACA;EAEA;EAEA;EACA;EAEA;EAEA;EAyEA;;AAvEA;EACI;EACA;EACA;EAEA;EACA;EACA;;AAIA;EACI;EACA;EAEA;;AAEA;EACI;EACA;EACA;EAEA;;AAEA;EACI;;AAGJ;EACI;EACA;;AAKZ;EACI;EACA;;AAGJ;EACI;EACA;;AAEA;EACI;;AAKR;EACI;EAEA;EACA;EAEA;EACA;EACA;EAEA;;AAGJ;EAEI;;;AASZ;EACI;EACA;;;AAGJ;EACI;EACA;;;AAGJ;EACI;IACI;IAEA;IAEA;IAEA;IACA;IAEA;;EAEA;IACI;IAEA;;EAIJ;IAEI;IAEA;IAEA;;EAEA;IACI;IACA;IAEA;;;AAMhB;EAEI;IAWI;;EATA;IACI;IAEA;;EAEA;IACI;;;AAQhB;EAMY;IACI","file":"style-selector.css"}
{"version":3,"sources":["style-selector.scss","style-selector.css"],"names":[],"mappings":"AAAA;EAEI,uBAAA;EAEA,iBAAA;EACA,oBAAA;EAEA,kBAAA;EAEA,SAAA;EACA,UAAA;EAEA,oDAAA;EAEA,WAAA;EAyEA,4BAAA;AC7EJ;ADMI;EACI,6BAAA;EACA,eAAA;EACA,kBAAA;EAEA,iBAAA;EACA,eAAA;EACA,kBAAA;ACLR;ADSQ;EACI,aAAA;EACA,YAAA;EAEA,mBAAA;ACRZ;ADUY;EACI,aAAA;EACA,sBAAA;EACA,uBAAA;EAEA,+BAAA;ACThB;ADWgB;EACI,eAAA;ACTpB;ADYgB;EACI,8BAAA;EACA,eAAA;ACVpB;ADeQ;EACI,WAAA;EACA,UAAA;ACbZ;ADgBQ;EACI,aAAA;EACA,+BAAA;ACdZ;ADgBY;EACI,mBAAA;ACdhB;ADmBQ;EACI,oBAAA;EAEA,WAAA;EACA,YAAA;EAEA,wCAAA;EACA,qBAAA;EACA,mBAAA;EAEA,4BAAA;ACpBZ;ADuBQ;EAEI,uCAAA;ACtBZ;;AD+BA;EACI,UAAA;EACA,oBAAA;AC5BJ;;AD+BA;EACI,UAAA;EACA,oBAAA;AC5BJ;;AD+BA;EACI;IACI,iBAAA;IAEA,gBAAA;IAEA,WAAA;IAEA,UAAA;IACA,gBAAA;IAEA,sBAAA;EChCN;EDkCM;IACI,iBAAA;IAEA,cAAA;ECjCV;EDqCM;IAEI,aAAA;IAEA,eAAA;IAEA,WAAA;ECtCV;EDwCU;IACI,YAAA;IACA,UAAA;IAEA,cAAA;ECvCd;AACF;AD4CA;EAEI;IAWI,kBAAA;ECrDN;ED4CM;IACI,iBAAA;IAEA,sBAAA;EC3CV;ED6CU;IACI,UAAA;EC3Cd;AACF;ADkDA;EAMY;IACI,WAAA;ECrDd;AACF","file":"style-selector.css"}