diff --git a/.gitignore b/.gitignore
index 02d0ec0..886374b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,10 @@
/samples/
/PDFs/
/XMLs/
-user.enc
\ No newline at end of file
+/jar/
+/.idea/artifacts
+user.enc
+pass.enc
+efakturaplus.iml
+
+/.idea/
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..13566b8
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..030ece4
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+efakturaplus
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b5f7aa7
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..4c815e8
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..2b63946
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..8306744
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index a68af01..aa20490 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,7 @@
### TO-DO list:
- - [x] - Ploting both sales and purchases
- - [x] - Payment description
- - [ ] - Password check
- - [ ] - Form validation
+ - [x] Multiple types of plots
+ - [ ] Total income/outcome/tax calculation
+ - [ ] Improved PDF file display
diff --git a/efakturaplus.iml b/efakturaplus.iml
new file mode 100644
index 0000000..537afbc
--- /dev/null
+++ b/efakturaplus.iml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/include/pdf-renderer-1.0.5.1.jar b/include/pdf-renderer-1.0.5.1.jar
old mode 100755
new mode 100644
diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..1fcacc1
--- /dev/null
+++ b/src/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: efakturaplus.App
+
diff --git a/src/efakturaplus/gui/InvoiceList.java b/src/efakturaplus/gui/InvoiceList.java
index 7917b21..ec6df2d 100644
--- a/src/efakturaplus/gui/InvoiceList.java
+++ b/src/efakturaplus/gui/InvoiceList.java
@@ -37,15 +37,15 @@ public class InvoiceList extends JPanel {
private static final long serialVersionUID = 1L;
- ArrayList invoices;
+ ArrayList invoices;
private JPanel invoiceDisplay;
private GridBagLayout layout;
- public InvoiceList() {
+ public InvoiceList(ArrayList invoices) {
this.setLayout(new BorderLayout());
- this.invoices = new ArrayList<>();
+ this.invoices = invoices;
this.layout = new GridBagLayout();
this.invoiceDisplay = new JPanel(this.layout);
@@ -55,38 +55,22 @@ public InvoiceList() {
printInvoices();
}
+
+ public void printInvoices() {
+ this.invoiceDisplay.removeAll();
- public void addInvoice(Invoice invoice) {
- InvoiceListItem toRemove = null;
- for(InvoiceListItem item : invoices) {
- if(item.invoice.id.equals(invoice.id) && invoice.status.ordinal() < item.invoice.status.ordinal())
- toRemove = item;
- }
-
- if(toRemove != null)
- this.invoices.remove(toRemove);
-
- InvoiceListItem item = new InvoiceListItem(invoice);
-
- this.invoices.add(item);
-
- this.invoices.sort(new Comparator() {
-
+ this.invoices.sort(new Comparator() {
@Override
- public int compare(InvoiceListItem item1, InvoiceListItem item2) {
- return item2.invoice.deliveryDate.compareTo(item1.invoice.deliveryDate);
+ public int compare(Invoice item1, Invoice item2) {
+ return item2.deliveryDate.compareTo(item1.deliveryDate);
}
});
-
- printInvoices();
- }
-
- public void printInvoices() {
- this.invoiceDisplay.removeAll();
-
+
int n = this.invoices.size();
GridBagConstraints constr = new GridBagConstraints();
-
+
+ ArrayList items = new ArrayList();
+
if (n == 0) {
ImageIcon loading_gif = new ImageIcon("./icons/loading.gif");
JLabel loading = new JLabel("Loading ...", loading_gif, JLabel.CENTER);
@@ -99,8 +83,11 @@ public void printInvoices() {
this.invoiceDisplay.add(loading, constr);
}
+ InvoiceListItem item = null;
for(int i=0; i invoices;
-
- private Plot plot;
-
- public StatisticsPanel() {
- this.invoices = new ArrayList();
-
- this.plot = new Plot();
- }
-
- @Override
- protected void paintComponent(Graphics g) {
- super.paintComponent(g);
- Graphics2D g2 = (Graphics2D) g;
- g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-
- double DATA_WIDTH = getWidth() - 2 * BORDER_GAP;
- double DATA_HEIGHT = getHeight() - 2 * BORDER_GAP;
-
- ArrayList graphPoints = new ArrayList();
- for (int i = 0; i < plot.points.size(); i++) {
-
- int x1 = BORDER_GAP + (int)(plot.points.get(i).first * DATA_WIDTH);
- int y1 = BORDER_GAP + (int)(DATA_HEIGHT - plot.points.get(i).second * DATA_HEIGHT);
-
- graphPoints.add(new Point(x1, y1));
- }
-
- // create x and y axes
- g2.drawLine(BORDER_GAP, getHeight() - BORDER_GAP, BORDER_GAP, BORDER_GAP);
- g2.drawLine(BORDER_GAP, getHeight() - BORDER_GAP, getWidth() - BORDER_GAP, getHeight() - BORDER_GAP);
-
- // create hatch marks for y axis.
- for (int i = 0; i < graphPoints.size(); i++) {
- int x0 = BORDER_GAP;
- int x1 = GRAPH_POINT_WIDTH + BORDER_GAP;
- int y0 = graphPoints.get(i).y;
- int y1 = y0;
- g2.drawLine(x0, y0, x1, y1);
- }
-
- // and for x axis
- for (int i = 0; i < graphPoints.size(); i++) {
- int x0 = graphPoints.get(i).x;
- int x1 = x0;
- int y0 = getHeight() - BORDER_GAP;
- int y1 = y0 - GRAPH_POINT_WIDTH;
- g2.drawLine(x0, y0, x1, y1);
- }
-
-
- // Pillars
- Stroke oldStroke = g2.getStroke();
- g2.setStroke(GRAPH_STROKE);
- for (int i = 0; i < graphPoints.size() - 1; i++) {
- int x1 = graphPoints.get(i).x - 5;
- int y1 = graphPoints.get(i).y;
- int x2 = graphPoints.get(i).x + 5;
- int y2 = getHeight() - BORDER_GAP;
-
- g2.setColor(plot.colors.get(i));
- g2.fillRect(x1, y1, 10, y2-y1);
- }
-
- g2.setStroke(oldStroke);
- g2.setColor(GRAPH_POINT_COLOR);
- for (int i = 0; i < graphPoints.size(); i++) {
- int x = graphPoints.get(i).x - GRAPH_POINT_WIDTH / 2;
- int y = graphPoints.get(i).y - GRAPH_POINT_WIDTH / 2;
- ;
- int ovalW = GRAPH_POINT_WIDTH;
- int ovalH = GRAPH_POINT_WIDTH;
- g2.fillOval(x, y, ovalW, ovalH);
- }
- }
-
- @Override
- public Dimension getPreferredSize() {
- return new Dimension(PREF_W, PREF_H);
- }
-
- public void addInvoice(Invoice invoice) {
- this.invoices.add(invoice);
- }
-
- public void updatePlot() {
- this.plot.updateData(invoices);
- this.plot.makePoints();
- repaint();
- }
-}
-
-class Plot {
- private final Color PURCHASE_COLOR = new Color(0.0f, 1.0f, 0.1f, 0.2f);
- private final Color SALE_COLOR = new Color(1.0f, 0.0f, 0.1f, 0.2f);
-
- public ArrayList dates;
- public ArrayList values;
- public ArrayList colors;
-
- public ArrayList> points;
-
- public Plot() {
- this.dates = new ArrayList();
- this.values = new ArrayList();
-
- this.colors = new ArrayList();
-
- makePoints();
- }
-
- public void updateData(ArrayList invoices) {
- invoices.sort(new Comparator() {
-
- @Override
- public int compare(Invoice o1, Invoice o2) {
- return o1.deliveryDate.compareTo(o2.deliveryDate);
- }
- });
-
- this.dates = new ArrayList();
- this.values = new ArrayList();
- this.colors = new ArrayList();
-
- for(Invoice inv : invoices) {
- this.dates.add(inv.deliveryDate);
- this.values.add(inv.payableAmount);
- this.colors.add((inv.type.equals(InvoiceType.PURCHASE)) ? PURCHASE_COLOR : SALE_COLOR);
- }
- }
-
- public void makePoints() {
- this.points = new ArrayList>();
-
- if(dates.size() == 0)
- return;
-
- Date refDate = Collections.min(dates);
-
- System.out.println(values.toString());
-
- int n = dates.size();
-
- System.out.println(values.toString());
-
- double maxValue = Collections.max(values);
- double minValue = Collections.min(values);
-
- System.out.println(minValue + " < " + maxValue);
-
- long dateDiff = (new Date()).getTime() - refDate.getTime();
- dateDiff = TimeUnit.DAYS.convert(dateDiff, TimeUnit.MILLISECONDS);
-
- for (int i = 0; i < dates.size(); ++i) {
- long diff = dates.get(i).getTime() - refDate.getTime();
- diff = TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
-
- points.add(new Pair(1.0 * diff / dateDiff, values.get(i) / maxValue));
- }
-
- }
-}
diff --git a/src/efakturaplus/gui/Window.java b/src/efakturaplus/gui/Window.java
index 127db17..604094d 100644
--- a/src/efakturaplus/gui/Window.java
+++ b/src/efakturaplus/gui/Window.java
@@ -6,7 +6,8 @@
import javax.swing.JFrame;
import javax.swing.UIManager;
-import efakturaplus.models.User;
+import efakturaplus.gui.panels.KeyPanel;
+import efakturaplus.gui.panels.MainPanel;
public class Window extends JFrame {
private static final long serialVersionUID = 1L;
@@ -54,7 +55,7 @@ public Window(String title) {
public void showMainPanel() {
this.panels.show(this.getContentPane(), "MAIN_PANEL");
- this.mainPanel.printPurchaseInvoices();
+ this.mainPanel.loadUserInvoices();
}
public void showKeyPanel() {
diff --git a/src/efakturaplus/gui/InvoicePanel.java b/src/efakturaplus/gui/panels/InvoicePanel.java
similarity index 82%
rename from src/efakturaplus/gui/InvoicePanel.java
rename to src/efakturaplus/gui/panels/InvoicePanel.java
index 48166ae..7f00cec 100644
--- a/src/efakturaplus/gui/InvoicePanel.java
+++ b/src/efakturaplus/gui/panels/InvoicePanel.java
@@ -1,4 +1,4 @@
-package efakturaplus.gui;
+package efakturaplus.gui.panels;
import javax.swing.JPanel;
diff --git a/src/efakturaplus/gui/KeyPanel.java b/src/efakturaplus/gui/panels/KeyPanel.java
similarity index 72%
rename from src/efakturaplus/gui/KeyPanel.java
rename to src/efakturaplus/gui/panels/KeyPanel.java
index 7a0589d..4439352 100644
--- a/src/efakturaplus/gui/KeyPanel.java
+++ b/src/efakturaplus/gui/panels/KeyPanel.java
@@ -1,24 +1,23 @@
-package efakturaplus.gui;
+package efakturaplus.gui.panels;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
+import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
+import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
+import java.util.Arrays;
import javax.crypto.*;
import javax.crypto.spec.*;
-import javax.swing.BorderFactory;
-import javax.swing.JComponent;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JPasswordField;
-import javax.swing.JTextField;
+import javax.swing.*;
+import efakturaplus.gui.Window;
import efakturaplus.models.User;
public class KeyPanel extends JPanel {
@@ -29,7 +28,7 @@ public class KeyPanel extends JPanel {
JTextField keyInput;
JPasswordField passInput, passInput2;
- private Window parent;
+ private efakturaplus.gui.Window parent;
public KeyPanel(Window parent, int width, int height) {
this.parent = parent;
@@ -53,7 +52,7 @@ private void loadAPIKey() {
sb.append(line);
}
- User.API_KEY = sb.toString();
+ User.useApiKey(sb.toString());
br.close();
fis.close();
@@ -70,7 +69,6 @@ private void addComponents(int width, int height) {
passLabel = new JLabel("Please enter your password here:");
passLabel.setFont(font);
- passLabel.setForeground(Color.black);
passInput = new JPasswordField();
addBorder(passInput, Color.black);
@@ -84,11 +82,17 @@ public void actionPerformed(ActionEvent e) {
});
File userData = new File("user.enc");
-
+
+ JButton signInBtn = makeButton("Sign in", new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ submitData();
+ }
+ });
+
if(!userData.exists()) {
keyLabel = new JLabel("Please enter your API key here:");
keyLabel.setFont(font);
- keyLabel.setForeground(Color.black);
keyLabel.setBounds(width/2-150, height/4-75, 350, 50);
keyInput = new JTextField();
@@ -110,7 +114,6 @@ public void actionPerformed(ActionEvent e) {
passLabel2 = new JLabel("Please enter your password again:");
passLabel2.setFont(font);
- passLabel2.setForeground(Color.black);
passLabel2.setBounds(width/2-150, height*3/4-75, 350, 50);
@@ -132,11 +135,13 @@ public void actionPerformed(ActionEvent e) {
this.add(passInput, gbc(0, 3));
this.add(passLabel2, gbc(0, 4));
this.add(passInput2, gbc(0, 5));
+ this.add(signInBtn, gbc(0, 6));
}else {
passLabel.setBounds(width/2-150, height/2-75, 350, 50);
passInput.setBounds(width/2-150, height/2-25, 300, 60);
this.add(passLabel, gbc(0, 0));
this.add(passInput, gbc(0, 1));
+ this.add(signInBtn, gbc(0, 2));
}
}
@@ -149,10 +154,27 @@ private GridBagConstraints gbc(int x, int y) {
constr.insets = new Insets(10, 5, 10, 5);
constr.fill = GridBagConstraints.HORIZONTAL;
- constr.gridwidth = GridBagConstraints.REMAINDER;
+ constr.gridwidth = GridBagConstraints.CENTER;
return constr;
}
-
+
+ private JButton makeButton(String text, ActionListener listener) {
+ JButton btn = new JButton();
+
+ btn.setLayout(new BoxLayout(btn, BoxLayout.Y_AXIS));
+
+ btn.add(centeredLabel(text));
+ btn.addActionListener(listener);
+
+ return btn;
+ }
+
+ private JLabel centeredLabel(String text) {
+ JLabel label = new JLabel(text);
+ label.setAlignmentX(CENTER_ALIGNMENT);
+ return label;
+ }
+
private void addBorder(JComponent comp, Color c) {
comp.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(c, 3, true),
@@ -163,8 +185,17 @@ private void submitData() {
File userData = new File("user.enc");
if(!userData.exists()) {
- User.API_KEY = keyInput.getText();
-
+ User.useApiKey(keyInput.getText());
+
+ if(!Arrays.equals(passInput.getPassword(), passInput2.getPassword())){
+ addBorder(passInput, Color.RED);
+ addBorder(passInput2, Color.RED);
+
+ JOptionPane.showMessageDialog(null, "Passwords do not match!");
+
+ return;
+ }
+
try {
encryptData();
} catch (Exception e) {
@@ -174,11 +205,20 @@ private void submitData() {
addComponents(getWidth(), getHeight());
}
else {
+ if(!validateInput()){
+ addBorder(passInput, Color.RED);
+ passInput.setText("");
+
+ JOptionPane.showMessageDialog(null, "Incorrect password!");
+
+ return;
+ }
+
try {
String data = decryptData();
System.out.println("Decrypted: "+data);
- User.API_KEY = data;
+ User.useApiKey(data);
} catch (Exception e) {
e.printStackTrace();
}
@@ -188,14 +228,51 @@ private void submitData() {
}
private boolean validateInput() {
- boolean valid = true;
-
-
+ try {
+ FileInputStream fis = new FileInputStream("pass.enc");
+
+ String password = new String(passInput.getPassword());
+
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+ byte[] passEnc = messageDigest.digest(password.getBytes(StandardCharsets.UTF_8));
+
+ byte[] loadedPass = fis.readAllBytes();
+
+ if(Arrays.equals(passEnc, loadedPass)){
+ return true;
+ }
+
+ fis.close();
+ } catch (Exception e){
+ e.printStackTrace();
+ }
- return valid;
+ return false;
}
private void encryptData() throws NoSuchAlgorithmException, InvalidKeySpecException {
+ /* SAVING PASSWORD
+ * The password is saved as SHA-256 encoded string
+ * */
+
+ String password = passInput.getText();
+
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
+ byte[] passEnc = messageDigest.digest(password.getBytes(StandardCharsets.UTF_8));
+
+ try {
+ FileOutputStream fos = new FileOutputStream("pass.enc");
+
+ fos.write(passEnc);
+
+ fos.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ /* SAVING EFAKTURA API KEY
+ * The API key is encrypted with AES256 algorithm, and saved alongside with IV
+ * */
SecureRandom random = new SecureRandom();
byte[] salt = new byte[8];
random.nextBytes(salt);
diff --git a/src/efakturaplus/gui/MainPanel.java b/src/efakturaplus/gui/panels/MainPanel.java
similarity index 90%
rename from src/efakturaplus/gui/MainPanel.java
rename to src/efakturaplus/gui/panels/MainPanel.java
index 8733def..2c4c4d4 100644
--- a/src/efakturaplus/gui/MainPanel.java
+++ b/src/efakturaplus/gui/panels/MainPanel.java
@@ -1,4 +1,4 @@
-package efakturaplus.gui;
+package efakturaplus.gui.panels;
import java.awt.BorderLayout;
import java.awt.CardLayout;
@@ -11,11 +11,8 @@
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
-import java.time.Period;
import java.util.ArrayList;
-import java.util.Calendar;
import java.util.Collections;
-import java.util.Date;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
@@ -23,9 +20,13 @@
import javax.swing.JLabel;
import javax.swing.JPanel;
+import efakturaplus.gui.InvoiceList;
+import efakturaplus.gui.StretchIcon;
+import efakturaplus.gui.Window;
import efakturaplus.models.Invoice;
import efakturaplus.models.InvoiceStatus;
import efakturaplus.models.InvoiceType;
+import efakturaplus.models.User;
import efakturaplus.util.EFakturaUtil;
public class MainPanel extends JPanel {
@@ -57,11 +58,13 @@ public MainPanel(Window parent) {
this.dataPanel = new JPanel();
dataPanel.setLayout(dataPanelLayout);
-
- purchaseIl = new InvoiceList();
+
+ User user = User.getUser();
+
+ purchaseIl = new InvoiceList(user.purchases);
dataPanel.add(purchaseIl, "PURCHASE");
- salesIl = new InvoiceList();
+ salesIl = new InvoiceList(user.sales);
dataPanel.add(salesIl, "SALES");
statsPanel = new StatisticsPanel();
@@ -152,7 +155,10 @@ private JButton makeButton(Image ico, String text, ActionListener listener) {
JButton btn = new JButton();
btn.setLayout(new BoxLayout(btn, BoxLayout.Y_AXIS));
- btn.add(centeredLabel(new StretchIcon(ico)));
+
+ if(ico != null)
+ btn.add(centeredLabel(new StretchIcon(ico)));
+
btn.add(centeredLabel(text));
btn.addActionListener(listener);
@@ -171,7 +177,7 @@ private JLabel centeredLabel(StretchIcon ico) {
return label;
}
- public void printPurchaseInvoices() {
+ public void loadUserInvoices() {
new Thread(()->{
InvoiceStatus[] pStatusArr = {InvoiceStatus.ReNotified, InvoiceStatus.New, InvoiceStatus.Approved, InvoiceStatus.Reminded, InvoiceStatus.Seen, InvoiceStatus.Rejected};
@@ -191,23 +197,26 @@ private void displayInvoicesByStatus(InvoiceType type, InvoiceStatus[] statusArr
LocalDate today = LocalDate.now();
LocalDate from = LocalDate.now().minusMonths(3);
-
+
+ User user = User.getUser();
+
for (int i=0;i<3;++i) {
-
+
for(InvoiceStatus status : statusArr) {
ArrayList invoices = efu.getInvoices(type, status, from, from.plusMonths(1));
Collections.reverse(invoices);
for (Invoice element : invoices) {
if(type == InvoiceType.PURCHASE) {
- purchaseIl.addInvoice(element);
- statsPanel.addInvoice(element);
+ user.purchases.add(element);
}else {
- salesIl.addInvoice(element);
- statsPanel.addInvoice(element);
+ user.sales.add(element);
}
}
}
+
+ purchaseIl.printInvoices();
+ salesIl.printInvoices();
dataPanel.revalidate();
@@ -222,8 +231,7 @@ private void displayInvoicesByStatus(InvoiceType type, InvoiceStatus[] statusArr
e.printStackTrace();
}
}
-
-
+
statsPanel.updatePlot();
}
diff --git a/src/efakturaplus/gui/panels/StatisticsPanel.java b/src/efakturaplus/gui/panels/StatisticsPanel.java
new file mode 100644
index 0000000..f09be90
--- /dev/null
+++ b/src/efakturaplus/gui/panels/StatisticsPanel.java
@@ -0,0 +1,69 @@
+package efakturaplus.gui.panels;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+import javax.swing.*;
+
+import efakturaplus.gui.plots.BarPlot;
+import efakturaplus.gui.plots.MultiPlotPanel;
+import efakturaplus.gui.plots.Plot;
+import efakturaplus.gui.plots.WaterfallChart;
+import efakturaplus.models.Invoice;
+import efakturaplus.models.InvoiceType;
+import efakturaplus.models.User;
+import efakturaplus.util.Pair;
+
+public class StatisticsPanel extends JPanel {
+
+ private Plot plot;
+
+ private MultiPlotPanel plotsPanel;
+
+
+ public StatisticsPanel() {
+ this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
+ this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+
+ this.plotsPanel = new MultiPlotPanel();
+
+ this.plotsPanel.addPlot(new BarPlot(), "BAR-Plot");
+ this.plotsPanel.addPlot(new WaterfallChart(), "WATERFALL-PLOT");
+
+ this.add(plotToggleButton());
+ this.add(plotsPanel);
+ }
+
+ private JButton plotToggleButton(){
+ JButton btn = new JButton();
+ btn.setAlignmentX(RIGHT_ALIGNMENT);
+
+ btn.addActionListener(e -> {
+ plotsPanel.switchPlot();
+ });
+
+ JLabel lbl = new JLabel("Switch plot type");
+ lbl.setAlignmentX(CENTER_ALIGNMENT);
+ lbl.setAlignmentY(CENTER_ALIGNMENT);
+ lbl.setBorder(BorderFactory.createEmptyBorder(7, 7, 7, 7));
+
+ btn.add(lbl);
+
+ return btn;
+ }
+
+ public void updatePlot() {
+ User user = User.getUser();
+
+ ArrayList data = new ArrayList<>();
+ data.addAll(user.purchases);
+ data.addAll(user.sales);
+
+ this.plotsPanel.updatePlots(data);
+ repaint();
+ }
+}
\ No newline at end of file
diff --git a/src/efakturaplus/gui/plots/BarPlot.java b/src/efakturaplus/gui/plots/BarPlot.java
new file mode 100644
index 0000000..1de3af6
--- /dev/null
+++ b/src/efakturaplus/gui/plots/BarPlot.java
@@ -0,0 +1,97 @@
+package efakturaplus.gui.plots;
+
+import efakturaplus.models.Invoice;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+public class BarPlot extends Plot{
+
+ public BarPlot(){
+ super();
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ Graphics2D g2 = (Graphics2D) g;
+
+ // create x and y axes
+ g2.drawLine(BORDER_GAP, getHeight() - BORDER_GAP, BORDER_GAP, BORDER_GAP);
+ g2.drawLine(BORDER_GAP, getHeight() - BORDER_GAP, getWidth() - BORDER_GAP, getHeight() - BORDER_GAP);
+
+ System.out.println("Rendering " + items.size() + " items ....");
+
+ // Pravougaonici
+ for(PlotItem plotItem : items){
+ g.setColor(plotItem.color);
+ g.fillRect(BORDER_GAP + plotItem.x - plotItem.width/2, BORDER_GAP + plotItem.y, plotItem.width, plotItem.height);
+ }
+
+ // Crtice
+ for (PlotItem item : items) {
+ g.setColor(Color.BLACK);
+ int x0 = BORDER_GAP + item.x;
+ int x1 = x0;
+ int y0 = getHeight() - BORDER_GAP - GRAPH_POINT_WIDTH;
+ int y1 = y0 + 2 * GRAPH_POINT_WIDTH;
+ g2.drawLine(x0, y0, x1, y1);
+ }
+
+ int a = 5;
+ }
+
+ @Override
+ void makeItems(ArrayList invoices) {
+ this.items = new ArrayList<>();
+
+ invoices.sort(new Comparator() {
+ @Override
+ public int compare(Invoice o1, Invoice o2) {
+ return o1.deliveryDate.compareTo(o2.deliveryDate);
+ }
+ });
+
+ double DATA_WIDTH = getWidth() - 2 * BORDER_GAP;
+ double DATA_HEIGHT = getHeight() - 2 * BORDER_GAP;
+
+ LocalDateTime today = LocalDate.now().atStartOfDay();
+ LocalDateTime from = LocalDate.now().minusMonths(3).atStartOfDay();
+
+ double maxAmount = -1;
+
+ for(Invoice inv : invoices)
+ maxAmount = Math.max(maxAmount, inv.payableAmount);
+
+ for(Invoice inv: invoices){
+ PlotItem item = new PlotItem(inv);
+
+ long daysToItem = Duration.between(from, inv.deliveryDate).toDays();
+ long totalDays = Duration.between(from, today).toDays();
+
+ item.x = (int) (DATA_WIDTH * daysToItem / totalDays);
+ item.y = (int) ((1 - 0.9 * inv.payableAmount / maxAmount) * DATA_HEIGHT);
+ item.width = GRAPH_POINT_WIDTH;
+ item.height = (int) DATA_HEIGHT - item.y;
+
+ this.items.add(item);
+ }
+ }
+
+ @Override
+ protected PlotItem isItemFocused(int x, int y) {
+ for(PlotItem item : items){
+ if(x >= item.x - item.width && x <= item.x + item.width
+ && y >= item.y && y <= item.y + item.height){
+ return item;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/efakturaplus/gui/plots/MultiPlotPanel.java b/src/efakturaplus/gui/plots/MultiPlotPanel.java
new file mode 100644
index 0000000..0342d79
--- /dev/null
+++ b/src/efakturaplus/gui/plots/MultiPlotPanel.java
@@ -0,0 +1,48 @@
+package efakturaplus.gui.plots;
+
+import efakturaplus.models.Invoice;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+
+public class MultiPlotPanel extends JPanel {
+ private CardLayout layout;
+ private ArrayList plots;
+
+ public MultiPlotPanel() {
+ this(800, 700);
+ }
+
+ public MultiPlotPanel(int prefWidth, int prefHeight) {
+ super();
+ this.setPreferredSize(new Dimension(prefWidth, prefHeight));
+
+ this.layout = new CardLayout();
+ this.setLayout(this.layout);
+ this.plots = new ArrayList<>();
+ }
+
+ public void addPlot(Plot plot, Object constraint){
+ this.add(plot, constraint);
+
+ if( !this.plots.isEmpty() )
+ this.layout.show(this, constraint.toString());
+
+ this.plots.add(plot);
+ }
+
+ public void switchPlot(){
+ this.layout.next(this);
+ }
+
+ public void switchPlot(String name){
+ this.layout.show(this, name);
+ }
+
+ public void updatePlots(ArrayList invoices){
+ for(Plot plot : this.plots){
+ plot.updateData(invoices);
+ }
+ }
+}
diff --git a/src/efakturaplus/gui/plots/Plot.java b/src/efakturaplus/gui/plots/Plot.java
new file mode 100644
index 0000000..a9106cb
--- /dev/null
+++ b/src/efakturaplus/gui/plots/Plot.java
@@ -0,0 +1,76 @@
+package efakturaplus.gui.plots;
+
+import efakturaplus.models.Invoice;
+import efakturaplus.models.InvoiceType;
+import efakturaplus.util.Pair;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.concurrent.TimeUnit;
+
+public abstract class Plot extends JComponent implements MouseListener{
+
+ protected int PREF_W = 800;
+ protected int PREF_H = 650;
+ protected int BORDER_GAP = 30;
+ protected Color GRAPH_POINT_COLOR = new Color(150, 50, 50, 180);
+ protected Stroke GRAPH_STROKE = new BasicStroke(3f);
+ protected int GRAPH_POINT_WIDTH = 5;
+
+ protected ArrayList items;
+
+ public Plot() {
+ this.items = new ArrayList<>();
+ addMouseListener(this);
+ }
+
+ public void updateData(ArrayList invoices) {
+ makeItems(invoices);
+
+ repaint();
+ }
+
+ abstract void makeItems(ArrayList invoices);
+
+ protected abstract PlotItem isItemFocused(int x, int y);
+
+ @Override
+ public void mouseClicked(MouseEvent e) {
+
+ }
+
+ @Override
+ public void mousePressed(MouseEvent e) {
+
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ PlotItem item = isItemFocused(e.getX() - BORDER_GAP, e.getY() - BORDER_GAP);
+ if(item != null){
+ System.out.println(item.getLabel());
+ }
+ }
+
+ @Override
+ public void mouseEntered(MouseEvent e) {
+
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e) {
+
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ return new Dimension(PREF_W, PREF_H);
+ }
+}
diff --git a/src/efakturaplus/gui/plots/PlotItem.java b/src/efakturaplus/gui/plots/PlotItem.java
new file mode 100644
index 0000000..fe9768b
--- /dev/null
+++ b/src/efakturaplus/gui/plots/PlotItem.java
@@ -0,0 +1,42 @@
+package efakturaplus.gui.plots;
+
+import efakturaplus.models.Invoice;
+import efakturaplus.models.InvoiceType;
+
+import java.awt.*;
+
+public class PlotItem {
+ protected static final Color PURCHASE_COLOR = new Color(1.0f, 0.0f, 0.1f);
+ protected static final Color SALE_COLOR = new Color(0.0f, 1.0f, 0.1f);
+
+ public Invoice invoiceRef;
+
+ public int x,y;
+ public int width,height;
+ public Color color;
+
+ public PlotItem(Invoice invoiceRef){
+ this(invoiceRef,0,0,0, 0);
+ }
+
+ public PlotItem(Invoice invoiceRef, int x, int y){
+ this(invoiceRef,x,y,0,0);
+ }
+
+ public PlotItem(Invoice invoiceRef, int x, int y, int width, int height){
+ this.invoiceRef = invoiceRef;
+ this.color = invoiceRef.type == InvoiceType.PURCHASE ? PURCHASE_COLOR : SALE_COLOR;
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+ }
+
+ public double getValue(){
+ return invoiceRef.payableAmount;
+ }
+
+ public String getLabel(){
+ return (invoiceRef.type == InvoiceType.PURCHASE) ? invoiceRef.supplier.name : invoiceRef.customer.name;
+ }
+}
diff --git a/src/efakturaplus/gui/plots/WaterfallChart.java b/src/efakturaplus/gui/plots/WaterfallChart.java
new file mode 100644
index 0000000..b5f693e
--- /dev/null
+++ b/src/efakturaplus/gui/plots/WaterfallChart.java
@@ -0,0 +1,106 @@
+package efakturaplus.gui.plots;
+
+import efakturaplus.models.Invoice;
+import efakturaplus.models.InvoiceType;
+
+import javax.xml.crypto.Data;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Comparator;
+
+public class WaterfallChart extends Plot{
+ public WaterfallChart(){
+ super();
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ Graphics2D g2 = (Graphics2D) g;
+
+ // create x and y axes
+ g2.drawLine(BORDER_GAP, getHeight() - BORDER_GAP, BORDER_GAP, BORDER_GAP);
+ g2.drawLine(BORDER_GAP, getHeight() - BORDER_GAP, getWidth() - BORDER_GAP, getHeight() - BORDER_GAP);
+
+ System.out.println("Rendering " + items.size() + " items ....");
+
+ // Pravougaonici
+ for(PlotItem plotItem : items){
+ g.setColor(plotItem.color);
+ g.fillRect(BORDER_GAP + plotItem.x, BORDER_GAP + plotItem.y, plotItem.width, plotItem.height);
+ }
+
+ // Crtice
+ for (PlotItem item : items) {
+ g.setColor(Color.BLACK);
+ int x0 = BORDER_GAP + item.x + item.width/2;
+ int x1 = x0;
+ int y0 = getHeight() - BORDER_GAP - GRAPH_POINT_WIDTH;
+ int y1 = y0 + 2 * GRAPH_POINT_WIDTH;
+ g2.drawLine(x0, y0, x1, y1);
+ }
+ }
+
+ @Override
+ void makeItems(ArrayList invoices) {
+ this.items = new ArrayList<>();
+
+ invoices.sort(new Comparator() {
+ @Override
+ public int compare(Invoice o1, Invoice o2) {
+ return o1.deliveryDate.compareTo(o2.deliveryDate);
+ }
+ });
+
+ double DATA_WIDTH = getWidth() - 2 * BORDER_GAP;
+ double DATA_HEIGHT = getHeight() - 2 * BORDER_GAP;
+
+ int itemWidth = (int) DATA_WIDTH / invoices.size();
+
+ double minValue = 0;
+ double maxValue = 0;
+ double currentValue = 0;
+
+ for(Invoice invoice : invoices){
+ currentValue += invoice.payableAmount * ((invoice.type == InvoiceType.PURCHASE) ? -1 : 1);
+
+ maxValue = Math.max(currentValue, maxValue);
+ minValue = Math.min(currentValue, minValue);
+ }
+
+ double totalValueDiff = maxValue - minValue;
+ double heightOffset = Math.abs(minValue) * DATA_HEIGHT / totalValueDiff ;
+
+ double currentHeight = DATA_HEIGHT - heightOffset;
+
+ for(int i=0; i < invoices.size(); ++i){
+ PlotItem item = new PlotItem(invoices.get(i));
+
+ item.width = itemWidth;
+ item.height = (int) (0.9 * DATA_HEIGHT * invoices.get(i).payableAmount / totalValueDiff);
+ item.x = i * itemWidth;
+
+ if(invoices.get(i).type == InvoiceType.SALES){
+ currentHeight -= item.height;
+ item.y = (int) (currentHeight);
+ }else{
+ item.y = (int) (currentHeight);
+ currentHeight += item.height;
+ }
+
+ System.out.println("Item: ("+item.x+", "+item.y+", "+item.width+", "+item.height+")");
+ this.items.add(item);
+ }
+ }
+
+ @Override
+ protected PlotItem isItemFocused(int x, int y) {
+ for(PlotItem item : items){
+ if(x >= item.x && x <= item.x + item.width
+ && y >= item.y && y <= item.y + item.height){
+ return item;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/efakturaplus/models/Invoice.java b/src/efakturaplus/models/Invoice.java
index f07b9e2..3138bb9 100644
--- a/src/efakturaplus/models/Invoice.java
+++ b/src/efakturaplus/models/Invoice.java
@@ -7,6 +7,9 @@
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
@@ -30,7 +33,7 @@ public class Invoice {
public Party customer;
public Party supplier;
- public Date deliveryDate;
+ public LocalDateTime deliveryDate;
public String paymentMod;
public String paymentId;
@@ -89,10 +92,10 @@ private void parse(Document doc) throws DOMException, ParseException, IOExceptio
/*
* DATE PARSING
*/
- DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
Node dateNode = doc.getElementsByTagName("cbc:IssueDate").item(0);
- this.deliveryDate = format.parse(dateNode.getTextContent());
+ this.deliveryDate = LocalDate.parse(dateNode.getTextContent(), dtf).atStartOfDay();
/*
* PAYMENT ID PARSING
@@ -224,14 +227,14 @@ public String toString() {
}
public String getDateString() {
- DateFormat format = new SimpleDateFormat("dd.MM.yyyy");
+ DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd.MM.yyyy");
if(this.deliveryDate != null)
- return format.format(this.deliveryDate);
- return format.format(new Date());
+ return deliveryDate.format(dtf);
+ return LocalDateTime.now().format(dtf);
}
- public static Date getDate(Invoice inv) {
+ public static LocalDateTime getDate(Invoice inv) {
return inv.deliveryDate;
}
diff --git a/src/efakturaplus/models/User.java b/src/efakturaplus/models/User.java
index 952518e..6df711e 100644
--- a/src/efakturaplus/models/User.java
+++ b/src/efakturaplus/models/User.java
@@ -1,5 +1,31 @@
package efakturaplus.models;
+import java.util.ArrayList;
+
public class User {
- public static String API_KEY = "";
+ private static User instance;
+
+ public String API_KEY = "";
+ public ArrayList purchases;
+ public ArrayList sales;
+
+ private User(){
+ purchases = new ArrayList<>();
+ sales = new ArrayList<>();
+ }
+
+ public static User getUser(){
+ if(instance == null){
+ instance = new User();
+ }
+ return instance;
+ }
+
+ public static void useApiKey(String apiKey){
+ User user = getUser();
+ user.API_KEY = apiKey;
+ user.purchases.clear();
+ user.sales.clear();
+ }
+
}
diff --git a/src/efakturaplus/util/EFakturaUtil.java b/src/efakturaplus/util/EFakturaUtil.java
index d129b83..769d42f 100644
--- a/src/efakturaplus/util/EFakturaUtil.java
+++ b/src/efakturaplus/util/EFakturaUtil.java
@@ -45,7 +45,7 @@ private EFakturaUtil(String API_KEY) {
}
public static EFakturaUtil getInstance() {
- return new EFakturaUtil(User.API_KEY);
+ return new EFakturaUtil(User.getUser().API_KEY);
}
private ArrayList getIdsFromResponse(InvoiceType type, HttpResponse response){
@@ -77,7 +77,7 @@ private synchronized HttpResponse sendRequest(HttpRequest request) {
HttpClient client = HttpClient.newHttpClient();
try {
// GetInvoiceRequest
- Thread.sleep(500);
+ Thread.sleep(1000);
HttpResponse res = client.send(request, HttpResponse.BodyHandlers.ofString());
return res;