...
 
Commits (80)
{
"presets": ["es2015", "react"]
"presets": ["es2015", "react", "stage-0"]
}
......@@ -4,7 +4,6 @@
build/
# ignore vendor JS files
client/flux/
client/vendor/
# ignore locales
......
This diff is collapsed.
......@@ -3,6 +3,7 @@ image: node:4.4.4-slim
before_script:
- npm install
check:
tests:
script:
- npm run check
- npm run lint-full
- npm run test
# Bug report
Thanks for filling all the following fields, if they apply, and to describe
your issue the most precisely you can.
Weboob's version I am using is: [fill here]
Kresus's version I am using is: [fill here]
Is Kresus running within CozyCloud or without? [fill here]
If it's used within CozyCloud, am I hosted on cozycloud.cc or self-hosted? [fill here]
Steps to reproduce: [fill here]
What I expect: [fill here]
What I observe: [fill here]
Client logs (if apply): [fill here]
Server logs (if apply): [fill here]
# Rapport de bug
Merci de compléter tous les champs suivants, dans la mesure du possible, et de
décrire le plus précisément possible votre souci.
Version de Weboob que j'utilise : [compléter]
Version de Kresus que j'utilise : [compléter]
Est-ce que Kresus fonctionne dans CozyCloud ou est-ce que j'utilise la version autonome : [compléter]
Si j'utilise Kresus dans CozyCloud, suis-je hébergé sur cozycloud.cc ou auto-hébergé : [compléter]
Les étapes à faire pour reproduire le bug : [compléter]
Ce que je m'attends à voir : [compléter]
Ce que j'observe en pratique : [compléter]
Mes logs côté client (s'ils s'appliquent) : [compléter]
Mes logs côté serveur (s'ils s'appliquent) : [compléter]
......@@ -7,7 +7,7 @@ RUN apt-get update && \
libxml2-dev libxslt-dev libyaml-dev libtiff-dev libjpeg-dev zlib1g-dev \
libfreetype6-dev libwebp-dev build-essential gcc g++;
RUN git clone git://git.symlink.me/pub/weboob/devel.git /tmp/weboob \
RUN git clone https://git.weboob.org/weboob/devel /tmp/weboob \
&& cd /tmp/weboob \
&& python ./setup.py install \
&& rm -rf /tmp/weboob
......
.PHONY: help run run-cozy install-node-deps install install-debians-deps install-debian install-node-dev-deps build dev lint lint-client lint-server test release
.PHONY: help install-node-dev-deps localrun install build dev lint lint-full lint-client lint-server test check release
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' Makefile | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
run: install-node-deps ## Runs the standalone version of kresus, from this directory.
node bin/kresus.js
run-cozy: install-node-deps ## Runs the CozyCloud version of kresus, from this directory.
node build/server/index.js
install-node-deps: ## Installs all node dependencies for a production environment.
npm install --production
install: ## Globally install the standalone version of kresus.
npm -g install --production
install-node-dev-deps: ## Installs all node dependencies for a development environment.
npm install
install-debian-deps: ## Install the extra Debian dependencies necessary to run Kresus.
sudo apt-get update
sudo apt-get install -y python-weboob-core
localrun: install-node-dev-deps build ## Runs the standalone version of kresus, from this directory.
node bin/kresus.js
install-debian: install install-debian-deps ## Globally install Kresus and all its dependencies.
install: ## Globally install a prebuilt standalone version of kresus.
npm -g install kresus
install-node-dev-deps: ## Installs all node dependencies for a development environment.
npm install
# Dev rules:
build: ## Transpiles ES6 files to ES5, moves files and concatenate them to obtain a usable build.
./scripts/build.sh
......@@ -30,14 +20,17 @@ build: ## Transpiles ES6 files to ES5, moves files and concatenate them to obtai
dev: build ## As build, but retriggers incremental compilation as the files are changed on disk.
./scripts/dev.sh
lint: ## Runs the linter for the server and the client.
lint: ## Runs the linter for the server and the client, without warnings.
./scripts/lint.sh
lint-full: ## Runs the linter for the server and the client, with warnings.
./scripts/lint-full.sh
lint-client: ## Runs the linter on the client.
./scripts/lint.sh ./client
./scripts/lint-full.sh ./client
lint-server: ## Runs the linter on the server.
./scripts/lint.sh ./server
./scripts/lint-full.sh ./server
test: ## Runs all the tests.
./scripts/test.sh
......@@ -45,11 +38,5 @@ test: ## Runs all the tests.
check: ## Runs all tests and style checks.
./scripts/check.sh
release: ## Prepares for a release. To be done only on the `master` branch.
NODE_ENV=production make build
rm -rf build/server/weboob/data
git add -f build/
npm shrinkwrap
git add npm-shrinkwrap.json
git status
release: ## Prepares for a release. To be done only on the `builds` branch.
./scripts/release.sh
......@@ -9,80 +9,39 @@ but is way different now.
If you like the work we're doing, consider making a donation!
[![Make a recurrent donation](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/bnjbvr/donate)
[![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=bnj&url=https://github.com/bnjbvr/kresus&title=Kresus&language=&tags=github&category=software)
# How to install Kresus
## With Docker
### Run a pre-built image
```
docker run -p 9876:9876 -v /opt/kresus/data:/home/user/data -ti -d bnjbvr/kresus
```
### Build the image
There is a Dockerfile from which you can build and run Kresus, using the
following commands (don't forget to change the port mapping and the volume
mapping, if necessary!):
- `git clone https://framagit.org/bnjbvr/kresus && cd kresus`
- `docker build -t kresus .`
- `docker run -p 9876:9876 -v /opt/kresus/data:/home/user/data -ti -d kresus`
## Pre-requisites
Kresus uses [Weboob](http://weboob.org/) under the hood, to connect to your
bank website. You'll need to [install Weboob](http://weboob.org/install) core
modules so that the user executing the Kresus server can use them.
Note that Kresus needs the latest stable version of Weboob, which is 1.1 at
this point. Although Kresus would also work with the 1.0 version, it won't let
you run this version, because bank modules might be outdated and not updated
anymore, and as a result synchronization with your bank website would not work.
This can be done with the following command, under Debian based systems:
make install-debian-deps
## Install on CozyCloud
If you already have a Cozy instance set up, then your best (and
[only](https://github.com/cozy/cozy-home/issues/789)) choice is to install
Kresus from the Marketplace.
## Standalone install on Debian
Note that Kresus needs the latest stable version of Weboob. Although Kresus
would also work with previous versions, bank modules might be outdated and not
updated anymore, and as a result synchronization with your bank website would
not work and/or display erroneous results.
Kresus can be installed standalone (without a cozy). In this case, a few
features won't be enabled: email reports and notifications, simple
notifications.
## Standalone install
**DISCLAIMER: as Kresus is designed to run under CozyCloud, which handles
authentication, there are no builtin authentication systems in the standalone
**DISCLAIMER: There are no builtin authentication systems in the standalone
Kresus and it is therefore more risky to use it in the wild. Choose this only
if you know what you're doing and you're able to handle authentication.**
There's a single command to run to install Kresus under Debian:
make install-debian
This will install the dependencies, build the project and install at the global
node.js program location. Note that if this location is `/usr/local/bin`, you
might need to use root access to apply this command properly.
if you know what you're doing and you're able to handle authentication by
yourself.**
## On any other (UNIX based) operating system
**DISCLAIMER: the disclaimer above for Debian applies here as well.**
First of all, make sure to have installed all the dependencies, as explained
above.
This will install the dependencies, build the project and install the program
in the global node.js bin directory. Note that if this location is
`/usr/local/bin`, you might need to use root access to run this command.
### Local setup
Install the node dependencies and build the scripts (this won't install
kresus globally):
make run
make localrun
### Global setup
......@@ -94,6 +53,29 @@ And then you can simply start Kresus from any terminal in any directory with:
kresus
## With Docker
### Run a pre-built image
```
docker run -p 9876:9876 -v /opt/kresus/data:/home/user/data -ti -d bnjbvr/kresus
```
### Build the image
There is a Dockerfile from which you can build and run Kresus, using the
following commands (don't forget to change the port mapping and the volume
mapping, if necessary!):
- `git clone https://framagit.org/bnjbvr/kresus && cd kresus`
- `docker build -t kresus .`
- `docker run -p 9876:9876 -v /opt/kresus/data:/home/user/data -ti -d kresus`
## Install on CozyCloud
If you already have a Cozy instance set up, then your best (and
[only](https://github.com/cozy/cozy-home/issues/789)) choice is to install
Kresus from the Marketplace.
# Runtime options
Note that whatever the launch process you're using (global or local install),
......@@ -114,6 +96,8 @@ You'll need the following firewall authorizations:
- http/https access to your bank website, for fetching new operations on your
behalf.
- http/https access to the Weboob repositories, for automatically updating the
bank modules before automatic pollings.
# Contributing
......@@ -126,13 +110,3 @@ A big thank you to [all contributors](https://framagit.org/bnjbvr/kresus/graphs/
There is a [code of conduct](CodeOfConduct.md) that everybody is expected to
follow. Read it for further information about how to behave, how to report
abuses, etc.
## What is Cozy?
![Cozy Logo](https://raw.github.com/cozy/cozy-setup/gh-pages/assets/images/happycloud.png)
[Cozy](http://cozy.io) is a platform that brings all your web services in the
same private space. With it, your web apps and your devices can share data
easily, providing you with a new experience. You can install Cozy on your own
hardware where no one profiles you.
#!/usr/bin/env node
var path = require('path-extra');
var ospath = require('ospath');
var path = require('path');
var fs = require('fs');
var mainDir = process.env.KRESUS_DIR ? process.env.KRESUS_DIR
: path.join(path.homedir(), '.kresus');
: path.join(ospath.home(), '.kresus');
if (!fs.existsSync(mainDir)) {
fs.mkdirSync(mainDir);
......
---
ecmaFeatures:
jsx: true
parserOptions:
ecmaFeatures:
jsx: true
plugins:
- react
- import
settings:
'import/ignore':
# We don't want to lint some modules/files
- vendor
- flux
- package.json
- ../shared/errors.json
- lodash.throttle
......@@ -17,169 +16,138 @@
globals:
"$": true
rules:
# Parent rules that are overriden.
# Conflicts with another React rule forcing several lines tags to be
# withing parenthesis.
no-extra-parens:
- 0
# specify whether double or single quotes should be used in JSX attributes
jsx-quotes:
- 2
- 'prefer-double'
# Can't enforce when enforcing a functions ordering.
no-use-before-define:
- 0
# Prevent missing displayName in a React component definition
# TODO: set to 2 when one module by file
'react/display-name':
0
# Forbid certain propTypes
'react/forbid-prop-types':
# React plugin rules.
react/display-name:
- 0
react/forbid-component-props:
- 0
react/forbid-prop-types:
- 2
- {'forbid': ['any']}
react/no-children-prop:
- 2
react/no-danger:
- 2
react/no-danger-with-children:
- 2
react/no-deprecated:
- 2
react/no-did-mount-set-state:
- 2
react/no-did-update-set-state:
- 2
react/no-direct-mutation-state:
- 2
react/no-find-dom-node:
- 0 # TODO 1
react/no-is-mounted:
- 2
react/no-multi-comp:
- 0
react/no-render-return-value:
- 2
react/no-set-state:
- 0
react/no-string-refs:
- 1 # TODO 2
react/no-unescaped-entities:
- 2
react/no-unknown-property:
- 2
react/no-unused-prop-types:
- 2
react/prefer-es6-class:
- 2
react/prefer-stateless-function:
- 2
react/prop-types:
- 1 # TODO 2
react/react-in-jsx-scope:
- 2
react/require-optimization:
- 0
react/require-render-return:
- 2
react/self-closing-comp:
- 2
react/sort-comp:
- 0
react/sort-prop-types:
- 0
react/style-prop-object:
- 2
# JSX specific rules.
# Enforce boolean attributes notation in JSX
'react/jsx-boolean-value':
react/jsx-boolean-value:
- 2
- 'always'
# Validate closing bracket location in JSX
'react/jsx-closing-bracket-location':
react/jsx-closing-bracket-location:
- 2
- {selfClosing: 'tag-aligned', nonEmpty: 'after-props'}
# Enforce or disallow spaces inside of curly braces in JSX attributes
'react/jsx-curly-spacing':
- { selfClosing: 'line-aligned', nonEmpty: 'after-props' }
react/jsx-curly-spacing:
- 2
- 'always'
# Enforce event handler naming conventions in JSX
'react/jsx-handler-names':
2
# Validate props indentation in JSX
'react/jsx-indent-props':
react/jsx-equals-spacing:
- 2
react/jsx-filename-extension:
- 0
react/jsx-first-prop-new-line:
- 2
# Validate JSX indentation
'react/jsx-indent':
- "multiline-multiprop"
react/jsx-handler-names:
- 2
react/jsx-indent:
- 2
- 4
# Validate JSX has key prop when in array or iterator
'react/jsx-key':
2
# Limit maximum of props on a single line in JSX
'react/jsx-max-props-per-line':
- 2
- {'maximum': 3}
# Prevent usage of .bind() and arrow functions in JSX props
'react/jsx-no-bind':
- 2
- {'ignoreRefs': true}
# Prevent duplicate props in JSX
'react/jsx-no-duplicate-props':
2
# Prevent usage of unwrapped JSX strings
'react/jsx-no-literals':
0
# Disallow undeclared variables in JSX
'react/jsx-no-undef':
2
# Enforce PascalCase for user-defined JSX components
'react/jsx-pascal-case':
2
# DEPRECATED Enforce quote style for JSX attributes
'react/jsx-quotes':
0
# Enforce props alphabetical sorting
'react/jsx-sort-prop-types':
0
# Sort the props by alphabetilcal order
'react/jsx-sort-props':
0
# Prevent React to be incorrectly marked as unused
'react/jsx-uses-react':
2
# Prevent variables used in JSX to be incorrectly marked as unused
'react/jsx-uses-vars':
2
# Prevent usage of dangerous JSX properties
'react/no-danger':
2
# Prevent usage of deprecated methods
'react/no-deprecated':
- 2
- 'react': '0.13.3'
# Prevent usage of setState in componentDidMount
'react/no-did-mount-set-state':
- 2
- 'allow-in-func'
# Prevent usage of setState in componentDidUpdate
'react/no-did-update-set-state':
- 2
- 'allow-in-func'
# Prevent direct mutation of this.state
'react/no-direct-mutation-state':
2
# Prevent usage of isMounted
'react/no-is-mounted':
2
# Prevent multiple component definition per file
# TODO: to update when migrating to one comp by file
'react/no-multi-comp':
0
# Prevent usage of setState
'react/no-set-state':
0
# Prevent using string references in ref attribute.
# TODO TO BE ANALYZED
'react/no-string-refs':
1
# Prevent usage of unknown DOM property
'react/no-unknown-property':
2
# Enforce ES5 or ES6 class for React Components
'react/prefer-es6-class':
2
# Prevent missing props validation in a React component definition
# TODO : Activate when prop checking is done with propTypes
'react/prop-types':
0
# Prevent missing React when using JSX
'react/react-in-jsx-scope':
2
# Restrict file extensions that may be required
'react/require-extension':
0
# Prevent extra closing tags for components without children
'react/self-closing-comp':
2
# Enforce component methods order
'react/sort-comp':
0
# Prevent missing parentheses around multilines JSX
'react/wrap-multilines':
- 2
- {declaration: true, assignment: true, return: true}
# Ensure imports point to a file/module that can be resolved.
'import/no-unresolved':
- 2
# Ensure named imports correspond to a named export in the remote file.
'import/named':
- 2
# Ensure a default export is present, given a default import:
# doesn't work when importing everything: `import * from module`
'import/default':
react/jsx-indent-props:
- 2
- 2
react/jsx-key:
- 2
react/jsx-max-props-per-line:
- 2
- { 'maximum': 1 }
react/jsx-no-bind:
- 2
react/jsx-no-comment-textnodes:
- 2
react/jsx-no-duplicate-props:
- 2
react/jsx-no-literals:
- 0
# Ensure imported namespaces contain dereferenced properties as they are dereferenced.
'import/namespace':
react/jsx-no-target-blank:
- 2
react/jsx-no-undef:
- 2
react/jsx-pascal-case:
- 2
react/jsx-sort-props:
- 0 # lolno
react/jsx-space-before-closing:
- 2
# Report any invalid exports, i.e. re-export of the same name
'import/export':
- "always"
#react/jsx-tag-spacing:
# - 2
# - { closingSlash: "never", beforeSelfClosing: "always", afterOpening: "never" }
react/jsx-uses-react:
- 2
# Report use of exported name as identifier of default export
'import/no-named-as-default':
react/jsx-uses-vars:
- 2
# No use of require
'import/no-require':
react/jsx-wrap-multilines:
- 2
# Ensure all imports appear before other statements
'import/imports-first':
import/no-unresolved:
- 2
# Report repeated import of the same module in multiple places
'import/no-duplicates':
import/namespace:
- 2
......@@ -16,15 +16,19 @@ class Budget extends React.Component {
let now = new Date();
this.state = {
month: now.getMonth(),
year: now.getFullYear()
year: now.getFullYear(),
showCatWithoutThreshold: true,
displayInPercent: false
};
this.handleChange = this.handleChange.bind(this);
this.showOperations = this.showOperations.bind(this);
this.handleToggleWithoutThreshold = this.handleToggleWithoutThreshold.bind(this);
this.handleTogglePercentDisplay = this.handleTogglePercentDisplay.bind(this);
}
handleChange() {
let period = this.refs.month.value.split('-');
handleChange(event) {
let period = event.currentTarget.value.split('-');
this.setState({
month: parseInt(period[1], 10),
......@@ -32,6 +36,18 @@ class Budget extends React.Component {
});
}
handleToggleWithoutThreshold() {
this.setState({
showCatWithoutThreshold: !this.state.showCatWithoutThreshold
});
}
handleTogglePercentDisplay() {
this.setState({
displayInPercent: !this.state.displayInPercent
});
}
showOperations(catId) {
let periodDate = { year: this.state.year, month: this.state.month };
let fromDate = moment(periodDate).toDate();
......@@ -45,19 +61,42 @@ class Budget extends React.Component {
let periodDate = { year: this.state.year, month: this.state.month };
let fromDate = moment(periodDate).toDate();
let toDate = moment(periodDate).endOf('month').toDate();
let dateFilter = op => ((op.date >= fromDate) && (op.date <= toDate));
let operations = this.props.operations.filter(dateFilter);
let categoriesToShow = this.props.categories;
if (!this.state.showCatWithoutThreshold) {
categoriesToShow = categoriesToShow.filter(cat => cat.threshold !== 0);
}
let sumAmounts = 0;
let sumThresholds = 0;
let items = categoriesToShow.map(cat => {
let catOps = operations.filter(op => cat.id === op.categoryId);
let amount = catOps.reduce((acc, op) => acc + op.amount, 0);
let items = this.props.categories.map(cat =>
<BudgetListItem
sumAmounts += amount;
sumThresholds += cat.threshold;
return (<BudgetListItem
key={ cat.id }
cat={ cat }
operations={ operations.filter(op => cat.id === op.categoryId) }
amount={ parseFloat(amount.toFixed(2)) }
updateCategory={ this.props.updateCategory }
showOperations={ this.showOperations }
/>
);
displayInPercent={ this.state.displayInPercent }
/>);
});
let remaining = '-';
if (sumAmounts) {
if (this.state.displayInPercent) {
remaining = 100 * (sumAmounts - sumThresholds) / sumThresholds;
remaining = `${remaining.toFixed(2)}%`;
} else {
remaining = (sumAmounts - sumThresholds).toFixed(2);
}
}
let currentDate = new Date();
let currentYear = currentDate.getFullYear();
......@@ -79,7 +118,9 @@ class Budget extends React.Component {
}
return (
<option value={ monthId } key={ monthId }>
<option
value={ monthId }
key={ monthId }>
{ label }
</option>
);
......@@ -95,17 +136,39 @@ class Budget extends React.Component {
</div>
<div className="panel-body">
<p>
<label className="budget_period_label">
{ $t('client.budget.period') }:
</label>
<select ref="month"
onChange={ this.handleChange }
defaultValue={ `${currentYear}-${currentMonth}` }>
{ months }
</select>
</p>
<div className="row">
<p className="col-md-4">
<label className="budget-period-label">
{ $t('client.budget.period') }:
</label>
<select
onChange={ this.handleChange }
defaultValue={ `${currentYear}-${currentMonth}` }>
{ months }
</select>
</p>
<p className="col-md-4">
<label className="budget-display-label">
{ $t('client.budget.show_categories_without_budget') }:
<input
type="checkbox"
onChange={ this.handleToggleWithoutThreshold }
checked={ this.state.showCatWithoutThreshold }
/>
</label>
</p>
<p className="col-md-4">
<label className="budget-display-label">
{ $t('client.budget.display_in_percent') }:
<input
type="checkbox"
onChange={ this.handleTogglePercentDisplay }
checked={ this.state.displayInPercent }
/>
</label>
</p>
</div>
</div>
<div className="table-responsive">
......@@ -115,20 +178,35 @@ class Budget extends React.Component {
<th className="col-sm-4 col-xs-6">
{ $t('client.category.column_category_name') }
</th>
<th className="col-sm-5 col-xs-6">
<th className="col-sm-4 col-xs-6">
{ $t('client.budget.amount') }
</th>
<th className="col-sm-1 hidden-xs">
<th className="col-sm-2 hidden-xs">
{ $t('client.budget.threshold') }
</th>
<th className="col-sm-1 hidden-xs">
{ $t('client.budget.remaining') }
{ $t('client.budget.difference') }
</th>
<th className="col-sm-1 hidden-xs">&nbsp;</th>
</tr>
</thead>
<tbody>
{ items }
<tr>
<th className="col-sm-4 col-xs-6">
{ $t('client.budget.total') }
</th>
<th className="col-sm-5 col-xs-6 text-right">
{ sumAmounts.toFixed(2) }
</th>
<th className="col-sm-1 hidden-xs text-right">
{ sumThresholds.toFixed(2) }
</th>
<th className="col-sm-1 hidden-xs text-right">
{ remaining }
</th>
<th className="col-sm-1 hidden-xs">&nbsp;</th>
</tr>
</tbody>
</table>
</div>
......
This diff is collapsed.
......@@ -13,7 +13,7 @@ class CategoryList extends React.Component {
super(props);
this.state = {
showForm: false,
showForm: false
};
this.handleShowForm = this.handleShowForm.bind(this);
......@@ -55,8 +55,8 @@ class CategoryList extends React.Component {
createCategory={ this.props.createCategory }
onCancelCreation={ this.handleShowForm }
ref={ this.refNewCategory }
/>) :
<tr/>
/>) :
<tr />
);
let buttonType = 'plus';
......@@ -78,11 +78,12 @@ class CategoryList extends React.Component {
</h3>
<div className="panel-options">
<span className={ `option-legend fa fa-${buttonType}-circle` }
<span
className={ `option-legend fa fa-${buttonType}-circle` }
aria-label={ buttonAriaLabel }
title={ $t(buttonLabel) }
onClick={ this.handleShowForm }>
</span>
onClick={ this.handleShowForm }
/>
</div>
</div>
......
......@@ -117,49 +117,65 @@ class CategoryListItem extends React.Component {
</option>);
replacementOptions = [
<option key="none" value={ NONE_CATEGORY_ID }>
<option
key="none"
value={ NONE_CATEGORY_ID }>
{ $t('client.category.dont_replace') }
</option>
].concat(replacementOptions);
let modalBody = (<div>
<div className="alert alert-info">
{ $t('client.category.erase', { title: c.title }) }
</div>
<div>
<select className="form-control" ref="replacement">
{ replacementOptions }
</select>
</div>
</div>);
let deleteButton;
let maybeModal;
if (this.isCreating()) {
deleteButton = (<span className="fa fa-times-circle"
deleteButton = (<span
className="fa fa-times-circle"
aria-label="remove"
onClick={ this.handleDelete }
title={ $t('client.general.delete') }>
</span>);
title={ $t('client.general.delete') }
/>);
} else {
deleteButton = (<span className="fa fa-times-circle"
deleteButton = (<span
className="fa fa-times-circle"
aria-label="remove"
data-toggle="modal"
data-target={ `#confirmDeleteCategory${c.id}` }
title={ $t('client.general.delete') }>
</span>);
title={ $t('client.general.delete') }
/>);
let modalBody = (<div>
<div className="alert alert-info">
{ $t('client.category.erase', { title: c.title }) }
</div>
<div>
<select
className="form-control"
ref="replacement">
{ replacementOptions }
</select>
</div>
</div>);
maybeModal = (<ConfirmDeleteModal
modalId={ `confirmDeleteCategory${c.id}` }
modalBody={ modalBody }
onDelete={ this.handleDelete }
/>);
}
return (
<tr key={ c.id }>
<td>
<ColorPicker defaultValue={ c.color }
<ColorPicker
defaultValue={ c.color }
onChange={ this.handleColorSave }
ref="color"
/>
</td>
<td>
<input type="text" className="form-control"
<input
type="text"
className="form-control"
placeholder={ $t('client.category.label') }
defaultValue={ c.title }
onKeyUp={ this.handleKeyUp }
......@@ -169,12 +185,7 @@ class CategoryListItem extends React.Component {
</td>
<td>
{ deleteButton }
<ConfirmDeleteModal
modalId={ `confirmDeleteCategory${c.id}` }
modalBody={ modalBody }
onDelete={ this.handleDelete }
/>
{ maybeModal }
</td>
</tr>
);
......
/* globals Dygraph: false */
import React from 'react';
import { debug } from '../../helpers';
import { debug, round2 } from '../../helpers';
import ChartComponent from './chart-base';
import { round2 } from './helpers';
function createChartBalance(chartId, account, operations) {
......@@ -61,6 +60,9 @@ export default class BalanceChart extends ChartComponent {
}
render() {
return <div id="barchart" style={ { width: '100%' } }></div>;
return (<div
id="barchart"
style={ { width: '100%' } }
/>);
}
}
import React from 'react';
import { connect } from 'react-redux';
import { actions, get } from '../../store';
import { assert, translate as $t } from '../../helpers';
import Modal from '../ui/modal';
import OpCatChartPeriodSelect from '../charts/operations-by-category-period-select';
import OpAmountTypeSelect from './operations-by-amount-type-select';
class DefaultParamsModal extends React.Component {
constructor(props) {
super(props);
this.handleSave = this.handleSave.bind(this);
this.handleDisplayTypeChange = this.handleDisplayTypeChange.bind(this);
this.handleAmountTypeChange = this.setState.bind(this);
this.handlePeriod = this.handlePeriodChange.bind(this);
this.displayType = this.props.displayType;
this.period = this.props.period;
this.state = {
showPositiveOps: props.showPositiveOps,
showNegativeOps: props.showNegativeOps
};
}
handleSave() {
let close = false;
if (this.state.showPositiveOps !== this.props.showPositiveOps ||
this.state.showNegativeOps !== this.props.showNegativeOps) {
this.props.setAmountType(this.state.showPositiveOps, this.state.showNegativeOps);
close = true;
}
if (this.displayType !== this.props.displayType) {
this.props.setDisplayType(this.displayType);
close = true;
}
if (this.period !== this.props.period) {
this.props.setPeriod(this.period);
close = true;
}
if (close) {
$(`#${this.props.modalId}`).modal('toggle');
}
}
handleDisplayTypeChange(event) {
this.displayType = event.target.value;
}
handlePeriodChange(event) {
this.period = event.currentTarget.value;
}
render() {
let modalBody = (<div>
<div className="form-group clearfix">
<label
className="col-xs-12 col-md-4"
htmlFor="defaultDisplayType">
{ $t('client.charts.default_display') }
</label>
<div className="col-xs-12 col-md-8">
<select
className="form-control"
id="defaultDisplayType"
onChange={ this.handleDisplayTypeChange }
defaultValue={ this.displayType }>
<option value='all'>{ $t('client.charts.by_category') }</option>
<option value='balance'>{ $t('client.charts.balance') }</option>
<option value='earnings'>{ $t('client.charts.differences_all') }</option>
</select>
</div>
</div>
<div className="form-group">
<h5 className="col-xs-12">{ $t('client.charts.category_chart') }</h5>
</div>
<div className="form-group clearfix">
<label className="col-xs-12 col-md-4">
{ $t('client.charts.default_amount_type') }
</label>
<OpAmountTypeSelect
className="col-xs-12 col-md-8"
showPositiveOps={ this.state.showPositiveOps }
showNegativeOps={ this.state.showNegativeOps }
onChange={ this.handleAmountTypeChange }
/>
</div>
<div className="form-group clearfix">
<label
htmlFor="defaultChartPeriod"
className="col-xs-12 col-md-4 control-label">
{ $t('client.charts.default_period') }
</label>
<div className="col-xs-12 col-md-8">
<OpCatChartPeriodSelect
defaultValue={ this.props.period }
onChange={ this.handlePeriod }
htmlId="defaultChartPeriod"
/>
</div>
</div>
</div>);
let modalFooter = (
<div>
<input
type="button"
className="btn btn-default"
data-dismiss="modal"
value={ $t('client.general.cancel') }
/>
<input
type="submit"
className="btn btn-success"
value={ $t('client.general.save') }
onClick={ this.handleSave }
/>
</div>
);
return (
<Modal
modalId={ this.props.modalId }
modalBody={ modalBody }
modalTitle={ $t('client.general.default_parameters') }
modalFooter={ modalFooter }
/>
);
}
}
DefaultParamsModal.propTypes = {
// Unique identifier of the modal.
modalId: React.PropTypes.string.isRequired,
// Whether to display positive operations.
showPositiveOps: React.PropTypes.bool.isRequired,
// Whether to display negative operations.
showNegativeOps: React.PropTypes.bool.isRequired,
// The function to set the default amount type.
setAmountType: React.PropTypes.func.isRequired,
// The current default chart display type.
displayType: React.PropTypes.string.isRequired,
// The function to set the default chart display type.
setDisplayType: React.PropTypes.func.isRequired,
// The current default chart period.
period: React.PropTypes.string.isRequired,
// The function to set the default chart period.
setPeriod: React.PropTypes.func.isRequired
};
const Export = connect(state => {
let amountType = get.setting(state, 'defaultChartType');
let showPositiveOps = ['all', 'positive'].includes(amountType);
let showNegativeOps = ['all', 'negative'].includes(amountType);
let displayType = get.setting(state, 'defaultChartDisplayType');
let period = get.setting(state, 'defaultChartPeriod');
return {
showPositiveOps,
showNegativeOps,
displayType,
period
};
}, dispatch => {
return {
setAmountType(showPositiveOps, showNegativeOps) {
let type = null;
if (showPositiveOps && showNegativeOps) {
type = 'all';
} else if (showPositiveOps) {
type = 'positive';
} else if (showNegativeOps) {
type = 'negative';
}
assert(type !== null);
actions.setSetting(dispatch, 'defaultChartType', type);
},
setDisplayType(val) {
actions.setSetting(dispatch, 'defaultChartDisplayType', val);
},
setPeriod(val) {
actions.setSetting(dispatch, 'defaultChartPeriod', val);
}
};
})(DefaultParamsModal);
export default Export;
export function round2(x) {
return Math.round(x * 100) / 100;
}
/* globals c3: false */
import React from 'react';
import { translate as $t } from '../../helpers';
import { translate as $t, round2 } from '../../helpers';
import ChartComponent from './chart-base';
import { round2 } from './helpers';
const CHART_SIZE = 600;
const SUBCHART_SIZE = 100;
......@@ -65,8 +64,8 @@ function createChartPositiveNegative(chartId, operations) {
// Undefined means the default locale
let defaultLocale;
let str = date.toLocaleDateString(defaultLocale, {
year: 'numeric',
month: 'long'
year: '2-digit',
month: 'short'
});
categories.push(str);
}
......@@ -96,7 +95,10 @@ function createChartPositiveNegative(chartId, operations) {
x: {
type: 'category',
extent: [lowExtent, highExtent],
categories
categories,
tick: {
fit: false
}
},
y: {
......@@ -134,6 +136,9 @@ export default class InOutChart extends ChartComponent {
}