From 8082db19ac6968f7c5d4c10689d121b8e90df4bf Mon Sep 17 00:00:00 2001 From: Steve Schafer Date: Sat, 17 Jan 2026 09:59:06 -0700 Subject: [PATCH] Add /categorize, /problems and /report --- src/Categorize.jsx | 228 ++++++++++++++++++++++++++++ src/EditRegex.jsx | 108 ++++++++----- src/Regexes.jsx | 6 + src/Report.jsx | 354 +++++++++++++++++++++++++++++++++++++++++++ src/ReportParams.jsx | 98 ++++++++++++ src/apiService.jsx | 151 +++++++++++++----- src/index.jsx | 12 +- src/main.css | 15 ++ start | 2 +- 9 files changed, 891 insertions(+), 83 deletions(-) create mode 100644 src/Categorize.jsx create mode 100644 src/Report.jsx create mode 100644 src/ReportParams.jsx diff --git a/src/Categorize.jsx b/src/Categorize.jsx new file mode 100644 index 0000000..3f6da66 --- /dev/null +++ b/src/Categorize.jsx @@ -0,0 +1,228 @@ +import React from 'react'; +import {useState, useEffect} from "react"; +import {useParams, useNavigate} from 'react-router-dom'; +import { + categorize +} from "./apiService"; + +const Categorize = (props) => { + const [error, setError] = useState(null); + const [isLoaded, setIsLoaded] = useState(false); + const [report, setReport] = useState({}); + const navigate = useNavigate(); + const params = useParams(); + + console.log("Categorize: calling useEffect"); + useEffect(() => { + console.log("Categorize: useEffect"); + console.log("params", params); + let redo = params.redo == 'true'; + + categorize(redo).then( + data => { + console.log("Categorize.useEffect categorize data", data); + if(data.status === 401) { + navigate('/login', {replace: true}); + return; + } + if(data.status !== 200) { + setIsLoaded(true); + setError(data); + return; + } + setIsLoaded(true); + setReport(data.result); + }, + error => { + if(error.status === 401) { + navigate('/login', {replace: true}); + return; + } + console.log('error object', error); + setIsLoaded(true); + setError(error); + } + ); + }, [navigate, params]); + + if(error) { + return
Error: {error.message}
; + } + + if(!isLoaded) { + return
Loading...
; + } + + const getFlagNames = function(flags) { + let names = []; + if(flags & 0x02) { + names.push("Case Insensitive"); + } + if ((flags & 0x08) !== 0) { + names.push("Multiline"); + } + if ((flags & 0x20) !== 0) { + names.push("Dotall"); + } + if ((flags & 0x40) !== 0) { + names.push("Unicode Case"); + } + if ((flags & 0x80) !== 0) { + names.push("Canon EQ"); + } + if ((flags & 0x01) !== 0) { + names.push("Unix Linex"); + } + if ((flags & 0x10) !== 0) { + names.push("Literal"); + } + if ((flags & 0x100) !== 0) { + names.push("Unicode Character Class"); + } + if ((flags & 0x04) !== 0) { + names.push("Comments"); + } + return names.join(", "); + } + + const numberFormat = new Intl.NumberFormat("en-US", { style: 'currency', currency: 'USD' }); + + console.log("report render", report); + return ( + <> +

Categorize Result

+ { + Object.values(report).map(yearReport => { + return ( + <> +

{yearReport.year}

+

Multiply Assigned Transactions

+ + + + + + + + + + + + + { + yearReport.multiplyAssignedTransactions.map(mat => { + return ( + + + + + + + + + ) + }) + } + +
IDsourcedescriptiondateamountregexes
+ {mat.transaction.transactionId} + + {mat.transaction.source} + + {mat.transaction.description} + + {mat.transaction.date} + + {numberFormat.format(mat.transaction.amount)} + + + + + + + + + + + + + + + { + mat.regexes.map(regex => { + return ( + + + + + + + + + + ) + }) + } + +
CategorySourceRegular ExpressionFlagsPriorityExtra DescriptionYear
+ {regex.fqCategoryName} + + {regex.source} + + {regex.regex} + + {getFlagNames(regex.flags)} + + {regex.priority} + + {regex.description} + + {regex.year} +
+
+

Unassigned Transactions

+ + + + + + + + + + + + { + yearReport.unassignedTransactions.map(transaction => { + return ( + + + + + + + + ) + }) + } + +
YearSourceDescriptionDateAmount
+ {transaction.year} + + {transaction.source} + + {transaction.description} + + {transaction.date} + + {numberFormat.format(transaction.amount)} +
+ + ); + }) + } + + ); +} + +export default Categorize; \ No newline at end of file diff --git a/src/EditRegex.jsx b/src/EditRegex.jsx index d85ae2c..128f102 100644 --- a/src/EditRegex.jsx +++ b/src/EditRegex.jsx @@ -29,8 +29,8 @@ const EditRegex = (props) => { const [addingCategory, setAddingCategory] = useState(null); const [addCategoryOkButtonDisabled, setAddCategoryOkButtonDisabled] = useState(true); const [dirty, setDirty] = useState(false); - const [attachedTransactions, setAttachedTransactions] = useState(null); - const [allTransactions, setAllTransactions] = useState(null); + const [attachedTransactions, setAttachedTransactions] = useState([]); + const [allTransactions, setAllTransactions] = useState([]); const navigate = useNavigate(); useEffect(() => { @@ -115,18 +115,18 @@ const EditRegex = (props) => { useEffect(() => { console.log('start useEffect 2', regexObj); let ignore = false; - - if(regexObj && regexObj.categoryId) { - getCategoryAncestry(regexObj.categoryId).then( - data => { - // console.log('useEffect 2 getCategoryAncestry data', data); - if(!ignore) { - setCategoryAncestry(data); - setOriginalCategoryAncestry(data); - setCategoryAncestryIsLoaded(true); - } + let categoryId = regexObj != null ? regexObj.categoryId : null; + getCategoryAncestry(categoryId).then( + data => { + // console.log('useEffect 2 getCategoryAncestry data', data); + if(!ignore) { + setCategoryAncestry(data); + setOriginalCategoryAncestry(data); + setCategoryAncestryIsLoaded(true); } - ); + } + ); + if(regexObj && regexObj.id) { getTransactionsByRegexId("2025", regexObj.id).then( data => { if(!ignore) { @@ -134,14 +134,14 @@ const EditRegex = (props) => { } } ); - getAllTransactions("2025").then( - data => { - if(!ignore) { - setAllTransactions(data); - } - } - ) } + getAllTransactions("2025").then( + data => { + if(!ignore) { + setAllTransactions(data); + } + } + ) return () => { ignore = true; }; @@ -327,7 +327,7 @@ const EditRegex = (props) => { } const handleCategoryChange = e => { - // console.log('EditRegex.handleCategoryChange', e); + console.log('EditRegex.handleCategoryChange', e); const name = e.target.name; const value = e.target.value; let categoryLevel = +name; @@ -341,6 +341,10 @@ const EditRegex = (props) => { alert(error.message); console.error('error object', error); }); + let newRegexObj = getCopyOfRegexObj(); + newRegexObj.categoryId = categoryId; + setRegexObj(newRegexObj); + setDirty(newRegexObj.categoryId !== originalRegexObj.categoryId); } const categoryAncestryWasChanged = (categoryAncestry) => { @@ -507,7 +511,7 @@ const EditRegex = (props) => { const categorySelects = []; // console.log("render categoryAncestry", categoryAncestry); - if(categoryAncestry) { + if(categoryAncestry.length > 0) { for(let category of categoryAncestry) { // console.log("category", category); let key = categorySelects.length; @@ -550,6 +554,20 @@ const EditRegex = (props) => { categorySelects.push(select); } } + else { + let key = categorySelects.length; + let options = []; + options.push() + options.push(); + // console.log("options", options); + let select = ; + // console.log("select", select); + categorySelects.push(select); + } let attachedTransactionRows = []; let i = 0; @@ -570,26 +588,34 @@ const EditRegex = (props) => { if(regexObj.flags & 2) { flagsStr += "i"; } - let re = new RegExp(regexObj.regex, flagsStr); + let re = null; + try { + re = new RegExp(regexObj.regex, flagsStr); + } + catch(e) { + re = null; + } let matchedTransactionRows = []; - i = 0; - for(let transaction of allTransactions) { - let regexMatches = re.test(transaction.description); - let sourceMatches = regexObj.source == null || regexObj.source === "" || - transaction.source === regexObj.source; - // console.log(regexMatches, sourceMatches, transaction.description, transaction.source); - if(regexMatches && sourceMatches) { - matchedTransactionRows.push( - {transaction.id} - {transaction.source} - {transaction.type} - {transaction.description} - {transaction.extraDescription} - {transaction.date} - {transaction.amount} - {transaction.optional} - {transaction.regexId} - ); + if(re) { + i = 0; + for(let transaction of allTransactions) { + let regexMatches = re.test(transaction.description); + let sourceMatches = regexObj.source == null || regexObj.source === "" || + transaction.source === regexObj.source; + // console.log(regexMatches, sourceMatches, transaction.description, transaction.source); + if(regexMatches && sourceMatches) { + matchedTransactionRows.push( + {transaction.id} + {transaction.source} + {transaction.type} + {transaction.description} + {transaction.extraDescription} + {transaction.date} + {transaction.amount} + {transaction.optional} + {transaction.regexId} + ); + } } } diff --git a/src/Regexes.jsx b/src/Regexes.jsx index bfac7fb..e8304f3 100644 --- a/src/Regexes.jsx +++ b/src/Regexes.jsx @@ -186,6 +186,12 @@ const Regexes = (props) => { deleteRegex(regex.id).then( data => { console.log('deleteRegex data', data); + var index = regexes.indexOf(regex); + if(index > -1) { + var newRegexes = regexes.slice(); + newRegexes.splice(index, 1); + setRegexes(newRegexes); + } }, error => { console.error('error object', error); diff --git a/src/Report.jsx b/src/Report.jsx new file mode 100644 index 0000000..3b7cb19 --- /dev/null +++ b/src/Report.jsx @@ -0,0 +1,354 @@ +import React from 'react'; +import {useState, useEffect} from "react"; +import {useSearchParams, useNavigate, useLocation} from 'react-router-dom'; +import {report} from "./apiService"; + +const Report = (props) => { + const [error, setError] = useState(null); + const [isLoaded, setIsLoaded] = useState(false); + const [reportMap, setReportMap] = useState({}); + const navigate = useNavigate(); + const location = useLocation(); + const [searchParams, setSearchParams] = useSearchParams(); + const [years, setYears] = useState(null); + const [startDate, setStartDate] = useState(null); + const [endDate, setEndDate] = useState(null); + const [includes, setIncludes] = useState(null); + + console.log("Report: calling useEffect"); + useEffect(() => { + console.log("Report: useEffect"); + console.log("searchParams", searchParams); + console.log('location', location); + let tmpYears = searchParams.get("years"); + if(tmpYears == null && location.state != null) { + tmpYears = location.state.years; + } + setYears(tmpYears); + let tmpStartDate = searchParams.get("startDate"); + if(tmpStartDate == null && location.state != null) { + tmpStartDate = location.state.startDate; + } + setStartDate(tmpStartDate); + let tmpEndDate = searchParams.get("endDate"); + if(tmpEndDate == null && location.state != null) { + tmpEndDate = location.state.endDate; + } + setEndDate(tmpEndDate); + let tmpIncludes = searchParams.get("includes"); + if(tmpIncludes == null && location.state != null) { + tmpIncludes = location.state.includes; + } + setIncludes(tmpIncludes); + window.localStorage.setItem('report-years', tmpYears); + window.localStorage.setItem('report-startDate', tmpStartDate); + window.localStorage.setItem('report-endDate', tmpEndDate); + window.localStorage.setItem('report-includes', tmpIncludes); + console.log(tmpYears, tmpStartDate, tmpEndDate, tmpIncludes); + report(tmpYears, tmpStartDate, tmpEndDate, tmpIncludes).then( + data => { + console.log("Report.useEffect report() data", data); + if(data.status === 401) { + navigate('/login', {replace: true}); + return; + } + if(data.status !== 200) { + setIsLoaded(true); + setError(data); + return; + } + setIsLoaded(true); + setReportMap(data.result); + }, + error => { + if(error.status === 401) { + navigate('/login', {replace: true}); + return; + } + console.log('error object', error); + setIsLoaded(true); + setError(error); + } + ); + }, [navigate, location, searchParams]); + + if(error) { + return
Error: {error.message}
; + } + + if(!isLoaded) { + return
Loading...
; + } + + const getFlagNames = function(flags) { + let names = []; + if(flags & 0x02) { + names.push("Case Insensitive"); + } + if ((flags & 0x08) !== 0) { + names.push("Multiline"); + } + if ((flags & 0x20) !== 0) { + names.push("Dotall"); + } + if ((flags & 0x40) !== 0) { + names.push("Unicode Case"); + } + if ((flags & 0x80) !== 0) { + names.push("Canon EQ"); + } + if ((flags & 0x01) !== 0) { + names.push("Unix Linex"); + } + if ((flags & 0x10) !== 0) { + names.push("Literal"); + } + if ((flags & 0x100) !== 0) { + names.push("Unicode Character Class"); + } + if ((flags & 0x04) !== 0) { + names.push("Comments"); + } + return names.join(", "); + } + + let renderCategoryName = (category, indent) => { + if(category.details.length > 0) { + return ( + + { category.name } + + ) + } + return ( + + { category.name } + + ) + }; + + const numberFormat = new Intl.NumberFormat("en-US", { style: 'currency', currency: 'USD' }); + + let renderMonths = (category) => { + let cols = []; + for(let monthNum = 0; monthNum < category.monthCount; monthNum++) { + let amount = category.monthGrandTotals[monthNum]; + if(amount == null) { + amount = 0; + } + let style = {textAlign: "right", display: "none"}; + if(category.largestMonth === monthNum) { + style["color"] = "RED"; + } + cols.push({numberFormat.format(amount)}); + } + return cols; + }; + + let renderDetail = (key, category) => { + let rows = []; + for(let detail of category.details) { + rows.push( + {detail.transactionId} + {detail.source} + {detail.description} + {detail.date} + {numberFormat.format(detail.amount)} + {detail.extraDescription} + {detail.regex} + {getFlagNames(detail.flags)} + {detail.requiredSource} + ) + } + return rows; + }; + + let handleCategoryNameClick = (e) => { + // alert("clicked " + e.target); + let row = e.target.parentElement.nextSibling; + let d = row.style.display; + row.style.display = d === "none" ? "" : "none"; + }; + + let renderCategory = (level, year, category) => { + let indent = level * 10; + let result = []; + let key = "key-" + year + "-" + category.id; + result.push( + <> + + { renderCategoryName(category, indent) } + + { numberFormat.format(category.grandAverage) } + + + { numberFormat.format(category.grandTotal) } + + { renderMonths(category) } + + + + + + + + + + + + + + + + + + + { renderDetail(key, category) } + +
IDSourceDescriptionDateAmountExtra DescriptionPatternFlagsRequired Source
+ + + + ); + for(let childCategory of category.childCategories) { + result.push(renderCategory(level + 1, year, childCategory)); + } + return result; + }; + + const toReport = (years, startDate, endDate, includes) => { + console.log("toReport", years, startDate, endDate, includes); + navigate('/report-params', {replace: true, state: { + years: years, + startDate: startDate, + endDate: endDate, + includes: includes + }}); + }; + + const showColumn = (e, name, doNotCheckAll) => { + let cols = document.body.querySelectorAll("table.report td." + name + ",th." + name); + for(let col of cols) { + col.style.display = e.target.checked ? "" : "none"; + } + if(!doNotCheckAll) { + let inputs = document.body.querySelectorAll("table.parameters label input:not(.all)"); + var allChecked = true; + for(let input of inputs) { + if(!input.checked) { + allChecked = false; + break; + } + } + let input = document.body.querySelector("table.parameters label input.all"); + input.checked = allChecked; + } + else { + let input = document.body.querySelector("table.parameters label input." + name); + input.checked = e.target.checked; + } + }; + + const showAllColumns = (e) => { + showColumn(e, "average", true); + showColumn(e, "total", true); + showColumn(e, "month0", true); + showColumn(e, "month1", true); + showColumn(e, "month2", true); + showColumn(e, "month3", true); + showColumn(e, "month4", true); + showColumn(e, "month5", true); + showColumn(e, "month6", true); + showColumn(e, "month7", true); + showColumn(e, "month8", true); + showColumn(e, "month9", true); + showColumn(e, "month10", true); + showColumn(e, "month11", true); + }; + + console.log("report render", report); + return ( + <> +

Report

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Years{years}
Start date{startDate}
End date{endDate}
Includes{includes} +
+
+ {toReport(years, startDate, endDate, includes)}}>Rerun +
+ { + Object.values(reportMap).map(yearReport => { + let style = {textAlign: "right", display: "none"}; + return ( + <> +

{yearReport.year}

+ + + + + + + + + + + + + + + + + + + + + + { renderCategory(0, yearReport.year, yearReport.rootCategory) } + +
CategoryAverageTotalJanFebMarAprMayJunJulAugSepOctNovDec
+ + ); + }) + } + + ); +} + +export default Report; \ No newline at end of file diff --git a/src/ReportParams.jsx b/src/ReportParams.jsx new file mode 100644 index 0000000..9ced202 --- /dev/null +++ b/src/ReportParams.jsx @@ -0,0 +1,98 @@ +import React from 'react'; +import {useState, useEffect} from "react"; +import {useSearchParams, useNavigate} from 'react-router-dom'; + +const ReportParamsForm = (props) => { + const navigate = useNavigate(); + const [inputs, setInputs] = useState({}); + const [searchParams, setSearchParams] = useSearchParams(); + + console.log("ReportParamsForm: calling useEffect"); + useEffect(() => { + console.log("ReportParamsForm: useEffect"); + console.log("searchParams", searchParams); + let years = searchParams.get("years") || window.localStorage.getItem('report-years'); + let startDate = searchParams.get("startDate") || window.localStorage.getItem('report-startDate'); + let endDate = searchParams.get("endDate") || window.localStorage.getItem('report-endDate'); + let includes = searchParams.get("includes") || window.localStorage.getItem('report-includes'); + let tmpInputs = {}; + if(years) { + tmpInputs.years = years == "null" ? null : years; + } + if(startDate) { + tmpInputs.startDate = startDate == 'null' ? null : startDate; + } + if(endDate) { + tmpInputs.endDate = endDate == 'null' ? null : endDate; + } + if(includes) { + tmpInputs.includes = includes == 'null' ? null : includes; + } + setInputs(tmpInputs); + }, [navigate, searchParams]); + + const handleChange = event => { + const name = event.target.name; + const value = event.target.value; + setInputs(values => ({...values, [name]: value})); + console.log("handleChange", inputs); + } + + const handleSubmit = event => { + event.preventDefault(); + console.log("handleSubmit", inputs); + window.localStorage.setItem('report-years', inputs.years); + window.localStorage.setItem('report-startDate', inputs.startDate); + window.localStorage.setItem('report-endDate', inputs.endDate); + window.localStorage.setItem('report-includes', inputs.includes); + navigate('/report',{state:inputs}); + } + + console.log("ReportParamsForm: render, inputs", inputs); + return ( + <> +
+

Report Parameters

+ + + + + + + + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ + ) +} + +export default ReportParamsForm; \ No newline at end of file diff --git a/src/apiService.jsx b/src/apiService.jsx index 73e82fd..b42208e 100644 --- a/src/apiService.jsx +++ b/src/apiService.jsx @@ -1,4 +1,4 @@ -const baseUrl = 'http://localhost:9090'; +const baseUrl = 'http://kirk:9090'; const fetchOptions = (method, data) => { let token = window.localStorage.getItem('token'); @@ -76,6 +76,52 @@ export const getRegexesBySource = source => { }); }; +export const categorize = (redo) => { + let url = redo ? `${baseUrl}/categorize` : `${baseUrl}/problems`; + return fetch(url, fetchOptions('GET')).then(response => { + // console.log(url, "response", response); + if(response.status !== 200) { + return new Promise((resolve, reject) => { + reject({status: response.status}); + }); + } + return response.json(); // promise + }); +} + +export const report = (years, startDate, endDate, includes) => { + let url = `${baseUrl}/report`; + let params = {}; + if(years) { + params.years = years; + } + if(startDate) { + params.startDate = startDate; + } + if(endDate) { + params.endDate = endDate; + } + if(includes) { + params.includes = includes; + } + var sep = "?"; + for(let key in params) { + let value = params[key]; + url += sep; + sep = "&"; + url += key + "=" + value; + } + return fetch(url, fetchOptions('GET')).then(response => { + // console.log(url, "response", response); + if(response.status !== 200) { + return new Promise((resolve, reject) => { + reject({status: response.status}); + }); + } + return response.json(); // promise + }); +} + export const getRegexes = () => { let url = `${baseUrl}/regexes`; return fetch(url, fetchOptions('GET')).then(response => { @@ -222,52 +268,79 @@ export const createCategory = (dataResult, parentCategoryId) => ({ name: dataResult.name }); +const alphabetize = (siblings) => { + siblings.sort(function(c1, c2) { + if(c1.name < c2.name) { + return -1; + } + if(c1.name > c2.name) { + return 1; + } + return 0; + }); + return siblings; +}; + export const getCategoryAncestry = categoryId => { - let url = `${baseUrl}/categories/ancestry/${categoryId}`; - return fetch(url, fetchOptions('GET')).then(response => { - // console.log(url, "response", response); - if(response.status !== 200) { + if(categoryId) { + let url = `${baseUrl}/categories/ancestry/${categoryId}`; + return fetch(url, fetchOptions('GET')).then(response => { + // console.log(url, "response", response); + if(response.status !== 200) { + return new Promise((resolve, reject) => { + reject({status: response.status}); + }); + } + return response.json(); // promise + }).then(data => { + // console.log('getCategoryAncestry data', categoryId, data); + if(data.status !== 200) { + return new Promise((resolve, reject) => { + reject(data); + }); + } return new Promise((resolve, reject) => { - reject({status: response.status}); - }); - } - return response.json(); // promise - }).then(data => { - // console.log('getCategoryAncestry data', categoryId, data); - if(data.status !== 200) { - return new Promise((resolve, reject) => { - reject(data); - }); - } - return new Promise((resolve, reject) => { - let ancestry = data.result; - let getSelectionList = index => { - if(index >= ancestry.length) { - let lastCategoryId = ancestry.length > 0 ? ancestry[ancestry.length - 1].id : null; - let category = {parentCategoryId: lastCategoryId}; + let ancestry = data.result; + let getSelectionList = index => { + if(index >= ancestry.length) { + let lastCategoryId = ancestry.length > 0 ? ancestry[ancestry.length - 1].id : null; + let category = {parentCategoryId: lastCategoryId}; + getCategories(category.parentCategoryId).then(siblings => { + // console.log("siblings", siblings); + category.siblings = alphabetize(siblings); + ancestry.push(category); + resolve(ancestry); + }); + return; + } + let category = ancestry[index]; + category.siblings = []; getCategories(category.parentCategoryId).then(siblings => { // console.log("siblings", siblings); - category.siblings = siblings; - ancestry.push(category); - resolve(ancestry); + category.siblings = alphabetize(siblings); + getSelectionList(index + 1); }); - return; - } - let category = ancestry[index]; - category.siblings = []; - getCategories(category.parentCategoryId).then(siblings => { - // console.log("siblings", siblings); - category.siblings = siblings; - getSelectionList(index + 1); - }); - }; - getSelectionList(0); + }; + getSelectionList(0); + }); + }, error => { + return new Promise((resolve, reject) => { + reject(error); + }); }); - }, error => { + } + else { return new Promise((resolve, reject) => { - reject(error); + let category = {}; + getCategories(category.parentCategoryId).then(siblings => { + // console.log("siblings", siblings); + category.siblings = alphabetize(siblings); + let ancestry = [category]; + resolve(ancestry); + }); + return; }); - }); + } }; export const saveCategory = category => { diff --git a/src/index.jsx b/src/index.jsx index da6ca5a..6aab26a 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -4,15 +4,20 @@ import LoginForm from './LoginForm'; import {BrowserRouter, Routes, Route, Link} from "react-router-dom"; import Regexes from "./Regexes"; import EditRegex from "./EditRegex"; +import Categorize from "./Categorize"; +import Report from "./Report"; +import ReportParamsForm from "./ReportParams"; import './main.css'; export default function App() { let token = window.localStorage.getItem('token'); - console.log('token', token); + console.log('App: token', token); return ( Regexes   - Week   + Categorize   + Problems   + Report   Log out  
@@ -20,6 +25,9 @@ export default function App() { }/> }/> + }/> + }/> + }/> }/> }/> }/> diff --git a/src/main.css b/src/main.css index eeaa89c..5cc79bc 100644 --- a/src/main.css +++ b/src/main.css @@ -2,6 +2,18 @@ body { padding: 40px; font-family: sans-serif; } +table.mat tr td { + border: 1px solid #CCC; +} +table.ut tr td { + border: 1px solid #CCC; +} +table.mat tr td.amount { + text-align: right; +} +table.ut tr td.amount { + text-align: right; +} table.week tr td.duration { text-align: right; } @@ -24,6 +36,9 @@ div.header-date, div.header-links { div.header-date { margin-right: 5px; } +table.detail tr th,td { + padding-right: 20px; +} @media only screen and (max-width: 640px) { body { padding: 0px; diff --git a/start b/start index 39cfe6b..284b5cb 100755 --- a/start +++ b/start @@ -1,4 +1,4 @@ #!/bin/sh npm run build > build.log 2> build.err.log -serve -s --no-port-switching -l 3000 build > start.log 2> start.err.log & +serve -s --no-port-switching -l 3001 build > start.log 2> start.err.log & echo "$!" > pid \ No newline at end of file