浏览代码

moved independent classes and renamed states.

Tomi Cvetic 7 年之前
父节点
当前提交
395d240604

+ 664 - 0
package-lock.json

@@ -853,6 +853,12 @@
       "integrity": "sha1-s7026T3Uy/s5WjwmiWNSRFJlwFs=",
       "dev": true
     },
+    "classnames": {
+      "version": "2.2.5",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
+      "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0=",
+      "dev": true
+    },
     "clean-css": {
       "version": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.3.tgz",
       "integrity": "sha1-B8/omA7bINRV3cI6rc8eBMblCc4=",
@@ -1133,6 +1139,20 @@
       "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=",
       "dev": true
     },
+    "cssauron": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
+      "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
+      "dev": true,
+      "dependencies": {
+        "through": {
+          "version": "2.3.8",
+          "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+          "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+          "dev": true
+        }
+      }
+    },
     "cssesc": {
       "version": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
       "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
@@ -1266,6 +1286,12 @@
         }
       }
     },
+    "dom-helpers": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.2.1.tgz",
+      "integrity": "sha1-MgPgf+0he9H0JLAZc1WC/Deyglo=",
+      "dev": true
+    },
     "dom-serializer": {
       "version": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
       "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
@@ -2371,11 +2397,149 @@
       "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=",
       "dev": true
     },
+    "html-inline": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/html-inline/-/html-inline-1.2.0.tgz",
+      "integrity": "sha1-eFSUam9cMSK5k7gdPTfWvQYAvsE=",
+      "dev": true,
+      "dependencies": {
+        "minimist": {
+          "version": "1.1.3",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz",
+          "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=",
+          "dev": true
+        }
+      }
+    },
     "html-minifier": {
       "version": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.2.tgz",
       "integrity": "sha1-1zvD/0SJQkCIGM5gm/P7DqfvTrc=",
       "dev": true
     },
+    "html-select": {
+      "version": "2.3.24",
+      "resolved": "https://registry.npmjs.org/html-select/-/html-select-2.3.24.tgz",
+      "integrity": "sha1-Rq1tcS5zLPMcZznV0BEKX6vxdYU=",
+      "dev": true,
+      "dependencies": {
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "duplexer2": {
+          "version": "0.0.2",
+          "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
+          "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "minimist": {
+          "version": "0.0.10",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+          "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        },
+        "through2": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz",
+          "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=",
+          "dev": true
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+          "dev": true
+        }
+      }
+    },
+    "html-tokenize": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/html-tokenize/-/html-tokenize-1.2.5.tgz",
+      "integrity": "sha1-flupnstR75Buyaf83ubKMmfHiX4=",
+      "dev": true,
+      "dependencies": {
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "minimist": {
+          "version": "0.0.10",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+          "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
+          "dev": true
+        },
+        "object-keys": {
+          "version": "0.4.0",
+          "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz",
+          "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        },
+        "through2": {
+          "version": "0.4.2",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz",
+          "integrity": "sha1-2/WGYDEVHsg1K7bE22SiKSqEC5s=",
+          "dev": true
+        },
+        "xtend": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz",
+          "integrity": "sha1-bv7MKk2tjmlixJAbM3znuoe10os=",
+          "dev": true
+        }
+      }
+    },
     "html-webpack-plugin": {
       "version": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-2.28.0.tgz",
       "integrity": "sha1-LnhjtX5f1I/iYzA+L/yTTDBk0Ak=",
@@ -2799,6 +2963,12 @@
       "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=",
       "dev": true
     },
+    "keycode": {
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.1.9.tgz",
+      "integrity": "sha1-lkojxU5IiUBbSGGlyfBIDUUUHfo=",
+      "dev": true
+    },
     "kind-of": {
       "version": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
       "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
@@ -3841,6 +4011,136 @@
       "version": "https://registry.npmjs.org/react/-/react-15.5.4.tgz",
       "integrity": "sha1-+oPrAVBqsjfNwcjDsc6o3gEr8Ec="
     },
+    "react-bootstrap": {
+      "version": "0.31.0",
+      "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-0.31.0.tgz",
+      "integrity": "sha1-u8qATAQE2cZAECsrZWrkzVvqNcg=",
+      "dev": true,
+      "dependencies": {
+        "asap": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz",
+          "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=",
+          "dev": true
+        },
+        "babel-runtime": {
+          "version": "6.23.0",
+          "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
+          "integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs=",
+          "dev": true
+        },
+        "core-js": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
+          "integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4=",
+          "dev": true
+        },
+        "encoding": {
+          "version": "0.1.12",
+          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+          "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+          "dev": true
+        },
+        "fbjs": {
+          "version": "0.8.12",
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz",
+          "integrity": "sha1-ELXZL3bUVXX9Y6IX1OoCvqL47QQ=",
+          "dev": true,
+          "dependencies": {
+            "core-js": {
+              "version": "1.2.7",
+              "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+              "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
+              "dev": true
+            }
+          }
+        },
+        "iconv-lite": {
+          "version": "0.4.18",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz",
+          "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==",
+          "dev": true
+        },
+        "invariant": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+          "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+          "dev": true
+        },
+        "is-stream": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+          "dev": true
+        },
+        "isomorphic-fetch": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+          "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+          "dev": true
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "dev": true
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "dev": true
+        },
+        "node-fetch": {
+          "version": "1.7.1",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz",
+          "integrity": "sha512-j8XsFGCLw79vWXkZtMSmmLaOk9z5SQ9bV/tkbZVCqvgwzrjAGq66igobLofHtF63NvMTp2WjytpsNTGKa+XRIQ==",
+          "dev": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "dev": true
+        },
+        "promise": {
+          "version": "7.3.1",
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+          "dev": true
+        },
+        "prop-types": {
+          "version": "15.5.10",
+          "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz",
+          "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=",
+          "dev": true
+        },
+        "regenerator-runtime": {
+          "version": "0.10.5",
+          "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
+          "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=",
+          "dev": true
+        },
+        "setimmediate": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+          "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+          "dev": true
+        },
+        "ua-parser-js": {
+          "version": "0.7.13",
+          "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.13.tgz",
+          "integrity": "sha1-zZ3S+GSTs/RNvu7zeA/adMXuFL4=",
+          "dev": true
+        },
+        "whatwg-fetch": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
+          "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=",
+          "dev": true
+        }
+      }
+    },
     "react-dev-utils": {
       "version": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-3.0.0.tgz",
       "integrity": "sha1-Nnfzdxi6DK6JK6nAH+VNFiLm73w=",
@@ -3909,6 +4209,116 @@
         }
       }
     },
+    "react-overlays": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-0.7.0.tgz",
+      "integrity": "sha1-UxiY/1ZsflxyJurShjuM+fu1qYE=",
+      "dev": true,
+      "dependencies": {
+        "asap": {
+          "version": "2.0.5",
+          "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz",
+          "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=",
+          "dev": true
+        },
+        "core-js": {
+          "version": "1.2.7",
+          "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
+          "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
+          "dev": true
+        },
+        "encoding": {
+          "version": "0.1.12",
+          "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+          "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+          "dev": true
+        },
+        "fbjs": {
+          "version": "0.8.12",
+          "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.12.tgz",
+          "integrity": "sha1-ELXZL3bUVXX9Y6IX1OoCvqL47QQ=",
+          "dev": true
+        },
+        "iconv-lite": {
+          "version": "0.4.18",
+          "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz",
+          "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==",
+          "dev": true
+        },
+        "is-stream": {
+          "version": "1.1.0",
+          "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+          "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+          "dev": true
+        },
+        "isomorphic-fetch": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+          "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+          "dev": true
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "dev": true
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "dev": true
+        },
+        "node-fetch": {
+          "version": "1.7.1",
+          "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.1.tgz",
+          "integrity": "sha512-j8XsFGCLw79vWXkZtMSmmLaOk9z5SQ9bV/tkbZVCqvgwzrjAGq66igobLofHtF63NvMTp2WjytpsNTGKa+XRIQ==",
+          "dev": true
+        },
+        "object-assign": {
+          "version": "4.1.1",
+          "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+          "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+          "dev": true
+        },
+        "promise": {
+          "version": "7.3.1",
+          "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+          "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+          "dev": true
+        },
+        "prop-types": {
+          "version": "15.5.10",
+          "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz",
+          "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=",
+          "dev": true
+        },
+        "setimmediate": {
+          "version": "1.0.5",
+          "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+          "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+          "dev": true
+        },
+        "ua-parser-js": {
+          "version": "0.7.13",
+          "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.13.tgz",
+          "integrity": "sha1-zZ3S+GSTs/RNvu7zeA/adMXuFL4=",
+          "dev": true
+        },
+        "whatwg-fetch": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz",
+          "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=",
+          "dev": true
+        }
+      }
+    },
+    "react-prop-types": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/react-prop-types/-/react-prop-types-0.4.0.tgz",
+      "integrity": "sha1-+ZsL+0AGkpya8gUefBQUpcdbk9A=",
+      "dev": true
+    },
     "react-scripts": {
       "version": "https://registry.npmjs.org/react-scripts/-/react-scripts-1.0.7.tgz",
       "integrity": "sha1-/hQ23aA7tFRlx20JfP6k8y63y7s=",
@@ -3946,6 +4356,44 @@
       "integrity": "sha1-B5azH412iAB/8Lk6gIjTSqF8D3I=",
       "dev": true
     },
+    "readable-wrap": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/readable-wrap/-/readable-wrap-1.0.0.tgz",
+      "integrity": "sha1-O1ohHGMeEjA6VJkcgGwX564ga/8=",
+      "dev": true,
+      "dependencies": {
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        }
+      }
+    },
     "readdirp": {
       "version": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
       "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
@@ -4326,6 +4774,20 @@
       "integrity": "sha1-c15yBUxIayNU/onnAiVgBKOazk0=",
       "dev": true
     },
+    "split": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz",
+      "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=",
+      "dev": true,
+      "dependencies": {
+        "through": {
+          "version": "2.3.8",
+          "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+          "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+          "dev": true
+        }
+      }
+    },
     "sprintf-js": {
       "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
       "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
@@ -4350,6 +4812,62 @@
       "integrity": "sha1-VGpRdBrVprB+njGwsQRBqRffUoo=",
       "dev": true
     },
+    "stream-splicer": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-1.3.2.tgz",
+      "integrity": "sha1-PARBvhW5v04iYnXm3IOWR0VUZmE=",
+      "dev": true,
+      "dependencies": {
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "indexof": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+          "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        },
+        "through2": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz",
+          "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=",
+          "dev": true
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+          "dev": true
+        }
+      }
+    },
     "strict-uri-encode": {
       "version": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
       "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
@@ -4469,6 +4987,50 @@
       "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
       "dev": true
     },
+    "through2": {
+      "version": "0.6.5",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz",
+      "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=",
+      "dev": true,
+      "dependencies": {
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.0.34",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+          "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+          "dev": true
+        }
+      }
+    },
     "timed-out": {
       "version": "https://registry.npmjs.org/timed-out/-/timed-out-3.1.3.tgz",
       "integrity": "sha1-lYYL/MXHbCd/j4Mm/Q9bLiDrohc=",
@@ -4504,6 +5066,62 @@
       "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
       "dev": true
     },
+    "trumpet": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/trumpet/-/trumpet-1.7.2.tgz",
+      "integrity": "sha1-sCxp5GXRcfVeRJJL+bW90gl0yDA=",
+      "dev": true,
+      "dependencies": {
+        "core-util-is": {
+          "version": "1.0.2",
+          "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+          "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+          "dev": true
+        },
+        "duplexer2": {
+          "version": "0.0.2",
+          "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz",
+          "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=",
+          "dev": true
+        },
+        "inherits": {
+          "version": "2.0.3",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+          "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+          "dev": true
+        },
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+          "dev": true
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+          "dev": true
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+          "dev": true
+        },
+        "through2": {
+          "version": "1.1.1",
+          "resolved": "https://registry.npmjs.org/through2/-/through2-1.1.1.tgz",
+          "integrity": "sha1-CEfLxESfNAVXTb3M2buEG4OsNUU=",
+          "dev": true
+        },
+        "xtend": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+          "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+          "dev": true
+        }
+      }
+    },
     "tryit": {
       "version": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz",
       "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=",
@@ -4538,6 +5156,32 @@
       "integrity": "sha1-qssyOoRrI0YCJw3q2KMkQaiAb0I=",
       "dev": true
     },
+    "uncontrollable": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-4.1.0.tgz",
+      "integrity": "sha1-4DWCkSUuGGUiLZCTmxny9J+Bwak=",
+      "dev": true,
+      "dependencies": {
+        "invariant": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz",
+          "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=",
+          "dev": true
+        },
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "dev": true
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "dev": true
+        }
+      }
+    },
     "uniq": {
       "version": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz",
       "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=",
@@ -4669,6 +5313,26 @@
       "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
       "dev": true
     },
+    "warning": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
+      "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
+      "dev": true,
+      "dependencies": {
+        "js-tokens": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+          "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+          "dev": true
+        },
+        "loose-envify": {
+          "version": "1.3.1",
+          "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
+          "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=",
+          "dev": true
+        }
+      }
+    },
     "watchpack": {
       "version": "https://registry.npmjs.org/watchpack/-/watchpack-1.3.1.tgz",
       "integrity": "sha1-fYaTkHsozmAT5/NhCqKhrPB9rYc=",

+ 1 - 0
package.json

@@ -21,6 +21,7 @@
   "scripts": {
     "start": "react-scripts start",
     "build": "react-scripts build",
+    "inline": "node node_modules/html-inline/bin/cmd.js build/index.html -o build/index_inline.html",
     "test": "react-scripts test --env=jsdom",
     "eject": "react-scripts eject"
   }

+ 0 - 373
src/App.js

@@ -1,373 +0,0 @@
-import React from 'react'           // React to manage the GUI
-import Player from './player'       // Everything that has to do with players
-import Match from './match'         // Everything that has to do with matches
-import Excel from './excel'         // Helper files to create Excel files
-import { date2s, time2s } from './helpers'
-import 'bootstrap/dist/css/bootstrap.css'
-
-/**
- * General Application Design
- *
- * 4 Components:
- * - PlayerList (representing the PlayerList Excel file)
- * - Calendar (representing the Calendar Excel file)
- * - MatchList (representing the Spielliste Excel file)
- * - PaymentList (representing the Zahlliste Excel file)
- *
- * PlayerList
- * - Shows the relevant information from the file
- * - Shows calculated information from combination with Calendar
- * - Points out potential problems
- * - Allows access to player information
- *
- * Calendar
- * - Shows the relevant information from the file
- * - Shows calculated information from combination with PlayerList
- * - Points out potential problems
- * - Allows access to match information
- *
- * MatchList
- * - Shows the calculated match lists
- * - Points out problems
- *
- * PaymentList
- * - Shows the calculated payment lists
- * - Points out problems
- *
- */
-
-const FILTER_OFF = 'Alle'
-
-/** Main application */
-class App extends React.Component {
-  /**
-   * Constructor
-   * Defines the state and binds all 'this' in all functions to the object.
-   */
-  constructor () {
-    super()
-
-    // Define the state
-    this.state = {
-      player: Player.state,
-      match: Match.state,
-      worksheets: {
-        'PlayerList': null,
-        'Calendar': null
-      },
-      activeTab: 1
-    }
-
-    // Bind 'this' to functions
-    this.handlePlayerList = this.handlePlayerList.bind(this)
-    this.handleCalendar = this.handleCalendar.bind(this)
-    this.generatePlayerList = this.generatePlayerList.bind(this)
-    this.generateCalendar = this.generateCalendar.bind(this)
-    this.filterMatches = this.filterMatches.bind(this)
-    this.filterPlayers = this.filterPlayers.bind(this)
-    this.generateSchedule = this.generateSchedule.bind(this)
-    this.generatePayTable = this.generatePayTable.bind(this)
-  }
-
-  calculateMatchFilters () {
-    const dates = {}
-    const places = []
-    const categories = []
-    this.state.match.matches.forEach((item) => {
-      const dateString = date2s(item.Datum)
-      if (!!item.Datum & !dates.hasOwnProperty(dateString)) {
-        dates[dateString] = item.Datum
-      }
-      if (!!item.Ort & !places.includes(item.Ort)) {
-        places.push(item.Ort)
-      }
-      if (!!item.Konkurrenz & !categories.includes(item.Konkurrenz)) {
-        categories.push(item.Konkurrenz)
-      }
-    })
-    const match = { ...this.state.match, dates, places, categories }
-    this.setState({ match })
-  }
-
-  calculatePlayerFilters () {
-    const categories = []
-    this.state.player.players.forEach((item) => {
-      if (!!item.Konkurrenz & !categories.includes(item.Konkurrenz)) {
-        categories.push(item.Ort)
-      }
-    })
-    const player = { ...this.state.player, categories }
-    this.setState({ player })
-  }
-
-  handlePlayerList (event) {
-    const file = this.playerList.files[0]
-    Excel.readWorkbook(file, this.generatePlayerList)
-  }
-
-  handleCalendar (event) {
-    const file = this.calendar.files[0]
-    Excel.readWorkbook(file, this.generateCalendar)
-  }
-
-  generatePlayerList (worksheet) {
-    console.log('About to read the player list.')
-    const worksheets = { ...this.state.worksheets }
-    worksheets['PlayerList'] = worksheet
-    this.setState({ worksheets })
-
-    if (worksheet[4].length !== 32 & worksheet[3][0] !== 'Konkurrenz' & worksheet[3][31] !== 'bezahlt') {
-      throw Error('Wrong file structure.')
-    }
-    const players = worksheet.slice(4, worksheet.length).map((playerData) => new Player.Player(playerData))
-    const player = { ...this.state.player }
-    player.players = players
-    this.setState({ player })
-    this.calculatePayDate()
-    this.filterPlayers()
-    console.log('State after generating player list:', this.state)
-  }
-
-  generateCalendar (worksheet) {
-    console.log('About to read the calendar.')
-    const worksheets = { ...this.state.worksheets }
-    worksheets['Calendar'] = worksheet
-    this.setState({ worksheets })
-
-    if (worksheet[2].length < 8 | worksheet[2].length > 9) {
-      throw Error('Wrong file structure.')
-    }
-    const matches = worksheet.slice(2, worksheet.length).map((matchData) => new Match.MatchClass(matchData))
-    const match = { ...this.state.match }
-    match.matches = matches
-    this.setState({ match })
-    this.calculateMatchFilters()
-    this.calculatePayDate()
-    this.filterMatches()
-    console.log('State after generating calendar:', this.state)
-  }
-
-  filterMatches () {
-    const filters = {
-      date: this.matchDate.value !== FILTER_OFF ? this.matchDate.value : null,
-      place: this.matchPlace.value !== FILTER_OFF ? this.matchPlace.value : null,
-      category: this.matchCategory.value !== FILTER_OFF ? this.matchCategory.value : null
-    }
-    console.log('New filter settings:', filters)
-
-    const match = { ...this.state.match }
-    match.filtered = match.matches.filter((match) => {
-      const matchDate = new Date(match.Datum)
-      matchDate.setHours(0, 0, 0, 0)
-      const filtDate = new Date(filters.date)
-      filtDate.setHours(0, 0, 0, 0)
-      return (!filters.date | matchDate.getTime() === filtDate.getTime()) &
-      (!filters.place | filters.place === match.Ort) &
-      (!filters.category | filters.category === match.Konkurrenz)
-    })
-    this.setState({ match })
-
-    const player = { ...this.state.player, filters }
-    player.filtered = player.players
-    this.setState({ player })
-  }
-
-  filterPlayers () {
-    const filters = {
-      junior: this.playerJunior.checked,
-      paid: this.playerPaid.checked,
-      category: this.playerCategory.value !== FILTER_OFF ? this.playerCategory.value : null
-    }
-    console.log('New filter settings:', filters)
-
-    const player = { ...this.state.player, filters }
-    player.filtered = player.players
-    this.setState({ player })
-  }
-
-  generateSchedule (event) {
-    event.preventDefault()
-
-    const matchlist = new Excel.Workbook()
-    matchlist.SheetNames = []
-    matchlist.Sheets = {}
-
-    const worksheets = {}
-
-    let placeArray = this.state.match.places
-    if (placeArray.length > 1) {
-      placeArray = placeArray.concat([FILTER_OFF])
-    }
-    const date = Object.keys(this.state.match.dates).find((key) =>
-      String(this.state.match.dates[key]) === this.matchDate.value
-    )
-
-    placeArray.forEach((place) => {
-      let header = [
-        [`Spielplan für den ${date} (${place})`],
-        [],
-        ['Ort', 'Zeit', 'Kategorie', 'Spieler 1', '', 'Spieler 2', '', '1. Satz', '2. Satz', '3. Satz', 'WO Grund']
-      ]
-      let matchListPerPlace = this.state.match.filtered.filter((match) => (match.Ort === place | place === FILTER_OFF)).map((match) =>
-        [match.Ort, time2s(match.Datum), match.Konkurrenz, match.Spieler1, match.Spieler1Klassierung, match.Spieler2, match.Spieler2Klassierung]
-      )
-      console.log('Generated match list per place:', matchListPerPlace)
-      worksheets[place] = Excel.SheetFromArray(header.concat(matchListPerPlace))
-      matchlist.SheetNames.push(place)
-      matchlist.Sheets[place] = worksheets[place]
-    })
-
-    Excel.saveAs(matchlist, 'Spielliste.xlsx')
-  }
-
-  calculatePayDate () {
-    if ((this.state.player.players.length === 0) | (this.state.match.matches.length === 0)) {
-      return
-    }
-
-    this.state.match.matches.forEach((match) => {
-      [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
-        if (matchPlayer) {
-          let foundPlayer = this.state.player.players.find((player) =>
-            (player.name === matchPlayer) & (player.Konkurrenz === match.Konkurrenz)
-          )
-          if (!foundPlayer) {
-            console.log('Debug payerlist:', foundPlayer, match)
-            throw Error('Match player not found in player list. This is an error!')
-          }
-          if (!foundPlayer.BezahltAm) {
-            foundPlayer.BezahltAm = match.Datum
-          }
-        }
-      })
-    })
-  }
-
-  generatePayTable (event) {
-    event.preventDefault()
-
-    const paylist = new Excel.Workbook()
-    paylist.SheetNames = []
-    paylist.Sheets = {}
-
-    const worksheets = {}
-
-    let placeArray = this.state.match.places
-    if (placeArray.length > 1) {
-      placeArray = placeArray.concat([FILTER_OFF])
-    }
-    const date = Object.keys(this.state.match.dates).find((key) =>
-      String(this.state.match.dates[key]) === this.matchDate.value
-    )
-
-    placeArray.forEach((place) => {
-      let header = [
-        [`Zahlliste für den ${date} (${place})`],
-        [],
-        ['bereits bez.', 'Kategorie', 'Zeit', 'Name', 'Betrag bez.', 'Quittung abgeben']
-      ]
-
-      let payListPerPlace = []
-      this.state.match.filtered.forEach((match) => {
-        [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
-          if (!!matchPlayer & (match.Ort === place | FILTER_OFF === place)) {
-            const player = this.state.player.players.find((player) =>
-              (player.Konkurrenz === match.Konkurrenz) & (player.name === matchPlayer)
-            )
-            let paid = null
-            if (player.BezahltAm < this.matchDate.value) {
-              paid = date2s(player.BezahltAm)
-            }
-            if (player.Bezahlt) {
-              paid = 'OK'
-            }
-            let price
-            if (player.isDoubles) {
-              price = (player.isJunior ? 15 : 25) + (player.isJuniorDP ? 15 : 25)
-            } else {
-              price = player.isJunior ? 30 : 50
-            }
-            payListPerPlace.push([ paid, match.Konkurrenz, time2s(match.Datum), `${matchPlayer} (${price}.-)` ])
-          }
-        })
-      })
-
-      let footer = [
-        [],
-        ['Turnierleiter', null, null, 'Kassierer'],
-        ['Betrag von Spielern erhalten', null, null, 'Betrag von Turnierleiter erhalten']
-      ]
-      console.log(`Generated pay list per place ${place}:`, payListPerPlace)
-      worksheets[place] = Excel.SheetFromArray(header.concat(payListPerPlace, footer))
-      paylist.SheetNames.push(place)
-      paylist.Sheets[place] = worksheets[place]
-    })
-    Excel.saveAs(paylist, 'Zahlliste.xlsx')
-  }
-
-  render () {
-    const PlayerList = Player.components.PlayerList
-    const MatchList = Match.components.MatchList
-
-    const places = this.state.match.places
-    const dates = this.state.match.dates
-    const matchCategories = this.state.match.categories
-    const playerCategories = this.state.player.categories
-
-    return (
-      <div className='container'>
-        <form>
-          <fieldset>
-            <legend>Input Files</legend>
-            <label htmlFor='PlayerList'>Swisstennis PlayerList.xls</label>
-            <input type='file' id='PlayerList' ref={(input) => { this.playerList = input }} accept='.xls' placeholder='PlayerList File' onChange={this.handlePlayerList} />
-            <label htmlFor='Calendar'>Swisstennis Calendar.xls</label>
-            <input type='file' id='Calendar' ref={(input) => { this.calendar = input }} accept='.xls' placeholder='Calendar File' onChange={this.handleCalendar} />
-          </fieldset>
-          <fieldset><legend>Filter</legend>
-            <label htmlFor='Date'>Datum</label>
-            <select id='Date' ref={(input) => { this.matchDate = input }} onChange={this.filterMatches}>
-              <option>{FILTER_OFF}</option>
-              {Object.keys(dates).map((key) => (
-                <option key={key} value={dates[key]}>{key}</option>
-            ))}
-            </select>
-            <label htmlFor='Place'>Ort</label>
-            <select id='Place' ref={(input) => { this.matchPlace = input }} onChange={this.filterMatches}>
-              <option>{FILTER_OFF}</option>
-              {places.map((place, key) => (
-                <option key={key}>{place}</option>
-            ))}
-            </select>
-            <label htmlFor='MatchCategory'>Match Konkurrenz</label>
-            <select id='MatchCategory' ref={(input) => { this.matchCategory = input }} onChange={this.filterMatches}>
-              <option>{FILTER_OFF}</option>
-              {matchCategories.map((category, key) => (
-                <option key={key}>{category}</option>
-            ))}
-            </select>
-            <label htmlFor='Junior'>Junior</label>
-            <input id='Junior' ref={(input) => { this.playerJunior = input }} type='checkbox' onChange={this.filterPlayers} />
-            <label htmlFor='Paid'>Bezahlt</label>
-            <input id='Paid' ref={(input) => { this.playerPaid = input }} type='checkbox' onChange={this.filterPlayers} />
-            <label htmlFor='PlayerCategory'>Spieler Konkurrenz</label>
-            <select id='PlayerCategory' ref={(input) => { this.playerCategory = input }} onChange={this.filterPlayers}>
-              <option>{FILTER_OFF}</option>
-              {playerCategories.map((category, key) => (
-                <option key={key}>{category}</option>
-            ))}
-            </select>
-          </fieldset>
-          <fieldset><legend>Output Files</legend>
-            <button onClick={this.generateSchedule} disabled={!this.state.match.filtered.length}>Spielliste generieren</button>
-            <button onClick={this.generatePayTable} disabled={(!this.state.match.filtered.length | !this.state.player.filtered.length)}>Zahlliste generieren</button>
-          </fieldset>
-        </form>
-        <PlayerList player={this.state.player} />
-        <MatchList match={this.state.match} />
-      </div>
-    )
-  }
-}
-
-export default App

+ 111 - 55
src/Main.js

@@ -1,9 +1,13 @@
 import React from 'react'           // React to manage the GUI
-import Player from './player'       // Everything that has to do with players
-import Match from './match'         // Everything that has to do with matches
+import Player from './classes/player'
+import Match from './classes/match'
+import PlayerList from './playerList'       // Everything that has to do with players
+import MatchList from './matchList'         // Everything that has to do with matches
 import Excel from './excel'         // Helper files to create Excel files
 import { date2s, time2s } from './helpers'
 import 'bootstrap/dist/css/bootstrap.css'
+import EmailList from './lists/components/EmailList'
+import PhoneList from './lists/components/PhoneList'
 
 /**
  * General Application Design
@@ -34,13 +38,12 @@ import 'bootstrap/dist/css/bootstrap.css'
  * - Shows the calculated payment lists
  * - Points out problems
  *
- *
  */
 
 const FILTER_OFF = 'Alle'
 
 /** Main application */
-class Main extends React.Component {
+class App extends React.Component {
   /**
    * Constructor
    * Defines the state and binds all 'this' in all functions to the object.
@@ -68,6 +71,7 @@ class Main extends React.Component {
     this.filterPlayers = this.filterPlayers.bind(this)
     this.generateSchedule = this.generateSchedule.bind(this)
     this.generatePayTable = this.generatePayTable.bind(this)
+    this.generatePhoneList = this.generatePhoneList.bind(this)
   }
 
   calculateMatchFilters () {
@@ -203,8 +207,9 @@ class Main extends React.Component {
       String(this.state.match.dates[key]) === this.matchDate.value
     )
 
-    placeArray.forEach((place) => {
+    placeArray.forEach(place => {
       let header = [
+        ['Stadtzürcher Tennismeisterschaft'],
         [`Spielplan für den ${date} (${place})`],
         [],
         ['Ort', 'Zeit', 'Kategorie', 'Spieler 1', '', 'Spieler 2', '', '1. Satz', '2. Satz', '3. Satz', 'WO Grund']
@@ -221,6 +226,36 @@ class Main extends React.Component {
     Excel.saveAs(matchlist, 'Spielliste.xlsx')
   }
 
+  generatePhoneList (event) {
+    event.preventDefault()
+
+    const phoneMail = new Excel.Workbook()
+    phoneMail.SheetNames = []
+    phoneMail.Sheets = {}
+
+    const dataList = [
+      ['Vorname', 'Nachname', 'Anrede', 'Geschlecht', 'Handy', 'E-Mail']
+    ]
+    const phonePot = []
+
+    const players = this.state.player.filtered
+    players.forEach(player => {
+      if (!player.phone.match(/^FEHLER/) && !phonePot.includes(player.phone)) {
+        phonePot.push(player.phone)
+        dataList.push([
+          player.Vorname,
+          player.Name,
+          2,
+          player.geschlecht === 'w' ? 2 : 1,
+          player.phone
+        ])
+      }
+    })
+    phoneMail.Sheets['Sheet1'] = Excel.SheetFromArray(dataList)
+    phoneMail.SheetNames.push('Sheet1')
+    Excel.saveAs(phoneMail, 'Telefon.xlsx')
+  }
+
   calculatePayDate () {
     if ((this.state.player.players.length === 0) | (this.state.match.matches.length === 0)) {
       return
@@ -254,20 +289,24 @@ class Main extends React.Component {
     const worksheets = {}
 
     let placeArray = this.state.match.places
-    if (placeArray.length > 1) {
+    /* if (placeArray.length > 1) {
       placeArray = placeArray.concat([FILTER_OFF])
-    }
+    } */
     const date = Object.keys(this.state.match.dates).find((key) =>
       String(this.state.match.dates[key]) === this.matchDate.value
     )
 
     placeArray.forEach((place) => {
       let header = [
-        [`Zahlliste für den ${date} (${place})`],
+        ['Stadtzürcher Tennismeisterschaft'],
+        [`Nenngelder für ${date}`],
+        [],
+        [`${place}`, null, '50.- oder 30.- (Junioren Jg. 1999 oder jünger)'],
         [],
-        ['bereits bez.', 'Kategorie', 'Zeit', 'Name', 'Betrag bez.', 'Quittung abgeben']
+        ['bereits bez.', 'Kat.', 'Zeit', 'Name', 'Betrag bez.', 'Quittung abgeben']
       ]
 
+      // Per place
       let payListPerPlace = []
       this.state.match.filtered.forEach((match) => {
         [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
@@ -295,6 +334,7 @@ class Main extends React.Component {
 
       let footer = [
         [],
+        ['Datum'],
         ['Turnierleiter', null, null, 'Kassierer'],
         ['Betrag von Spielern erhalten', null, null, 'Betrag von Turnierleiter erhalten']
       ]
@@ -303,13 +343,33 @@ class Main extends React.Component {
       paylist.SheetNames.push(place)
       paylist.Sheets[place] = worksheets[place]
     })
+
+    let payListPerPlace = []
+    this.state.match.filtered.forEach((match) => {
+      [match.Spieler1, match.Spieler2].forEach((matchPlayer) => {
+        if (matchPlayer) {
+          const player = this.state.player.players.find((player) =>
+            (player.Konkurrenz === match.Konkurrenz) & (player.name === matchPlayer)
+          )
+          let price
+          if (player.isDoubles) {
+            price = (player.isJunior ? 15 : 25) + (player.isJuniorDP ? 15 : 25)
+          } else {
+            price = player.isJunior ? 30 : 50
+          }
+          payListPerPlace.push([ match.Ort, match.Konkurrenz, `${matchPlayer} (${price}.-)` ])
+        }
+      })
+    })
+    console.log(`Generated pay list for "Alle":`, payListPerPlace)
+    worksheets[FILTER_OFF] = Excel.SheetFromArray(payListPerPlace)
+    paylist.SheetNames.push(FILTER_OFF)
+    paylist.Sheets[FILTER_OFF] = worksheets[FILTER_OFF]
+
     Excel.saveAs(paylist, 'Zahlliste.xlsx')
   }
 
   render () {
-    const PlayerList = Player.components.PlayerList
-    const MatchList = Match.components.MatchList
-
     const places = this.state.match.places
     const dates = this.state.match.dates
     const matchCategories = this.state.match.categories
@@ -318,57 +378,53 @@ class Main extends React.Component {
     return (
       <div className='container'>
         <form>
-          <fieldset>
-            <legend>Input Files</legend>
-            <label htmlFor='PlayerList'>Swisstennis PlayerList.xls</label>
-            <input type='file' id='PlayerList' ref={(input) => { this.playerList = input }} accept='.xls' placeholder='PlayerList File' onChange={this.handlePlayerList} />
-            <label htmlFor='Calendar'>Swisstennis Calendar.xls</label>
-            <input type='file' id='Calendar' ref={(input) => { this.calendar = input }} accept='.xls' placeholder='Calendar File' onChange={this.handleCalendar} />
-          </fieldset>
-          <fieldset><legend>Filter</legend>
-            <label htmlFor='Date'>Datum</label>
-            <select id='Date' ref={(input) => { this.matchDate = input }} onChange={this.filterMatches}>
-              <option>{FILTER_OFF}</option>
-              {Object.keys(dates).map((key) => (
-                <option key={key} value={dates[key]}>{key}</option>
+          <label htmlFor='PlayerList'>Swisstennis PlayerList.xls</label>
+          <input type='file' id='PlayerList' ref={(input) => { this.playerList = input }} accept='.xls' placeholder='PlayerList File' onChange={this.handlePlayerList} />
+          <label htmlFor='Calendar'>Swisstennis Calendar.xls</label>
+          <input type='file' id='Calendar' ref={(input) => { this.calendar = input }} accept='.xls' placeholder='Calendar File' onChange={this.handleCalendar} />
+          <label htmlFor='Date'>Datum</label>
+          <select id='Date' ref={(input) => { this.matchDate = input }} onChange={this.filterMatches}>
+            <option>{FILTER_OFF}</option>
+            {Object.keys(dates).map((key) => (
+              <option key={key} value={dates[key]}>{key}</option>
             ))}
-            </select>
-            <label htmlFor='Place'>Ort</label>
-            <select id='Place' ref={(input) => { this.matchPlace = input }} onChange={this.filterMatches}>
-              <option>{FILTER_OFF}</option>
-              {places.map((place, key) => (
-                <option key={key}>{place}</option>
+          </select>
+          <label htmlFor='Place'>Ort</label>
+          <select id='Place' ref={(input) => { this.matchPlace = input }} onChange={this.filterMatches}>
+            <option>{FILTER_OFF}</option>
+            {places.map((place, key) => (
+              <option key={key}>{place}</option>
             ))}
-            </select>
-            <label htmlFor='MatchCategory'>Match Konkurrenz</label>
-            <select id='MatchCategory' ref={(input) => { this.matchCategory = input }} onChange={this.filterMatches}>
-              <option>{FILTER_OFF}</option>
-              {matchCategories.map((category, key) => (
-                <option key={key}>{category}</option>
+          </select>
+          <label htmlFor='MatchCategory'>Match Konkurrenz</label>
+          <select id='MatchCategory' ref={(input) => { this.matchCategory = input }} onChange={this.filterMatches}>
+            <option>{FILTER_OFF}</option>
+            {matchCategories.map((category, key) => (
+              <option key={key}>{category}</option>
             ))}
-            </select>
-            <label htmlFor='Junior'>Junior</label>
-            <input id='Junior' ref={(input) => { this.playerJunior = input }} type='checkbox' onChange={this.filterPlayers} />
-            <label htmlFor='Paid'>Bezahlt</label>
-            <input id='Paid' ref={(input) => { this.playerPaid = input }} type='checkbox' onChange={this.filterPlayers} />
-            <label htmlFor='PlayerCategory'>Spieler Konkurrenz</label>
-            <select id='PlayerCategory' ref={(input) => { this.playerCategory = input }} onChange={this.filterPlayers}>
-              <option>{FILTER_OFF}</option>
-              {playerCategories.map((category, key) => (
-                <option key={key}>{category}</option>
+          </select>
+          <label htmlFor='Junior'>Junior</label>
+          <input id='Junior' ref={(input) => { this.playerJunior = input }} type='checkbox' onChange={this.filterPlayers} />
+          <label htmlFor='Paid'>Bezahlt</label>
+          <input id='Paid' ref={(input) => { this.playerPaid = input }} type='checkbox' onChange={this.filterPlayers} />
+          <label htmlFor='PlayerCategory'>Spieler Konkurrenz</label>
+          <select id='PlayerCategory' ref={(input) => { this.playerCategory = input }} onChange={this.filterPlayers}>
+            <option>{FILTER_OFF}</option>
+            {playerCategories.map((category, key) => (
+              <option key={key}>{category}</option>
             ))}
-            </select>
-          </fieldset>
-          <fieldset><legend>Output Files</legend>
-            <button onClick={this.generateSchedule} disabled={!this.state.match.filtered.length}>Spielliste generieren</button>
-            <button onClick={this.generatePayTable} disabled={(!this.state.match.filtered.length | !this.state.player.filtered.length)}>Zahlliste generieren</button>
-          </fieldset>
+          </select>
+          <button onClick={this.generateSchedule} disabled={!this.state.match.filtered.length}>Spielliste generieren</button>
+          <button onClick={this.generatePayTable} disabled={(!this.state.match.filtered.length | !this.state.player.filtered.length)}>Zahlliste generieren</button>
+          <button onClick={this.generatePhoneList} disabled={!this.state.player.players.length}>Telefonliste generieren</button>
         </form>
-        <PlayerList player={this.state.player} />
         <MatchList match={this.state.match} />
+        <PhoneList filtered={this.state.match.filtered} players={this.state.player.players} />
+        <EmailList filtered={this.state.match.filtered} players={this.state.player.players} />
+        <PlayerList player={this.state.player} />
       </div>
     )
   }
 }
 
-export default Main
+export default App

+ 0 - 0
src/match/components/MatchDisp.js → src/calendar/components/MatchDisp.js


+ 0 - 0
src/match/components/MatchList.js → src/calendar/components/MatchList.js


+ 0 - 1
src/match/components/index.js → src/calendar/components/index.js

@@ -2,4 +2,3 @@ import MatchDisp from './MatchDisp'
 import MatchList from './MatchList'
 
 export default { MatchDisp, MatchList }
- 

+ 2 - 3
src/match/index.js → src/calendar/index.js

@@ -3,18 +3,17 @@ import components from './components'
 import { normalize } from '../helpers.js'
 
 const filters = {
-  all: players => players,
+  all: players => players
 }
 
 const selectors = {}
 
-
 /** Class representing a player */
 class MatchClass {
   /**
    * Create a player
    * A player data item in the Swisstennis PlayerList.xlsx file has the following columns
-   * Ort | Datum | Zeit | Konkurrenz | Spieler 1 | Spieler 1 Klassierung | Spieler 2 | 
+   * Ort | Datum | Zeit | Konkurrenz | Spieler 1 | Spieler 1 Klassierung | Spieler 2 |
    * Spieler 2 Klassierung | [Resultat]
    */
   constructor (data) {

+ 1 - 2
src/match/state.js → src/calendar/state.js

@@ -6,7 +6,6 @@
  * Collection of everything which has to do with state changes.
  **/
 
-
 /** actionTypes define what actions are handeled by the reducer. */
 export const actions = {
   setState: matches => {
@@ -27,4 +26,4 @@ export function reducer (state = [], action) {
 }
 
 /** sagas are asynchronous workers (JS generators) to handle the state. */
-export function * saga () {}
+export function * saga () {}

+ 24 - 0
src/classes/match.js

@@ -0,0 +1,24 @@
+import { normalize } from '../helpers.js'
+
+/** Class representing a match */
+class Match {
+  /**
+   * Create a match
+   * A match data item in the Swisstennis Calendar.xlsx file has the following columns
+   * Ort | Datum | Zeit | Konkurrenz | Spieler 1 | Spieler 1 Klassierung | Spieler 2 |
+   * Spieler 2 Klassierung | [Resultat]
+   */
+  constructor (data) {
+    this.Ort = normalize(data[0])
+    this.Datum = data[1]
+    this.Konkurrenz = normalize(data[3])
+    this.Spieler1 = normalize(data[4])
+    this.Spieler1Klassierung = normalize(data[5])
+    this.Spieler2 = normalize(data[6])
+    this.Spieler2Klassierung = normalize(data[7])
+    this.Resultat = normalize(data[8] || null)
+    this.isDoubles = this.Konkurrenz.match(/DM.*|[MW]D.*/)
+  }
+}
+
+export default { Match }

+ 32 - 12
src/player/index.js → src/classes/player.js

@@ -1,12 +1,7 @@
-import { actions, reducer, state } from './state'
-import components from './components'
-import { normalize } from '../helpers.js'
+import { normalize, normalizePhone } from '../helpers.js'
 
-const filters = {
-  all: players => players,
-}
-
-const selectors = {}
+/** Regular expression for cellphone numbers. Cellphone numbers start with +417 */
+const reMobile = /^\+417/
 
 /** Class representing a player */
 class Player {
@@ -21,9 +16,21 @@ class Player {
   constructor (data) {
     this.Konkurrenz = normalize(data[0])
     this.Lizenz = normalize(data[2])
+    this.geschlecht = null
+    if (this.Konkurrenz) {
+      if (this.Konkurrenz[0] === 'M') {
+        this.geschlecht = 'm'
+      } else if (this.Konkurrenz[0] === 'W') {
+        this.geschlecht = 'w'
+      }
+    }
     this.Name = normalize(data[5])
     this.Vorname = normalize(data[6])
     this.Geburtsdatum = data[7]
+    this.TelP = normalizePhone(data[13])
+    this.TelG = normalizePhone(data[14])
+    this.Mobile = normalizePhone(data[15])
+    this.Email = normalize(data[16])
     this.Klassierung = normalize(data[17])
     this.LizenzDP = normalize(data[23])
     this.NameDP = normalize(data[24])
@@ -34,11 +41,24 @@ class Player {
     this.Bezahlt = data[31]
     this.BezahltAm = null
     this.Matches = []
-    this.isDoubles = this.Konkurrenz.match(/DM.*|[MW]D.*/)
-    this.isJunior = (this.Geburtsdatum) ? this.Geburtsdatum.getTime() >= (new Date((new Date()).getFullYear() - 18, 11, 31, 23, 59, 59, 999)).getTime() : false
-    this.isJuniorDP = (this.isDoubles & !!this.GeburtsdatumDP) ? this.GeburtsdatumDP.getTime() >= (new Date((new Date()).getFullYear() - 18, 11, 31, 23, 59, 59, 999)).getTime() : false
+    this.isDoubles = !!this.Konkurrenz.match(/DM.*|[MW]D.*/)
+    this.isJunior = (this.Geburtsdatum) ? this.Geburtsdatum.getTime() >= (new Date((new Date()).getFullYear() - 19, 11, 31, 23, 59, 59, 999)).getTime() : false
+    this.isJuniorDP = (this.isDoubles && this.GeburtsdatumDP) ? this.GeburtsdatumDP.getTime() >= (new Date((new Date()).getFullYear() - 19, 11, 31, 23, 59, 59, 999)).getTime() : false
     this.name = this.isDoubles ? `${this.Name} ${this.Vorname} / ${this.NameDP} ${this.VornameDP}` : `${this.Name} ${this.Vorname}`
+    if (this.Mobile && this.Mobile.match(reMobile)) {
+      this.phone = this.Mobile
+    } else if (this.TelP && this.TelP.match(reMobile)) {
+      this.phone = this.TelP
+    } else if (this.TelG && this.TelG.match(reMobile)) {
+      this.phone = this.TelG
+    } else if (this.Mobile && this.Mobile.match(/FEHLER/)) {
+      this.phone = this.Mobile
+    } else if (this.TelP && this.TelP.match(/FEHLER/)) {
+      this.phone = this.TelP
+    } else if (this.TelG && this.TelG.match(/FEHLER/)) {
+      this.phone = this.TelG
+    }
   }
 }
 
-export default { Player, actions, components, filters, selectors, reducer, state }
+export default { Player }

+ 20 - 1
src/helpers.js

@@ -24,4 +24,23 @@ function normalize (item, type) {
   return item ? String(item).replace(/\s+/g, ' ').trim() : null
 }
 
-export { date2s, time2s, datetime2s, sortTable, normalize }
+function normalizePhone (item) {
+  let phone = String(item).replace(/\s|\+|\/|,|-|'/g, '').replace(/\(0\)/, '').replace(/^0+/, '')
+  if (phone.match(/[^\d*]/)) {
+    return `FEHLER (nicht-numerische Zeichen): ${phone}`
+  }
+  if (phone.length === 0) {
+    return null
+  } else if (phone.length === 9) {
+    // Assume swiss number
+    phone = `+41${phone}`
+  } else if (phone.length === 11) {
+    phone = `+${phone}`
+  } else {
+    return `FEHLER (falsche Länge): ${phone}`
+  }
+
+  return phone
+}
+
+export { date2s, time2s, datetime2s, sortTable, normalize, normalizePhone }

+ 10 - 11
src/index.js

@@ -14,8 +14,8 @@ import { syncHistoryWithStore, routerReducer } from 'react-router-redux'
 import Main from './Main'
 
 // Import the submodules
-import player from './player'
-import match from './match'
+import playerList from './playerList'
+import matchList from './matchList'
 
 /**
  * Redux Section
@@ -23,15 +23,15 @@ import match from './match'
 
 /** The root reducer is combined from all sub-module reducers */
 const rootReducer = combineReducers({
-  player: player.reducer,
-  match: match.reducer,
+  playerList: playerList.reducer,
+  matchList: matchList.reducer
 })
 console.log('Root reducer:', rootReducer)
 
 /** The default state is combined from all sub-module states */
 const defaultState = {
-  player: player.state,
-  match: match.state,
+  playerList: playerList.state,
+  matchList: matchList.state
 }
 console.log('Default state:', defaultState)
 
@@ -50,9 +50,9 @@ console.log('history:', history)
 */
 
 /** Collect the action creators from all modules in actionCreators */
-const actionCreators = { 
-  player: player.actions,
-  match: match.actions,
+const actionCreators = {
+  playerList: playerList.actions,
+  matchList: matchList.actions
 }
 
 /** Creates a function  */
@@ -77,7 +77,6 @@ function mapDispatchToProps (dispatch) {
 
 const App = connect(mapStateToProps, mapDispatchToProps)(Main)
 
-
 /**
  * React-Router Section
  **/
@@ -114,4 +113,4 @@ const provider = (
 ReactDOM.render(
   provider,
   document.getElementById('root')
-)
+)

+ 41 - 0
src/lists/components/EmailList.js

@@ -0,0 +1,41 @@
+import React from 'react'
+
+class EmailList extends React.Component {
+  render () {
+    const filtered = this.props.filtered
+    const players = this.props.players
+
+    const emailList = []
+    filtered.forEach((match, key) => {
+      const player = players.find(player =>
+        (player.name === match.Spieler1) && (player.Konkurrenz === match.Konkurrenz)
+      )
+      if (!player) {
+        console.log('EmailList: Player not found!', player, key, match)
+      } else {
+        if (!emailList.includes(player.Email)) {
+          emailList.push(player.Email)
+        }
+      }
+      const player2 = players.find(player =>
+        (player.name === match.Spieler2) && (player.Konkurrenz === match.Konkurrenz)
+      )
+      if (!player2) {
+        console.log('EmailList: Player not found!', player2, key, match)
+      } else {
+        if (!emailList.includes(player2.Email)) {
+          emailList.push(player2.Email)
+        }
+      }
+    })
+
+    return (
+      <div>
+        <h2>Email Adressen</h2>
+        <p>{emailList.join('; ')}</p>
+      </div>
+    )
+  }
+}
+
+export default EmailList

+ 41 - 0
src/lists/components/PhoneList.js

@@ -0,0 +1,41 @@
+import React from 'react'
+
+class PhoneList extends React.Component {
+  render () {
+    const filtered = this.props.filtered
+    const players = this.props.players
+
+    const phoneList = []
+    filtered.forEach((match, key) => {
+        const player = players.find(player => 
+            (player.name === match.Spieler1) && (player.Konkurrenz === match.Konkurrenz)
+        )
+        if (!player) {
+            console.log('PhoneList: Player not found!', player, key, match)
+        } else {
+            if (!phoneList.includes(player.phone)) {
+                phoneList.push(player.phone)
+            }
+        }
+        const player2 = players.find(player => 
+            (player.name === match.Spieler2) && (player.Konkurrenz === match.Konkurrenz)
+        )
+        if (!player2) {
+            console.log('PhoneList: Player not found!', player2, key, match)
+        } else {
+            if (!phoneList.includes(player2.phone)) {
+                phoneList.push(player2.phone)
+            }
+        }
+    })
+
+    return (
+      <div>
+        <h2>Telefonnummern</h2>
+        <p>{phoneList.join('; ')}</p>
+      </div>
+    )
+  }
+}
+
+export default PhoneList

+ 126 - 0
src/macros/SZTM_Spielliste.bas

@@ -0,0 +1,126 @@
+Attribute VB_Name = "Module2"
+Sub SZTM_Spielliste()
+Attribute SZTM_Spielliste.VB_Description = "Formatiert die Spielliste"
+Attribute SZTM_Spielliste.VB_ProcData.VB_Invoke_Func = "X\n14"
+'
+' SZTM_Spielliste Macro
+' Formatiert die Spielliste
+'
+' Keyboard Shortcut: Ctrl+Shift+X
+'
+
+
+    Dim ws As Worksheet
+    For Each ws In ActiveWorkbook.Sheets
+        With ws
+        
+            ws.Activate
+            
+            'Select first row, change font size, make bold and merge
+            Range("A1:K1").Select
+            With Selection
+                .HorizontalAlignment = xlLeft
+                .VerticalAlignment = xlBottom
+                .WrapText = False
+                .Orientation = 0
+                .AddIndent = False
+                .IndentLevel = 0
+                .ShrinkToFit = False
+                .ReadingOrder = xlContext
+                .MergeCells = True
+            End With
+            Selection.Merge
+            With Selection.Font
+                .Name = "Calibri"
+                .Size = 14
+                .Strikethrough = False
+                .Superscript = False
+                .Subscript = False
+                .OutlineFont = False
+                .Shadow = False
+                .Underline = xlUnderlineStyleNone
+                .ThemeColor = xlThemeColorLight1
+                .TintAndShade = 0
+                .ThemeFont = xlThemeFontMinor
+            End With
+            Selection.Font.Bold = True
+            
+            'Select second row, make bold and merge
+            Range("A2:K2").Select
+            With Selection
+                .HorizontalAlignment = xlLeft
+                .VerticalAlignment = xlBottom
+                .WrapText = False
+                .Orientation = 0
+                .AddIndent = False
+                .IndentLevel = 0
+                .ShrinkToFit = False
+                .ReadingOrder = xlContext
+                .MergeCells = True
+            End With
+            Selection.Merge
+            Selection.Font.Bold = True
+                        
+            'Select all data cells, activate grid, fit width, increase height and add header.
+            Range("A4:K4").Select
+            With Selection.Interior
+                .Pattern = xlSolid
+                .PatternColorIndex = xlAutomatic
+                .ThemeColor = xlThemeColorDark1
+                .TintAndShade = -0.149998474074526
+                .PatternTintAndShade = 0
+            End With
+            Selection.Font.Bold = True
+            ActiveWindow.SmallScroll Down:=-3
+            Range("A4").Select
+            Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
+            Selection.Borders(xlDiagonalDown).LineStyle = xlNone
+            Selection.Borders(xlDiagonalUp).LineStyle = xlNone
+            With Selection.Borders(xlEdgeLeft)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeTop)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeBottom)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeRight)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideVertical)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideHorizontal)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            Selection.RowHeight = 33.75
+            Selection.EntireColumn.AutoFit
+            'Columns("A:A").EntireColumn.AutoFit
+            'Columns("B:B").EntireColumn.AutoFit
+            'Columns("C:C").EntireColumn.AutoFit
+            'Columns("D:D").EntireColumn.AutoFit
+            'Columns("E:E").EntireColumn.AutoFit
+            
+        End With
+    Next
+
+End Sub

+ 200 - 0
src/macros/SZTM_Zahlliste.bas

@@ -0,0 +1,200 @@
+Attribute VB_Name = "Module1"
+Sub SZTM_Zahlliste()
+Attribute SZTM_Zahlliste.VB_Description = "Formatiert die Zahlliste"
+Attribute SZTM_Zahlliste.VB_ProcData.VB_Invoke_Func = "Z\n14"
+'
+' SZTM_Zahlliste Macro
+' Formatiert die Zahlliste
+'
+' Keyboard Shortcut: Ctrl+Shift+F
+'
+    Dim ws As Worksheet
+    For Each ws In ActiveWorkbook.Sheets
+        With ws
+        
+            ws.Activate
+            ws.PageSetup.Orientation = xlLandscape
+            
+            'Select first row, change font size, make bold and merge
+            Range("A1:F1").Select
+            With Selection
+                .HorizontalAlignment = xlLeft
+                .VerticalAlignment = xlBottom
+                .WrapText = False
+                .Orientation = 0
+                .AddIndent = False
+                .IndentLevel = 0
+                .ShrinkToFit = False
+                .ReadingOrder = xlContext
+                .MergeCells = True
+            End With
+            Selection.Merge
+            With Selection.Font
+                .Name = "Calibri"
+                .Size = 14
+                .Strikethrough = False
+                .Superscript = False
+                .Subscript = False
+                .OutlineFont = False
+                .Shadow = False
+                .Underline = xlUnderlineStyleNone
+                .ThemeColor = xlThemeColorLight1
+                .TintAndShade = 0
+                .ThemeFont = xlThemeFontMinor
+            End With
+            Selection.Font.Bold = True
+            
+            'Select second row, make bold and merge
+            Range("A2:F2").Select
+            With Selection
+                .HorizontalAlignment = xlLeft
+                .VerticalAlignment = xlBottom
+                .WrapText = False
+                .Orientation = 0
+                .AddIndent = False
+                .IndentLevel = 0
+                .ShrinkToFit = False
+                .ReadingOrder = xlContext
+                .MergeCells = True
+            End With
+            Selection.Merge
+            Selection.Font.Bold = True
+            
+            'Select price cells and merge
+            Range("C4:F4").Select
+            Selection.Merge
+            
+            'Select last cell
+            ActiveCell.SpecialCells(xlLastCell).Select
+            Selection.Offset(0, -2).Select
+            Selection.Resize(Selection.Rows.Count + 1, Selection.Columns.Count + 2).Select
+            Selection.Merge
+            
+            Selection.Offset(0, -3).Select
+            Selection.Resize(Selection.Rows.Count + 1, Selection.Columns.Count + 2).Select
+            Selection.Merge
+            
+            Selection.Offset(-1, 0).Select
+            Selection.Font.Bold = True
+            Selection.Offset(0, 1).Select
+            Selection.Resize(Selection.Rows.Count, Selection.Columns.Count + 1).Select
+            Selection.Merge
+            Selection.Offset(0, 1).Select
+            Selection.Font.Bold = True
+            Selection.Offset(0, 1).Select
+            Selection.Resize(Selection.Rows.Count, Selection.Columns.Count + 1).Select
+            Selection.Merge
+            
+            Selection.Offset(-1, -4).Select
+            Selection.Font.Bold = True
+            Selection.Offset(0, 1).Select
+            Selection.Resize(Selection.Rows.Count, Selection.Columns.Count + 1).Select
+            Selection.Merge
+            
+            Selection.Offset(0, -1).Select
+            Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
+            Selection.Resize(Selection.Rows.Count - 1, Selection.Columns.Count).Select
+            Selection.Borders(xlDiagonalDown).LineStyle = xlNone
+            Selection.Borders(xlDiagonalUp).LineStyle = xlNone
+            With Selection.Borders(xlEdgeLeft)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeTop)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeBottom)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeRight)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideVertical)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideHorizontal)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            
+                
+            'Select all data cells, activate grid, fit width, increase height and add header.
+            Range("A6:F6").Select
+            With Selection.Interior
+                .Pattern = xlSolid
+                .PatternColorIndex = xlAutomatic
+                .ThemeColor = xlThemeColorDark1
+                .TintAndShade = -0.149998474074526
+                .PatternTintAndShade = 0
+            End With
+            Selection.Font.Bold = True
+            ActiveWindow.SmallScroll Down:=-3
+            Range("A6").Select
+            Range(Selection, ActiveCell.SpecialCells(xlLastCell)).Select
+            Selection.Resize(Selection.Rows.Count - 6, Selection.Columns.Count).Select
+            Selection.Borders(xlDiagonalDown).LineStyle = xlNone
+            Selection.Borders(xlDiagonalUp).LineStyle = xlNone
+            With Selection.Borders(xlEdgeLeft)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeTop)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeBottom)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlEdgeRight)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideVertical)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            With Selection.Borders(xlInsideHorizontal)
+                .LineStyle = xlContinuous
+                .ColorIndex = 0
+                .TintAndShade = 0
+                .Weight = xlThin
+            End With
+            Selection.RowHeight = 33.75
+            Selection.EntireColumn.AutoFit
+            'Columns("A:A").EntireColumn.AutoFit
+            'Columns("B:B").EntireColumn.AutoFit
+            'Columns("C:C").EntireColumn.AutoFit
+            'Columns("D:D").EntireColumn.AutoFit
+            'Columns("E:E").EntireColumn.AutoFit
+            
+        End With
+    Next
+
+End Sub

+ 20 - 0
src/matchList/components/MatchDisp.js

@@ -0,0 +1,20 @@
+import React from 'react'
+import { date2s, time2s } from '../../helpers.js'
+
+class MatchDisp extends React.Component {
+  render () {
+    const match = this.props.match
+    return (
+      <tr>
+        <td>{match.Ort || <strong>Kein Platz zugeteilt</strong>}</td>
+        <td>{match.Datum ? date2s(match.Datum) : <strong>Kein Datum zugeteilt</strong>}</td>
+        <td>{match.Datum ? time2s(match.Datum) : <strong>Keine Zeit zugeteilt</strong>}</td>
+        <td>{match.Konkurrenz}</td>
+        <td>{match.Spieler1}</td>
+        <td>{match.Spieler2}</td>
+      </tr>
+    )
+  }
+}
+
+export default MatchDisp

+ 34 - 0
src/matchList/components/MatchList.js

@@ -0,0 +1,34 @@
+import React from 'react'
+import MatchDisp from './MatchDisp'
+
+class MatchList extends React.Component {
+  render () {
+    const matches = this.props.match.matches
+    const filtered = this.props.match.filtered
+
+    return (
+      <div>
+        <h2>Matches ({filtered.length}/{matches.length})</h2>
+        <table className='table table-bordered table-striped'>
+          <thead>
+            <tr>
+              <th>Ort</th>
+              <th>Datum</th>
+              <th>Zeit</th>
+              <th>Konkurrenz</th>
+              <th>Spieler 1</th>
+              <th>Spieler 2</th>
+            </tr>
+          </thead>
+          <tbody>
+            {filtered.map((match, key) =>
+              <MatchDisp key={key} match={match} />
+            )}
+          </tbody>
+        </table>
+      </div>
+    )
+  }
+}
+
+export default MatchList

+ 1 - 0
src/matchList/components/index.js

@@ -0,0 +1 @@
+export default {}

+ 10 - 0
src/matchList/index.js

@@ -0,0 +1,10 @@
+import { actions, reducer, state } from './state'
+import components from './components'
+
+const filters = {
+  all: players => players
+}
+
+const selectors = {}
+
+export default { actions, components, filters, selectors, reducer, state }

+ 35 - 0
src/matchList/state.js

@@ -0,0 +1,35 @@
+/** @module matchList/state */
+
+/**
+ * state.js
+ *
+ * Collection of everything which has to do with state changes.
+ **/
+
+/** actionTypes define what actions are handeled by the reducer. */
+export const actions = {
+  setState: matches => {
+    return {
+      type: 'SET_MATCHES',
+      matches
+    }
+  }
+}
+console.log('State actions', actions)
+
+/** state definition */
+export const state = {
+  allMatches: [],
+  filteredMatches: [],
+  filters: {}
+}
+console.log('State state', state)
+
+/** reducer is called by the redux dispatcher and handles all component actions */
+export function reducer (state = [], action) {
+  let nextState = state
+  return nextState
+}
+
+/** sagas are asynchronous workers (JS generators) to handle the state. */
+export function * saga () {}

+ 0 - 0
src/player/components/PlayerDisp.js → src/playerList/components/PlayerDisp.js


+ 0 - 0
src/player/components/PlayerList.js → src/playerList/components/PlayerList.js


+ 0 - 0
src/player/components/index.js → src/playerList/components/index.js


+ 10 - 0
src/playerList/index.js

@@ -0,0 +1,10 @@
+import { actions, reducer, state } from './state'
+import components from './components'
+
+const filters = {
+  all: players => players
+}
+
+const selectors = {}
+
+export default { actions, components, filters, selectors, reducer, state }

+ 10 - 5
src/player/state.js → src/playerList/state.js

@@ -6,18 +6,23 @@
  * Collection of everything which has to do with state changes.
  **/
 
-
 /** actionTypes define what actions are handeled by the reducer. */
 export const actions = {
   setState: players => {
-    type: 'SET_PLAYERS',
-    players
+    return {
+      type: 'SET_PLAYERS',
+      players
+    }
   }
 }
 console.log('State actions', actions)
 
 /** state definition */
-export const state = {}
+export const state = {
+  allPlayers: [],
+  filteredPlayers: [],
+  filters: {}
+}
 console.log('State state', state)
 
 /** reducer is called by the redux dispatcher and handles all component actions */
@@ -27,4 +32,4 @@ export function reducer (state = [], action) {
 }
 
 /** sagas are asynchronous workers (JS generators) to handle the state. */
-export function * saga () {}
+export function * saga () {}

+ 13 - 13
src/registerServiceWorker.js

@@ -8,15 +8,15 @@
 // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
 // This link also includes instructions on opting out of this behavior.
 
-export default function register() {
+export default function register () {
   if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
     window.addEventListener('load', () => {
-      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
       navigator.serviceWorker
         .register(swUrl)
         .then(registration => {
           registration.onupdatefound = () => {
-            const installingWorker = registration.installing;
+            const installingWorker = registration.installing
             installingWorker.onstatechange = () => {
               if (installingWorker.state === 'installed') {
                 if (navigator.serviceWorker.controller) {
@@ -24,28 +24,28 @@ export default function register() {
                   // the fresh content will have been added to the cache.
                   // It's the perfect time to display a "New content is
                   // available; please refresh." message in your web app.
-                  console.log('New content is available; please refresh.');
+                  console.log('New content is available; please refresh.')
                 } else {
                   // At this point, everything has been precached.
                   // It's the perfect time to display a
                   // "Content is cached for offline use." message.
-                  console.log('Content is cached for offline use.');
+                  console.log('Content is cached for offline use.')
                 }
               }
-            };
-          };
+            }
+          }
         })
         .catch(error => {
-          console.error('Error during service worker registration:', error);
-        });
-    });
+          console.error('Error during service worker registration:', error)
+        })
+    })
   }
 }
 
-export function unregister() {
+export function unregister () {
   if ('serviceWorker' in navigator) {
     navigator.serviceWorker.ready.then(registration => {
-      registration.unregister();
-    });
+      registration.unregister()
+    })
   }
 }