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;