diff --git a/Makefile b/Makefile index 890d7a9..7920b20 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,11 @@ DOKKU_URL := dokku@dev.lookingfora.name:guesstimate +watch: + npm run dev + +build: + npm run build + dokku-build: docker build \ -t guesstimate-dokku:latest \ @@ -10,4 +16,4 @@ dokku-run: dokku-deploy: $(if $(shell git config remote.dokku.url),, git remote add dokku $(DOKKU_URL)) - git push -f dokku $(shell git rev-parse HEAD):master \ No newline at end of file + git push -f dokku $(shell git rev-parse HEAD):refs/heads/master \ No newline at end of file diff --git a/src/components/app.tsx b/src/components/app.tsx index a17d527..e09a41e 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -23,7 +23,7 @@ const App: FunctionalComponent = () => {
- + diff --git a/src/hooks/use-local-storage.ts b/src/hooks/use-local-storage.ts new file mode 100644 index 0000000..10d86ba --- /dev/null +++ b/src/hooks/use-local-storage.ts @@ -0,0 +1,37 @@ +import { useState } from "preact/hooks"; + +export function useLocalStorage(key: string, initialValue: T) { + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue] = useState(() => { + try { + // Get from local storage by key + const item = window.localStorage.getItem(key); + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue; + } catch (error) { + // If error also return initialValue + console.error(error); + return initialValue; + } + }); + + // Return a wrapped version of useState's setter function that ... + // ... persists the new value to localStorage. + const setValue = (value: T) => { + try { + // Allow value to be a function so we have same API as useState + const valueToStore = + value instanceof Function ? value(storedValue) : value; + // Save state + setStoredValue(valueToStore); + // Save to local storage + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + // A more advanced implementation would handle the error case + console.error(error); + } + }; + + return [storedValue, setValue]; + } \ No newline at end of file diff --git a/src/hooks/project-reducer.ts b/src/hooks/use-project-reducer.ts similarity index 94% rename from src/hooks/project-reducer.ts rename to src/hooks/use-project-reducer.ts index f56da82..ac5a841 100644 --- a/src/hooks/project-reducer.ts +++ b/src/hooks/use-project-reducer.ts @@ -1,5 +1,6 @@ import { Project } from "../models/project"; import { Task, TaskID, EstimationConfidence } from "../models/task"; +import { useReducer } from "preact/hooks"; export interface Action { type: string @@ -10,6 +11,10 @@ export type ProjectReducerActions = RemoveTaskAction | UpdateTaskEstimation +export function useProjectReducer(project: Project) { + return useReducer(projectReducer, project); +} + export function projectReducer(project: Project, action: ProjectReducerActions): Project { switch(action.type) { case ADD_TASK: diff --git a/src/hooks/use-stored-project-list.ts b/src/hooks/use-stored-project-list.ts new file mode 100644 index 0000000..42a5531 --- /dev/null +++ b/src/hooks/use-stored-project-list.ts @@ -0,0 +1,35 @@ +import {Project} from "../models/project"; +import { useState } from "preact/hooks"; +import { ProjectStorageKeyPrefix } from "../util/storage"; + +export function loadStoredProjects(): Project[] { + const projects: Project[] = []; + + Object.keys(window.localStorage).forEach(key => { + if (key.startsWith(ProjectStorageKeyPrefix)) { + try { + const data = window.localStorage.getItem(key); + if (data) { + const project = JSON.parse(data); + projects.push(project); + } + } catch(err) { + console.error(err); + } + } + }); + + return projects +} + +export function useStoredProjectList(): [Project[], () => void] { + const [ projects, setProjects ] = useState(() => { + return loadStoredProjects(); + }); + + const refresh = () => { + setProjects(loadStoredProjects()); + }; + + return [ projects, refresh]; +} \ No newline at end of file diff --git a/src/models/project.ts b/src/models/project.ts index b98e98d..843ae40 100644 --- a/src/models/project.ts +++ b/src/models/project.ts @@ -16,9 +16,9 @@ export interface Tasks { [id: string]: Task } -export function newProject(): Project { +export function newProject(id?: string): Project { return { - id: uuidV4(), + id: id ? id : uuidV4(), label: "", description: "", tasks: {}, diff --git a/src/routes/home/index.tsx b/src/routes/home/index.tsx index 616499a..32d4781 100644 --- a/src/routes/home/index.tsx +++ b/src/routes/home/index.tsx @@ -2,8 +2,10 @@ import { FunctionalComponent, h } from "preact"; import * as style from "./style.css"; import { route } from 'preact-router'; import { base58UUID } from '../../util/uuid'; +import { useStoredProjectList } from "../../hooks/use-stored-project-list"; const Home: FunctionalComponent = () => { + const [ projects, refreshProjects ] = useStoredProjectList(); const openNewProject = () => { const uuid = base58UUID(); @@ -24,16 +26,27 @@ const Home: FunctionalComponent = () => {

Mes projets

-
+ {/*

🔍

-
- - 🗒️ - Projet #1 - +
*/} + { + projects.map(p => ( + + 🗒️ + { p.label ? p.label : "Projet sans nom" } + + )) + } + { + projects.length === 0 ? +

+

Aucun project pour l'instant.
+

: + null + } diff --git a/src/routes/home/style.css b/src/routes/home/style.css index b63a3c1..70b3b78 100644 --- a/src/routes/home/style.css +++ b/src/routes/home/style.css @@ -1,3 +1,9 @@ .home { height: 100%; +} + +.noProjects { + width: 100%; + text-align: center; + font-style: italic; } \ No newline at end of file diff --git a/src/routes/home/style.css.d.ts b/src/routes/home/style.css.d.ts index b4b2672..1fd05dd 100644 --- a/src/routes/home/style.css.d.ts +++ b/src/routes/home/style.css.d.ts @@ -1,2 +1,3 @@ // This file is automatically generated from your CSS. Any edits will be overwritten. export const home: string; +export const noProjects: string; diff --git a/src/routes/project/index.tsx b/src/routes/project/index.tsx index 5210b6f..d91062e 100644 --- a/src/routes/project/index.tsx +++ b/src/routes/project/index.tsx @@ -1,15 +1,23 @@ import { FunctionalComponent, h } from "preact"; -import { useReducer } from "preact/hooks"; +import { useEffect } from "preact/hooks"; import * as style from "./style.css"; import { newProject } from "../../models/project"; import TaskTable from "./tasks-table"; import TimePreview from "./time-preview"; import FinancialPreview from "./financial-preview"; -import { projectReducer, addTask, updateTaskEstimation, removeTask } from "../../hooks/project-reducer"; +import { useProjectReducer, addTask, updateTaskEstimation, removeTask } from "../../hooks/use-project-reducer"; import { Task, TaskID, EstimationConfidence } from "../../models/task"; +import { getProjectStorageKey } from "../../util/storage"; +import { useLocalStorage } from "../../hooks/use-local-storage"; -const Project: FunctionalComponent = () => { - const [project, dispatch] = useReducer(projectReducer, newProject()); +export interface ProjectProps { + projectId: string +} + +const Project: FunctionalComponent = ({ projectId }) => { + const projectStorageKey = getProjectStorageKey(projectId); + const [ storedProject, storeProject ] = useLocalStorage(projectStorageKey, newProject(projectId)); + const [ project, dispatch ] = useProjectReducer(storedProject); const onTaskAdd = (task: Task) => { dispatch(addTask(task)); @@ -23,8 +31,18 @@ const Project: FunctionalComponent = () => { dispatch(updateTaskEstimation(taskId, confidence, value)); }; + // Save project in local storage on change + useEffect(()=> { + storeProject(project); + }, [project]); + return (
+

+ {project.label ? project.label : "Projet sans nom"} +   + 🖋️ +

  • diff --git a/src/routes/project/time-preview.tsx b/src/routes/project/time-preview.tsx index afa19ef..3c41384 100644 --- a/src/routes/project/time-preview.tsx +++ b/src/routes/project/time-preview.tsx @@ -51,6 +51,13 @@ const TimePreview: FunctionalComponent = ({ project }) => { {`${estimations.p68.e} ± ${estimations.p68.sd} j/h`} + + + + ❓ Estimation à 3 points + + +
); diff --git a/src/util/storage.ts b/src/util/storage.ts new file mode 100644 index 0000000..757f5a3 --- /dev/null +++ b/src/util/storage.ts @@ -0,0 +1,8 @@ +import { useState } from "preact/hooks/src"; +import { ProjectID } from "../models/project"; + +export const ProjectStorageKeyPrefix = "project-"; + +export function getProjectStorageKey(id: ProjectID): string { + return `${ProjectStorageKeyPrefix}${id}` +} \ No newline at end of file