From 6ebe4c90d7c11fbb361ed3c1aaf52257a399f1d8 Mon Sep 17 00:00:00 2001 From: Teddy Cornaut Date: Mon, 15 Jun 2020 15:05:28 -0400 Subject: [PATCH] =?UTF-8?q?Cr=C3=A9ation=20d'une=20page=20tableau=20en=20l?= =?UTF-8?q?ecture=20seule=20et=20possibilit=C3=A9=20d'exporter=20en=20PDF?= =?UTF-8?q?=20via=20wkhtmltopdf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editable-text/style.module.css.d.ts | 6 +- .../components/footer/style.module.css.d.ts | 6 +- .../components/header/style.module.css.d.ts | 6 +- .../src/components/tabs/style.module.css.d.ts | 6 +- client/src/routes/home/style.module.css.d.ts | 6 +- .../src/routes/notfound/style.module.css.d.ts | 6 +- client/src/routes/pdf/index.tsx | 23 +++++-- .../src/routes/project/style.module.css.d.ts | 6 +- client/src/routes/project/tasks-table.tsx | 11 ++- client/webpack.config.js | 3 + server/go.mod | 1 + server/go.sum | 2 + server/internal/config/config.go | 24 ++++++- server/internal/config/provider.go | 1 + server/internal/config/service.go | 1 + server/internal/route/mount.go | 6 ++ server/internal/route/project.go | 68 +++++++++++++++++++ 17 files changed, 151 insertions(+), 31 deletions(-) diff --git a/client/src/components/editable-text/style.module.css.d.ts b/client/src/components/editable-text/style.module.css.d.ts index 0b98c20..592d97e 100644 --- a/client/src/components/editable-text/style.module.css.d.ts +++ b/client/src/components/editable-text/style.module.css.d.ts @@ -1,13 +1,13 @@ -declare namespace StyleModuleCssModule { +declare namespace StyleModuleCssNamespace { export interface IStyleModuleCss { editIcon: string; editableText: string; } } -declare const StyleModuleCssModule: StyleModuleCssModule.IStyleModuleCss & { +declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & { /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ - locals: StyleModuleCssModule.IStyleModuleCss; + locals: StyleModuleCssNamespace.IStyleModuleCss; }; export = StyleModuleCssModule; diff --git a/client/src/components/footer/style.module.css.d.ts b/client/src/components/footer/style.module.css.d.ts index 980cacc..999eeb6 100644 --- a/client/src/components/footer/style.module.css.d.ts +++ b/client/src/components/footer/style.module.css.d.ts @@ -1,12 +1,12 @@ -declare namespace StyleModuleCssModule { +declare namespace StyleModuleCssNamespace { export interface IStyleModuleCss { footer: string; } } -declare const StyleModuleCssModule: StyleModuleCssModule.IStyleModuleCss & { +declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & { /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ - locals: StyleModuleCssModule.IStyleModuleCss; + locals: StyleModuleCssNamespace.IStyleModuleCss; }; export = StyleModuleCssModule; diff --git a/client/src/components/header/style.module.css.d.ts b/client/src/components/header/style.module.css.d.ts index 00823f0..b38bc08 100644 --- a/client/src/components/header/style.module.css.d.ts +++ b/client/src/components/header/style.module.css.d.ts @@ -1,12 +1,12 @@ -declare namespace StyleModuleCssModule { +declare namespace StyleModuleCssNamespace { export interface IStyleModuleCss { header: string; } } -declare const StyleModuleCssModule: StyleModuleCssModule.IStyleModuleCss & { +declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & { /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ - locals: StyleModuleCssModule.IStyleModuleCss; + locals: StyleModuleCssNamespace.IStyleModuleCss; }; export = StyleModuleCssModule; diff --git a/client/src/components/tabs/style.module.css.d.ts b/client/src/components/tabs/style.module.css.d.ts index cafc1ae..29db20b 100644 --- a/client/src/components/tabs/style.module.css.d.ts +++ b/client/src/components/tabs/style.module.css.d.ts @@ -1,13 +1,13 @@ -declare namespace StyleModuleCssModule { +declare namespace StyleModuleCssNamespace { export interface IStyleModuleCss { tabContent: string; tabs: string; } } -declare const StyleModuleCssModule: StyleModuleCssModule.IStyleModuleCss & { +declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & { /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ - locals: StyleModuleCssModule.IStyleModuleCss; + locals: StyleModuleCssNamespace.IStyleModuleCss; }; export = StyleModuleCssModule; diff --git a/client/src/routes/home/style.module.css.d.ts b/client/src/routes/home/style.module.css.d.ts index 70df6e4..cd52b6a 100644 --- a/client/src/routes/home/style.module.css.d.ts +++ b/client/src/routes/home/style.module.css.d.ts @@ -1,13 +1,13 @@ -declare namespace StyleModuleCssModule { +declare namespace StyleModuleCssNamespace { export interface IStyleModuleCss { home: string; noProjects: string; } } -declare const StyleModuleCssModule: StyleModuleCssModule.IStyleModuleCss & { +declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & { /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ - locals: StyleModuleCssModule.IStyleModuleCss; + locals: StyleModuleCssNamespace.IStyleModuleCss; }; export = StyleModuleCssModule; diff --git a/client/src/routes/notfound/style.module.css.d.ts b/client/src/routes/notfound/style.module.css.d.ts index 6b2fb35..2e0f329 100644 --- a/client/src/routes/notfound/style.module.css.d.ts +++ b/client/src/routes/notfound/style.module.css.d.ts @@ -1,12 +1,12 @@ -declare namespace StyleModuleCssModule { +declare namespace StyleModuleCssNamespace { export interface IStyleModuleCss { notFound: string; } } -declare const StyleModuleCssModule: StyleModuleCssModule.IStyleModuleCss & { +declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & { /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ - locals: StyleModuleCssModule.IStyleModuleCss; + locals: StyleModuleCssNamespace.IStyleModuleCss; }; export = StyleModuleCssModule; diff --git a/client/src/routes/pdf/index.tsx b/client/src/routes/pdf/index.tsx index 22d549f..be9bae5 100644 --- a/client/src/routes/pdf/index.tsx +++ b/client/src/routes/pdf/index.tsx @@ -8,6 +8,8 @@ import { useProjectReducer, addTask, updateTaskEstimation, removeTask, updateTas import { Task, TaskID, EstimationConfidence } from "../../models/task"; import TimePreview from "../project/time-preview"; import RepartitionPreview from "../project/repartition-preview"; +import FinancialPreview from "../project/financial-preview"; +import { getHideFinancialPreviewOnPrint } from "../../models/params"; export interface PdfProps { projectId: string @@ -20,14 +22,21 @@ const Pdf: FunctionalComponent = ({ projectId }) => { return (
-
- +
+
+ +
+
+ + +
-
- - +
+
+ +
); diff --git a/client/src/routes/project/style.module.css.d.ts b/client/src/routes/project/style.module.css.d.ts index 486b573..24be9b6 100644 --- a/client/src/routes/project/style.module.css.d.ts +++ b/client/src/routes/project/style.module.css.d.ts @@ -1,4 +1,4 @@ -declare namespace StyleModuleCssModule { +declare namespace StyleModuleCssNamespace { export interface IStyleModuleCss { estimation: string; mainColumn: string; @@ -9,9 +9,9 @@ declare namespace StyleModuleCssModule { } } -declare const StyleModuleCssModule: StyleModuleCssModule.IStyleModuleCss & { +declare const StyleModuleCssModule: StyleModuleCssNamespace.IStyleModuleCss & { /** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */ - locals: StyleModuleCssModule.IStyleModuleCss; + locals: StyleModuleCssNamespace.IStyleModuleCss; }; export = StyleModuleCssModule; diff --git a/client/src/routes/project/tasks-table.tsx b/client/src/routes/project/tasks-table.tsx index 7b24f75..e2bbfed 100644 --- a/client/src/routes/project/tasks-table.tsx +++ b/client/src/routes/project/tasks-table.tsx @@ -91,7 +91,10 @@ const TaskTable: FunctionalComponent = ({ project, onTaskAdd, on + { + readonly ? '' : + } @@ -109,6 +112,8 @@ const TaskTable: FunctionalComponent = ({ project, onTaskAdd, on const categoryLabel = category ? category.label : '???'; return ( + { + readonly ? '' : + } - diff --git a/client/webpack.config.js b/client/webpack.config.js index 7469cb2..6d47f90 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -27,6 +27,9 @@ module.exports = { proxy: { '/api': { target: 'http://127.0.0.1:8081', + }, + '/export': { + target: 'http://127.0.0.1:8081', } } }, diff --git a/server/go.mod b/server/go.mod index c6e2517..c99852f 100644 --- a/server/go.mod +++ b/server/go.mod @@ -3,6 +3,7 @@ module forge.cadoles.com/wpetit/guesstimate go 1.14 require ( + github.com/SebastiaanKlippert/go-wkhtmltopdf v1.5.0 github.com/asdine/storm/v3 v3.1.1 github.com/caarlos0/env/v6 v6.2.1 github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/server/go.sum b/server/go.sum index 4a15dba..54bbbad 100644 --- a/server/go.sum +++ b/server/go.sum @@ -19,6 +19,8 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/SebastiaanKlippert/go-wkhtmltopdf v1.5.0 h1:KNMkSqhko6c7eVncl/laVCS95jRryULVhVwwL0VynnU= +github.com/SebastiaanKlippert/go-wkhtmltopdf v1.5.0/go.mod h1:zRBvVJtIfhaWvQ9lPIkXrtona4qqSmjZ1HfKvq4dQzI= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= diff --git a/server/internal/config/config.go b/server/internal/config/config.go index 788c52a..65caa51 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -10,16 +10,27 @@ import ( "gopkg.in/yaml.v2" ) +// Config is the configuration struct type Config struct { - HTTP HTTPConfig `yaml:"http"` - Data DataConfig `ymal:"data"` + HTTP HTTPConfig `yaml:"http"` + Client ClientConfig `yaml:"client"` + Data DataConfig `ymal:"data"` } +// HTTPConfig is the configuration part which defines HTTP related params type HTTPConfig struct { + BaseURL string `yaml:"baseurl" env:"GUESSTIMATE_BASE_URL"` Address string `yaml:"address" env:"GUESSTIMATE_HTTP_ADDRESS"` PublicDir string `yaml:"publicDir" env:"GUESSTIMATE_PUBLIC_DIR"` } +// ClientConfig is the configuration part which defines the client app related params +type ClientConfig struct { + BaseURL string `yaml:"baseurl" env:"GUESSTIMATE_CLIENT_BASE_URL"` + Address string `yaml:"address" env:"GUESSTIMATE_CLIENT_HTTP_ADDRESS"` +} + +// DataConfig is the configuration part which defines data related params type DataConfig struct { Path string `yaml:"path" env:"GUESSTIMATE_DATA_PATH"` } @@ -40,6 +51,7 @@ func NewFromFile(filepath string) (*Config, error) { return config, nil } +// WithEnvironment retrieves the configuration from env vars func WithEnvironment(conf *Config) error { if err := env.Parse(conf); err != nil { return err @@ -48,23 +60,31 @@ func WithEnvironment(conf *Config) error { return nil } +// NewDumpDefault retrieves the default configuration func NewDumpDefault() *Config { config := NewDefault() return config } +// NewDefault creates and returns a new default configuration func NewDefault() *Config { return &Config{ HTTP: HTTPConfig{ + BaseURL: "localhost", Address: ":8081", PublicDir: "public", }, + Client: ClientConfig{ + BaseURL: "localhost", + Address: ":8080", + }, Data: DataConfig{ Path: "guesstimate.db", }, } } +// Dump writes a given config to a config file func Dump(config *Config, w io.Writer) error { data, err := yaml.Marshal(config) if err != nil { diff --git a/server/internal/config/provider.go b/server/internal/config/provider.go index 0e768ed..7a94d46 100644 --- a/server/internal/config/provider.go +++ b/server/internal/config/provider.go @@ -2,6 +2,7 @@ package config import "gitlab.com/wpetit/goweb/service" +// ServiceProvider returns the current config service func ServiceProvider(config *Config) service.Provider { return func(ctn *service.Container) (interface{}, error) { return config, nil diff --git a/server/internal/config/service.go b/server/internal/config/service.go index e57c05d..f716596 100644 --- a/server/internal/config/service.go +++ b/server/internal/config/service.go @@ -5,6 +5,7 @@ import ( "gitlab.com/wpetit/goweb/service" ) +// ServiceName defines the project's service const ServiceName service.Name = "config" // From retrieves the config service in the given container diff --git a/server/internal/route/mount.go b/server/internal/route/mount.go index da26801..08923a6 100644 --- a/server/internal/route/mount.go +++ b/server/internal/route/mount.go @@ -9,6 +9,7 @@ import ( "gitlab.com/wpetit/goweb/static" ) +// Mount endoints for server app func Mount(r *chi.Mux, config *config.Config) error { r.Route("/api/v1", func(r chi.Router) { r.Get("/projects/{projectID}", handleGetProject) @@ -17,6 +18,10 @@ func Mount(r *chi.Mux, config *config.Config) error { r.Delete("/projects/{projectID}", handleDeleteProject) }) + r.Route("/export", func(r chi.Router) { + r.Get("/projects/{projectID}", handleExportProject) + }) + clientIndex := path.Join(config.HTTP.PublicDir, "index.html") serveClientIndex := func(w http.ResponseWriter, r *http.Request) { @@ -24,6 +29,7 @@ func Mount(r *chi.Mux, config *config.Config) error { } r.Get("/p/*", serveClientIndex) + r.Get("/pdf/*", serveClientIndex) notFoundHandler := r.NotFoundHandler() r.Get("/*", static.Dir(config.HTTP.PublicDir, "", notFoundHandler)) diff --git a/server/internal/route/project.go b/server/internal/route/project.go index 5b6846f..de092c0 100644 --- a/server/internal/route/project.go +++ b/server/internal/route/project.go @@ -2,14 +2,17 @@ package route import ( "encoding/json" + "fmt" "log" "net/http" "strconv" jsonpatch "gopkg.in/evanphx/json-patch.v4" + "forge.cadoles.com/wpetit/guesstimate/internal/config" "forge.cadoles.com/wpetit/guesstimate/internal/model" "forge.cadoles.com/wpetit/guesstimate/internal/storm" + "github.com/SebastiaanKlippert/go-wkhtmltopdf" "github.com/go-chi/chi" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/middleware/container" @@ -74,6 +77,62 @@ func handleGetProject(w http.ResponseWriter, r *http.Request) { } } +func handleExportProject(w http.ResponseWriter, r *http.Request) { + ctn := container.Must(r.Context()) + cfg := config.Must(ctn) + + projectID := getProjectID(r) + + var ( + err error + url string + ) + + url = "http://" + string(cfg.Client.BaseURL) + string(cfg.Client.Address) + "/pdf/" + string(projectID) + fmt.Println(url) + + // Create new PDF generator + pdfg, err := wkhtmltopdf.NewPDFGenerator() + if err != nil { + log.Fatal(err) + } + + // Set global options + pdfg.Dpi.Set(300) + pdfg.Orientation.Set(wkhtmltopdf.OrientationPortrait) + pdfg.Grayscale.Set(true) + + // Create a new input page from an URL + page := wkhtmltopdf.NewPage(url) + + // Set options for this page + page.FooterRight.Set("[page]") + page.FooterFontSize.Set(10) + page.Zoom.Set(0.95) + + // Add to document + pdfg.AddPage(page) + + // Create PDF document in internal buffer + err = pdfg.Create() + if err != nil { + log.Fatal(err) + } + + // Write buffer contents to file on disk + err = pdfg.WriteFile("./sample.pdf") + if err != nil { + log.Fatal(err) + } + + rsp, err := writePDF(w, http.StatusOK, pdfg.Bytes()) + if err != nil { + panic(errors.Wrap(err, "could not write pdf response")) + } + + fmt.Println(rsp) +} + type createRequest struct { Project *model.Project `json:"project"` } @@ -246,3 +305,12 @@ func writeJSON(w http.ResponseWriter, statusCode int, data interface{}) error { return encoder.Encode(data) } + +func writePDF(w http.ResponseWriter, statusCode int, data []byte) (int, error) { + w.Header().Set("Content-Disposition", "attachment; filename=foo.pdf") + w.Header().Set("Content-Type", "application/pdf") + + w.WriteHeader(statusCode) + + return w.Write(data) +}
Tâche Catégorie Estimation (en )
({value})} @@ -166,7 +172,9 @@ const TaskTable: FunctionalComponent = ({ project, onTaskAdd, on
+ + { + readonly ? '' :

= ({ project, onTaskAdd, on

+ }
Total