Job Costing #6 – Static App

This post is part of a series on an ongoing project.

Now that I have my React frontend app bundled together and deployed in a single JAR file with my backend Maven/Spring/Kotlin project, I am going to turn to build a completely static version of the frontend app.

Inputs

Let’s start with the app’s inputs. There are three major inputs to the workflow, and they are three different Microsoft Excel files. One file is the list of jobs, one file is the list of employees and associated costs, and the last file is the data from the client’s time reporting system.

The existing Swing app (that the new app will replace) expects those files to be local to the computer running the app. It uses a “File -> Open” style dialog box to let the user select the file, and then it reads it from disk.

One of my project goals is to minimize the changes to the existing program logic, because that logic is not protected by test cases. Thus, I am going to leave the logic which expects the input files to be stored locally. Because the app isn’t going to be running on the user’s machine, the app will allow the user to upload the files to the server. Then, the existing program logic can operate on those files as it did in the Swing app.

To add this function to my frontend React app, I am leveraging this File Upload Component with React. I’m going to make some small changes such as limiting the number of files to 1 file per component, and then I’m going to have three different components – one for each different file.

The only complication is that one of the files (the one containing employee cost data) is encrypted and requires a password to decrypt it.

Parameters

In addition to the three files, I need a control that allows the user to specify some parameters that affect how the costs are allocated to the various jobs. This control will also report some basic information from the input files (such as the earliest/latest dates of time reported in the information from the time report file). So this control will be used for both input & output. That’s a complication, but I don’t have to worry about that right now.

Output

Then there are essentially two output controls. One control shows a text based report of warnings and error conditions with the data out of the time reporting system. The other control allows the user to download the output file – which contains the journal entry instructions to be fed into the client’s QuickBooks.

Build

So let’s build it! To start, I’ll change my App.js file to render basic layout. The function also references several other React components, which I’ll delve into later.

return (
    <div className="App">
        <Column>
            <Row>
                <Card width="15em" title="Project Sheet">
                    <InputFile/>
                </Card>
                <Card width="15em" title="Employee Cost">
                    <EncryptedInputFile/>
                </Card>
                <Card width="15em" title="Time Report">
                    <InputFile/>
                </Card>
            </Row>
            <Row>
                <Column>
                    <Card width="15em" title="Parameters">
                        <Parameters/>
                    </Card>
                    <Card width="15em" title="Import File">
                        <OutputFile/>
                    </Card>
                </Column>
                <Card width="31em" title="Information">
                    <OutputText/>
                </Card>
            </Row>
        </Column>
    </div>
);

Layout Components

The App render uses a few layout components: <Row>, <Column>, and <Card>. The card component simply defines a floating white box with a title and children components.

/* Card.js */
function Card(props) {
    let style = {
        width: props.width,
    };

    return (
        <div className="Card" style={style}>
            <span className="Title">{props.title}</span>
            {props.children}
        </div>
    );
}

/* Card.css */
.Card {
    background-color: white;
    margin: .5em;
    padding: 0.25em 1em 0.5em 1em;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
    box-shadow: .625em .625em .625em rgba(0, 0, 0, 0.16),
    .25em .25em .625em rgba(0, 0, 0, 0.08);
    box-sizing: border-box;
}

The row and column components simply use flex-box to arrange their children in a row or column, respectively.

/* Row.js */
function Row(props) {
    return (
        <div className="Row">
            {props.children}
        </div>
    );
}

/* Row.css */
.Row {
    text-align: center;
    display: flex;
    flex-direction: row;
    align-items: stretch;
    justify-content: stretch;
}

/* Column.js */
function Column(props) {
    return (
        <div className="Column">
            {props.children}
        </div>
    );
}

/* Column.css */
.Column {
    text-align: center;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-start;
}

File Inputs

The InputFile component is also fairly simple. It wraps a Dropzone component and renders a ‘Clear’ button. It also allows a sub-class to add other components within the same control. I’m going to use this for the encrypted employee cost file control. Let’s take a look.

export class InputFile extends React.Component {

    renderOthers = () => {};

    renderActions = () => {
        return (
            <button>
                Clear
            </button>
        );
    };

    render() {
        return (
            <div className="InputFile">
                <Dropzone
                    text={text}
                    complete={false}
                    isError={isError}
                    isUploading={isUploading}
                    onFileAdded={onFileAdded}
                    onClear={onClear}
                />
                {this.renderOthers()}
                <div className="Actions">{this.renderActions()}</div>
            </div>
        )
    }
}

The EncryptedInputFile component extends the InputFile component and simply overrides the renderOthers() function to allow the user to enter a password.

class EncryptedInputFile extends InputFile {

    obSubmit = () => {
    };

    renderOthers = () => {
        return (
            <form className="Form">
                <div className="FormDiv">
                    Password:
                </div>
                <div className="FormDiv">
                    <input type="password" size="12"/>
                </div>
            </form>
        );
    }
}

Parameters

The Parameters component is simply a form with label/field pairs. I’ve given it a stub onSubmit() function for now. It also has an ‘Analyze’ button which will call a service to populate the fields with default values.

class Parameters extends React.Component {

    onSubmit = () => {
    };

    render() {
        return(
            <div className="Parameters">
                <form onSubmit={this.onSubmit()} className="Form">
                    <div className="FormDiv">
                        <label>Earliest Date:</label>
                        <label>Latest Date:</label>
                        <label>Number of Work Days:</label>
                        <br/>
                        <label>Full Time Hours:</label>
                        <label>Too Few Hours:</label>
                        <label>Too Many Hours:</label>
                    </div>
                    <div className="FormDiv">
                        <input type="date" id="earliestDate" name="Earliest Date" readOnly/>
                        <input type="date" id="latestDate" name="Latest Date" readOnly/>
                        <input type="number" id="workDays" name="Number of work days" size="5"/>
                        <br/>
                        <input type="number" id="fullHours" name="Full time hours" size="5"/>
                        <input type="number" id="tooFewHours" name="Too few hours" size="5"/>
                        <input type="number" id="tooManyHours" name="Too many hours" size="5"/>
                    </div>
                </form>
                <div>
                    <button>Analyze</button>
                </div>
            </div>
        )
    }
}

Outputs

Finally, I need to define the two output components. I’ll start with the OutputFile component.

class OutputFile extends React.Component {

    onSubmit = () => {
    };

    render() {
        return(
            <div className="OutputFile">
                <form onSubmit={this.onSubmit()} className="Form">
                    <div className="FormDiv">
                        <label>Journal Entry Date:</label>
                    </div>
                    <div className="FormDiv">
                        <input type="date" id="qbDate" name="Journal entry import date" size="5" readOnly/>
                    </div>
                </form>
                <div>
                    <button>Download File</button>
                </div>
            </div>
        )
    }
}

And then the OutputText component prepopulated with some lorem ipsum.

class OutputText extends React.Component {
    render() {
        return (
            <div className="OutputText">
                Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Faucibus scelerisque eleifend donec pretium vulputate sapien nec sagittis. Iaculis at erat pellentesque adipiscing commodo elit at imperdiet dui. Vitae tortor condimentum lacinia quis vel eros. Sed turpis tincidunt id aliquet risus feugiat in ante metus. Urna id volutpat lacus laoreet non. Lorem donec massa sapien faucibus et molestie ac. Mattis vulputate enim nulla aliquet porttitor. Nec feugiat in fermentum posuere urna nec. Quis varius quam quisque id.<br/>
                <br/>
                Est ullamcorper eget nulla facilisi etiam dignissim. A lacus vestibulum sed arcu non odio. Mi bibendum neque egestas congue quisque. Porttitor massa id neque aliquam vestibulum. Purus non enim praesent elementum facilisis leo. Enim neque volutpat ac tincidunt vitae. Sagittis nisl rhoncus mattis rhoncus urna neque viverra. Est placerat in egestas erat imperdiet sed euismod. Arcu odio ut sem nulla pharetra diam. Accumsan tortor posuere ac ut consequat semper viverra. Felis donec et odio pellentesque diam volutpat. Id leo in vitae turpis. Iaculis urna id volutpat lacus. Ac turpis egestas maecenas pharetra convallis posuere.<br/>
                <br/>
                Congue quisque egestas diam in arcu. Laoreet suspendisse interdum consectetur libero id faucibus nisl tincidunt eget. Nibh nisl condimentum id venenatis a condimentum. Nunc lobortis mattis aliquam faucibus purus in massa tempor nec. Porttitor eget dolor morbi non arcu risus quis varius quam. Amet nisl suscipit adipiscing bibendum. Ac tortor vitae purus faucibus. Sit amet consectetur adipiscing elit. Nisi scelerisque eu ultrices vitae auctor eu augue ut lectus. Sed libero enim sed faucibus turpis in eu mi bibendum. Dignissim cras tincidunt lobortis feugiat vivamus at augue eget arcu. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. Habitant morbi tristique senectus et.<br/>
                <br/>
                Ut ornare lectus sit amet est placerat in egestas. Orci porta non pulvinar neque laoreet suspendisse. Egestas pretium aenean pharetra magna ac. Sit amet cursus sit amet. Sodales neque sodales ut etiam sit amet nisl purus. Sit amet purus gravida quis blandit. Pretium fusce id velit ut tortor pretium. Consectetur purus ut faucibus pulvinar elementum. Velit egestas dui id ornare arcu odio. Amet justo donec enim diam vulputate ut pharetra sit amet. Orci dapibus ultrices in iaculis nunc sed.<br/>
                <br/>
                Sed libero enim sed faucibus turpis in. Ullamcorper morbi tincidunt ornare massa eget egestas purus. Pellentesque sit amet porttitor eget dolor morbi non. Et molestie ac feugiat sed lectus. Tempor id eu nisl nunc mi ipsum. Cursus mattis molestie a iaculis. Ac odio tempor orci dapibus ultrices in iaculis nunc. Cras ornare arcu dui vivamus arcu felis bibendum ut. Sagittis eu volutpat odio facilisis mauris sit amet massa. Morbi non arcu risus quis varius quam quisque. Malesuada pellentesque elit eget gravida cum sociis natoque penatibus et. Donec massa sapien faucibus et molestie.<br/>
            </div>
        );
    }
}

Preview

After all the definitions, I view the app in the browser, and everything looks pretty good at this point!

A look at the static app

Wrap Up

There you have a static version of the app. The next step is to start making it interactive and building the user functions. I’ll start with adding the file upload function, but that’s a topic for another post.

Leave a comment