Création d'une page tableau en lecture seule et possibilité d'exporter en PDF via wkhtmltopdf
This commit is contained in:
parent
3c21412344
commit
6ebe4c90d7
|
@ -1,13 +1,13 @@
|
||||||
declare namespace StyleModuleCssModule {
|
declare namespace StyleModuleCssNamespace {
|
||||||
export interface IStyleModuleCss {
|
export interface IStyleModuleCss {
|
||||||
editIcon: string;
|
editIcon: string;
|
||||||
editableText: 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` */
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
locals: StyleModuleCssModule.IStyleModuleCss;
|
locals: StyleModuleCssNamespace.IStyleModuleCss;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = StyleModuleCssModule;
|
export = StyleModuleCssModule;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
declare namespace StyleModuleCssModule {
|
declare namespace StyleModuleCssNamespace {
|
||||||
export interface IStyleModuleCss {
|
export interface IStyleModuleCss {
|
||||||
footer: string;
|
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` */
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
locals: StyleModuleCssModule.IStyleModuleCss;
|
locals: StyleModuleCssNamespace.IStyleModuleCss;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = StyleModuleCssModule;
|
export = StyleModuleCssModule;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
declare namespace StyleModuleCssModule {
|
declare namespace StyleModuleCssNamespace {
|
||||||
export interface IStyleModuleCss {
|
export interface IStyleModuleCss {
|
||||||
header: string;
|
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` */
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
locals: StyleModuleCssModule.IStyleModuleCss;
|
locals: StyleModuleCssNamespace.IStyleModuleCss;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = StyleModuleCssModule;
|
export = StyleModuleCssModule;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
declare namespace StyleModuleCssModule {
|
declare namespace StyleModuleCssNamespace {
|
||||||
export interface IStyleModuleCss {
|
export interface IStyleModuleCss {
|
||||||
tabContent: string;
|
tabContent: string;
|
||||||
tabs: 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` */
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
locals: StyleModuleCssModule.IStyleModuleCss;
|
locals: StyleModuleCssNamespace.IStyleModuleCss;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = StyleModuleCssModule;
|
export = StyleModuleCssModule;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
declare namespace StyleModuleCssModule {
|
declare namespace StyleModuleCssNamespace {
|
||||||
export interface IStyleModuleCss {
|
export interface IStyleModuleCss {
|
||||||
home: string;
|
home: string;
|
||||||
noProjects: 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` */
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
locals: StyleModuleCssModule.IStyleModuleCss;
|
locals: StyleModuleCssNamespace.IStyleModuleCss;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = StyleModuleCssModule;
|
export = StyleModuleCssModule;
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
declare namespace StyleModuleCssModule {
|
declare namespace StyleModuleCssNamespace {
|
||||||
export interface IStyleModuleCss {
|
export interface IStyleModuleCss {
|
||||||
notFound: string;
|
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` */
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
locals: StyleModuleCssModule.IStyleModuleCss;
|
locals: StyleModuleCssNamespace.IStyleModuleCss;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = StyleModuleCssModule;
|
export = StyleModuleCssModule;
|
||||||
|
|
|
@ -8,6 +8,8 @@ import { useProjectReducer, addTask, updateTaskEstimation, removeTask, updateTas
|
||||||
import { Task, TaskID, EstimationConfidence } from "../../models/task";
|
import { Task, TaskID, EstimationConfidence } from "../../models/task";
|
||||||
import TimePreview from "../project/time-preview";
|
import TimePreview from "../project/time-preview";
|
||||||
import RepartitionPreview from "../project/repartition-preview";
|
import RepartitionPreview from "../project/repartition-preview";
|
||||||
|
import FinancialPreview from "../project/financial-preview";
|
||||||
|
import { getHideFinancialPreviewOnPrint } from "../../models/params";
|
||||||
|
|
||||||
export interface PdfProps {
|
export interface PdfProps {
|
||||||
projectId: string
|
projectId: string
|
||||||
|
@ -20,14 +22,21 @@ const Pdf: FunctionalComponent<PdfProps> = ({ projectId }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={`container ${style.pdf}`}>
|
<div class={`container ${style.pdf}`}>
|
||||||
<div class="column is-9">
|
<div class="columns">
|
||||||
<TaskTable
|
<div class="column is-9">
|
||||||
project={project}
|
<TaskTable
|
||||||
readonly={true} />
|
project={project}
|
||||||
|
readonly={true} />
|
||||||
|
</div>
|
||||||
|
<div class="column is-3">
|
||||||
|
<TimePreview project={project} />
|
||||||
|
<RepartitionPreview project={project} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="column is-3">
|
<div class="columns">
|
||||||
<TimePreview project={project} />
|
<div class={`column ${getHideFinancialPreviewOnPrint(project) ? 'noPrint': ''}`}>
|
||||||
<RepartitionPreview project={project} />
|
<FinancialPreview project={project} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
declare namespace StyleModuleCssModule {
|
declare namespace StyleModuleCssNamespace {
|
||||||
export interface IStyleModuleCss {
|
export interface IStyleModuleCss {
|
||||||
estimation: string;
|
estimation: string;
|
||||||
mainColumn: 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` */
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
locals: StyleModuleCssModule.IStyleModuleCss;
|
locals: StyleModuleCssNamespace.IStyleModuleCss;
|
||||||
};
|
};
|
||||||
|
|
||||||
export = StyleModuleCssModule;
|
export = StyleModuleCssModule;
|
||||||
|
|
|
@ -91,7 +91,10 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
|
||||||
<table class={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}>
|
<table class={`table is-bordered is-striped is-hoverable is-fullwidth ${style.middleTable}`}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
{
|
||||||
|
readonly ? '' :
|
||||||
<th class={`${style.noBorder} noPrint`} rowSpan={2}></th>
|
<th class={`${style.noBorder} noPrint`} rowSpan={2}></th>
|
||||||
|
}
|
||||||
<th class={style.mainColumn} rowSpan={2}>Tâche</th>
|
<th class={style.mainColumn} rowSpan={2}>Tâche</th>
|
||||||
<th rowSpan={2}>Catégorie</th>
|
<th rowSpan={2}>Catégorie</th>
|
||||||
<th colSpan={3}>Estimation (en <ProjectTimeUnit project={project} />)</th>
|
<th colSpan={3}>Estimation (en <ProjectTimeUnit project={project} />)</th>
|
||||||
|
@ -109,6 +112,8 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
|
||||||
const categoryLabel = category ? category.label : '???';
|
const categoryLabel = category ? category.label : '???';
|
||||||
return (
|
return (
|
||||||
<tr key={`taks-${t.id}`}>
|
<tr key={`taks-${t.id}`}>
|
||||||
|
{
|
||||||
|
readonly ? '' :
|
||||||
<td class={`is-narrow noPrint`}>
|
<td class={`is-narrow noPrint`}>
|
||||||
<button
|
<button
|
||||||
onClick={onTaskRemoveClick.bind(null, t.id)}
|
onClick={onTaskRemoveClick.bind(null, t.id)}
|
||||||
|
@ -116,6 +121,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
|
||||||
🗑️
|
🗑️
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
}
|
||||||
<td class={style.mainColumn}>
|
<td class={style.mainColumn}>
|
||||||
<EditableText
|
<EditableText
|
||||||
render={(value) => (<span>{value}</span>)}
|
render={(value) => (<span>{value}</span>)}
|
||||||
|
@ -166,7 +172,9 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
|
||||||
<tfoot>
|
<tfoot>
|
||||||
<tr>
|
<tr>
|
||||||
<td class={`${style.noBorder} noPrint`}></td>
|
<td class={`${style.noBorder} noPrint`}></td>
|
||||||
<td colSpan={2} class={readonly ? style.noBorder : ''}>
|
<td colSpan={readonly ? 1 : 2} class={readonly ? style.noBorder : ''}>
|
||||||
|
{
|
||||||
|
readonly ? '' :
|
||||||
<div class="field has-addons noPrint">
|
<div class="field has-addons noPrint">
|
||||||
<p class="control is-expanded">
|
<p class="control is-expanded">
|
||||||
<input class="input" type="text" placeholder="Nouvelle tâche"
|
<input class="input" type="text" placeholder="Nouvelle tâche"
|
||||||
|
@ -191,6 +199,7 @@ const TaskTable: FunctionalComponent<TaskTableProps> = ({ project, onTaskAdd, on
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<th colSpan={3}>Total</th>
|
<th colSpan={3}>Total</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -27,6 +27,9 @@ module.exports = {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://127.0.0.1:8081',
|
target: 'http://127.0.0.1:8081',
|
||||||
|
},
|
||||||
|
'/export': {
|
||||||
|
target: 'http://127.0.0.1:8081',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,6 +3,7 @@ module forge.cadoles.com/wpetit/guesstimate
|
||||||
go 1.14
|
go 1.14
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/SebastiaanKlippert/go-wkhtmltopdf v1.5.0
|
||||||
github.com/asdine/storm/v3 v3.1.1
|
github.com/asdine/storm/v3 v3.1.1
|
||||||
github.com/caarlos0/env/v6 v6.2.1
|
github.com/caarlos0/env/v6 v6.2.1
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
|
|
@ -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/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.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
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 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM=
|
||||||
github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM=
|
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=
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
|
|
@ -10,16 +10,27 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Config is the configuration struct
|
||||||
type Config struct {
|
type Config struct {
|
||||||
HTTP HTTPConfig `yaml:"http"`
|
HTTP HTTPConfig `yaml:"http"`
|
||||||
Data DataConfig `ymal:"data"`
|
Client ClientConfig `yaml:"client"`
|
||||||
|
Data DataConfig `ymal:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPConfig is the configuration part which defines HTTP related params
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
BaseURL string `yaml:"baseurl" env:"GUESSTIMATE_BASE_URL"`
|
||||||
Address string `yaml:"address" env:"GUESSTIMATE_HTTP_ADDRESS"`
|
Address string `yaml:"address" env:"GUESSTIMATE_HTTP_ADDRESS"`
|
||||||
PublicDir string `yaml:"publicDir" env:"GUESSTIMATE_PUBLIC_DIR"`
|
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 {
|
type DataConfig struct {
|
||||||
Path string `yaml:"path" env:"GUESSTIMATE_DATA_PATH"`
|
Path string `yaml:"path" env:"GUESSTIMATE_DATA_PATH"`
|
||||||
}
|
}
|
||||||
|
@ -40,6 +51,7 @@ func NewFromFile(filepath string) (*Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithEnvironment retrieves the configuration from env vars
|
||||||
func WithEnvironment(conf *Config) error {
|
func WithEnvironment(conf *Config) error {
|
||||||
if err := env.Parse(conf); err != nil {
|
if err := env.Parse(conf); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -48,23 +60,31 @@ func WithEnvironment(conf *Config) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDumpDefault retrieves the default configuration
|
||||||
func NewDumpDefault() *Config {
|
func NewDumpDefault() *Config {
|
||||||
config := NewDefault()
|
config := NewDefault()
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDefault creates and returns a new default configuration
|
||||||
func NewDefault() *Config {
|
func NewDefault() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
HTTP: HTTPConfig{
|
HTTP: HTTPConfig{
|
||||||
|
BaseURL: "localhost",
|
||||||
Address: ":8081",
|
Address: ":8081",
|
||||||
PublicDir: "public",
|
PublicDir: "public",
|
||||||
},
|
},
|
||||||
|
Client: ClientConfig{
|
||||||
|
BaseURL: "localhost",
|
||||||
|
Address: ":8080",
|
||||||
|
},
|
||||||
Data: DataConfig{
|
Data: DataConfig{
|
||||||
Path: "guesstimate.db",
|
Path: "guesstimate.db",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dump writes a given config to a config file
|
||||||
func Dump(config *Config, w io.Writer) error {
|
func Dump(config *Config, w io.Writer) error {
|
||||||
data, err := yaml.Marshal(config)
|
data, err := yaml.Marshal(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import "gitlab.com/wpetit/goweb/service"
|
import "gitlab.com/wpetit/goweb/service"
|
||||||
|
|
||||||
|
// ServiceProvider returns the current config service
|
||||||
func ServiceProvider(config *Config) service.Provider {
|
func ServiceProvider(config *Config) service.Provider {
|
||||||
return func(ctn *service.Container) (interface{}, error) {
|
return func(ctn *service.Container) (interface{}, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"gitlab.com/wpetit/goweb/service"
|
"gitlab.com/wpetit/goweb/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ServiceName defines the project's service
|
||||||
const ServiceName service.Name = "config"
|
const ServiceName service.Name = "config"
|
||||||
|
|
||||||
// From retrieves the config service in the given container
|
// From retrieves the config service in the given container
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"gitlab.com/wpetit/goweb/static"
|
"gitlab.com/wpetit/goweb/static"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Mount endoints for server app
|
||||||
func Mount(r *chi.Mux, config *config.Config) error {
|
func Mount(r *chi.Mux, config *config.Config) error {
|
||||||
r.Route("/api/v1", func(r chi.Router) {
|
r.Route("/api/v1", func(r chi.Router) {
|
||||||
r.Get("/projects/{projectID}", handleGetProject)
|
r.Get("/projects/{projectID}", handleGetProject)
|
||||||
|
@ -17,6 +18,10 @@ func Mount(r *chi.Mux, config *config.Config) error {
|
||||||
r.Delete("/projects/{projectID}", handleDeleteProject)
|
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")
|
clientIndex := path.Join(config.HTTP.PublicDir, "index.html")
|
||||||
|
|
||||||
serveClientIndex := func(w http.ResponseWriter, r *http.Request) {
|
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("/p/*", serveClientIndex)
|
||||||
|
r.Get("/pdf/*", serveClientIndex)
|
||||||
|
|
||||||
notFoundHandler := r.NotFoundHandler()
|
notFoundHandler := r.NotFoundHandler()
|
||||||
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", notFoundHandler))
|
r.Get("/*", static.Dir(config.HTTP.PublicDir, "", notFoundHandler))
|
||||||
|
|
|
@ -2,14 +2,17 @@ package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
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/model"
|
||||||
"forge.cadoles.com/wpetit/guesstimate/internal/storm"
|
"forge.cadoles.com/wpetit/guesstimate/internal/storm"
|
||||||
|
"github.com/SebastiaanKlippert/go-wkhtmltopdf"
|
||||||
"github.com/go-chi/chi"
|
"github.com/go-chi/chi"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gitlab.com/wpetit/goweb/middleware/container"
|
"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 {
|
type createRequest struct {
|
||||||
Project *model.Project `json:"project"`
|
Project *model.Project `json:"project"`
|
||||||
}
|
}
|
||||||
|
@ -246,3 +305,12 @@ func writeJSON(w http.ResponseWriter, statusCode int, data interface{}) error {
|
||||||
|
|
||||||
return encoder.Encode(data)
|
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)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue