Local storage basic persistence
This commit is contained in:
parent
3ebb707fd3
commit
10983f6ac4
8
Makefile
8
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
|
||||
git push -f dokku $(shell git rev-parse HEAD):refs/heads/master
|
|
@ -23,7 +23,7 @@ const App: FunctionalComponent = () => {
|
|||
<Header />
|
||||
<Router onChange={handleRoute}>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/p/:uuid" component={Project} />
|
||||
<Route path="/p/:projectId" component={Project} />
|
||||
<NotFoundPage default />
|
||||
</Router>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { useState } from "preact/hooks";
|
||||
|
||||
export function useLocalStorage<T>(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];
|
||||
}
|
|
@ -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:
|
|
@ -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];
|
||||
}
|
|
@ -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: {},
|
||||
|
|
|
@ -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 = () => {
|
|||
<p class="panel-heading">
|
||||
Mes projets
|
||||
</p>
|
||||
<div class="panel-block">
|
||||
{/* <div class="panel-block">
|
||||
<p class="control has-icons-left">
|
||||
<input class="input" type="text" placeholder="Search" />
|
||||
<span class="icon is-left">🔍</span>
|
||||
</p>
|
||||
</div>
|
||||
<a class="panel-block">
|
||||
</div> */}
|
||||
{
|
||||
projects.map(p => (
|
||||
<a class="panel-block" href={`/p/${p.id}`}>
|
||||
<span class="panel-icon">🗒️</span>
|
||||
Projet #1
|
||||
{ p.label ? p.label : "Projet sans nom" }
|
||||
</a>
|
||||
))
|
||||
}
|
||||
{
|
||||
projects.length === 0 ?
|
||||
<p class="panel-block">
|
||||
<div class={style.noProjects}>Aucun project pour l'instant.</div>
|
||||
</p> :
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
.home {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.noProjects {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<ProjectProps> = ({ 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 (
|
||||
<div class={`container ${style.estimation}`}>
|
||||
<h3 class="is-size-3">
|
||||
{project.label ? project.label : "Projet sans nom"}
|
||||
|
||||
<i class="icon is-size-4">🖋️</i>
|
||||
</h3>
|
||||
<div class="tabs">
|
||||
<ul>
|
||||
<li class="is-active">
|
||||
|
|
|
@ -51,6 +51,13 @@ const TimePreview: FunctionalComponent<TimePreviewProps> = ({ project }) => {
|
|||
<td>{`${estimations.p68.e} ± ${estimations.p68.sd} j/h`}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={2}>
|
||||
<a class="is-small is-pulled-right" href="https://en.wikipedia.org/wiki/Three-point_estimation" target="_blank">❓ Estimation à 3 points</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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}`
|
||||
}
|
Loading…
Reference in New Issue