Skip to content

Commit 402636f

Browse files
committed
Ability to create, delete edit and duplicate existing reported records
1 parent 4c0b712 commit 402636f

22 files changed

+181
-162
lines changed

README.md

+7-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Web application to report consulting hours on projects using selected rates on a
1616
- (dates should be entered in ISO format YYYY-MM-DD HH:MM:SS)
1717
- consultants, projects, rates, holidays, initial reported records - optional
1818

19+
# Screenshots
20+
21+
![Main screen](screenshots/main_screen.png?raw=true "Main screen")
22+
1923
# Download
2024
* [Releases](https://github.com/valasek/timesheet/releases/) (MS Windows 64bit, Linux 64bit, Mac OS X 64bit)
2125

@@ -63,8 +67,6 @@ Use "timesheet [command] --help" for more information about a command.
6367
# Todo
6468

6569
- IMPORTANT
66-
- create new record
67-
- duplicate record
6870
- always show full week, calculate month inputs correctly
6971
- auto retrieve weekly data from backend when week is changed
7072
- overview
@@ -75,12 +77,14 @@ Use "timesheet [command] --help" for more information about a command.
7577

7678
## Fixes
7779

80+
- implement rollback in js store if DB fails
7881
- Edit records
7982
- do not save the value id ESC is pressed
8083

8184
## Improvements business
8285
- Ability to lock last week
83-
- show only available rates per project
86+
- Add validations on entered data
87+
- Show only available rates per project
8488
- Paginate and sort server-side - using vuetify data table
8589
- Consistency checks
8690
- Add billing evidence

api/reportedRecords.go

+7-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package api
22

33
import (
44
"fmt"
5+
"strconv"
56

67
"github.com/valasek/timesheet/models"
78

@@ -32,11 +33,12 @@ func (api *API) ReportedRecordsInMonth(w http.ResponseWriter, req *http.Request)
3233
func (api *API) ReportedRecordDelete(w http.ResponseWriter, req *http.Request) {
3334
vars := mux.Vars(req)
3435
ID := vars["id"]
35-
if len(ID) < 1 {
36-
fmt.Println("ReportedRecordsDelete, param 'id' is missing")
36+
IDn, err := strconv.ParseUint(ID, 10, 32)
37+
if err != nil {
38+
fmt.Println("ReportedRecordsDelete, param 'id' is missing:", err)
3739
return
3840
}
39-
reportedRecords := api.reportedRecords.ReportedRecordsDelete(ID)
41+
reportedRecords := api.reportedRecords.ReportedRecordsDelete(IDn)
4042
json.NewEncoder(w).Encode(reportedRecords)
4143
}
4244

@@ -64,11 +66,6 @@ func (api *API) ReportedRecordsAddRecord(w http.ResponseWriter, req *http.Reques
6466
fmt.Println("unable to decode reported record, error: ", err)
6567
return
6668
}
67-
fmt.Println(reportedRecord)
68-
69-
fmt.Println("ReportedRecordsAddRecord")
70-
fmt.Println(req.Body)
71-
72-
id := "1234"
73-
json.NewEncoder(w).Encode(id)
69+
addedReportedRecord := api.reportedRecords.ReportedRecordAdd( reportedRecord )
70+
json.NewEncoder(w).Encode(addedReportedRecord)
7471
}

build.bat

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ IF EXIST .\build\timesheet.exe del .\build\timesheet.exe
77
IF EXIST .\build\timesheet.app del .\build\timesheet.app
88
IF EXIST .\build\timesheet.bin del .\build\timesheet.bin
99
IF EXIST .\build\timesheet.yaml del .\build\timesheet.yaml
10-
IF EXIST .\build\MS_Windows_64b.zip del .\build\MS_Windows_64b.zip
11-
IF EXIST .\build\Linux_64b.zip del .\build\Linux_64b.zip
12-
IF EXIST .\build\Mac_OS_X_64b.zip del .\build\Mac_OS_X_64b.zip
10+
IF EXIST .\build\MS_Windows_64bit.zip del .\build\MS_Windows_64bit.zip
11+
IF EXIST .\build\Linux_64bit.zip del .\build\Linux_64bit.zip
12+
IF EXIST .\build\Mac_OS_X_64bit.zip del .\build\Mac_OS_X_64bit.zip
1313
IF EXIST ".\build\client\dist\" @RD /S /Q ".\build\client\dist"
1414

1515
ECHO ======================
@@ -38,9 +38,9 @@ go build -o .\build\timesheet.app .\timesheet.go
3838
ECHO =========================
3939
ECHO Compressing artifacts ...
4040
cd .\build
41-
call %zip% a -r MS_Windows_64b.zip timesheet.exe timesheet.yaml client/
42-
call %zip% a -r Linux_64b.zip ./timesheet.bin ./timesheet.yaml client/
43-
call %zip% a -r Mac_OS_X_64b.zip ./timesheet.app ./timesheet.yaml client/
41+
call %zip% a -r MS_Windows_64bit.zip timesheet.exe timesheet.yaml client/
42+
call %zip% a -r Linux_64bit.zip ./timesheet.bin ./timesheet.yaml client/
43+
call %zip% a -r Mac_OS_X_64bit.zip ./timesheet.app ./timesheet.yaml client/
4444
cd ..
4545

4646
ECHO ===========

client/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "timesheet",
3-
"version": "0.0.3",
3+
"version": "0.0.4",
44
"private": true,
55
"scripts": {
66
"serve": "vue-cli-service serve",

client/src/components/App.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
</v-content>
109109
<v-footer app>
110110
<v-flex text-xs-center>
111-
2018 &copy; <strong>Stanislav Valasek</strong>
111+
2018 - {{ (new Date()).getFullYear() }} &copy; <strong>Stanislav Valasek</strong>
112112
</v-flex>
113113
</v-footer>
114114
</v-app>

client/src/components/ReportTable.vue

+17-15
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<!-- <v-menu :value="props.item.date" :close-on-content-click="true" :nudge-right="40" lazy transition="scale-transition" offset-y full-width min-width="290px"> -->
1818
<v-menu :close-on-content-click="true" :nudge-right="40" lazy transition="scale-transition" offset-y full-width min-width="290px" @keyup.esc="model = false">
1919
<v-text-field slot="activator" :value="formatDate(props.item.date)" readonly />
20-
<v-date-picker first-day-of-week="1" :value="props.item.date" @input="onUpdateDate({_id: props.item._id, date: $event})" />
20+
<v-date-picker first-day-of-week="1" :value="props.item.date" @input="onUpdateDate({id: props.item.id, date: $event})" />
2121
</v-menu>
2222
<!-- {{ formatDate(props.item.date) }} -->
2323
</td>
@@ -26,7 +26,7 @@
2626
{{ props.item.hours }}
2727
<v-text-field slot="input" :value="props.item.hours" label="Hours" single-line
2828
type="number" min="0" max="20" step="0.5" maxlength="2"
29-
@change="onUpdateHours({_id: props.item._id, hours: $event})"
29+
@change="onUpdateHours({id: props.item.id, hours: $event})"
3030
/>
3131
</v-edit-dialog>
3232
<!-- {{ props.item.hours }} -->
@@ -36,7 +36,7 @@
3636
{{ props.item.project }}
3737
<v-select slot="input" :value="props.item.project" item-text="name" item-value="name"
3838
:items="assignedProjects" label="Project" :dense="true" :hide-selected="true"
39-
@change="onUpdateProject({_id: props.item._id, project: $event})"
39+
@change="onUpdateProject({id: props.item.id, project: $event})"
4040
/>
4141
</v-edit-dialog>
4242
<!-- {{ props.item.project }} -->
@@ -45,7 +45,7 @@
4545
<v-edit-dialog :return-value="props.item.description" lazy>
4646
{{ props.item.description }}
4747
<v-text-field slot="input" :value="props.item.description" :rules="[ruleMax100chars]" label="Description" single-line
48-
counter @change="onUpdateDescription({_id: props.item._id, description: $event})"
48+
counter @change="onUpdateDescription({id: props.item.id, description: $event})"
4949
/>
5050
</v-edit-dialog>
5151
<!-- {{ props.item.description }} -->
@@ -55,7 +55,7 @@
5555
{{ props.item.rate }}
5656
<v-select slot="input" :value="props.item.rate" item-text="name" item-value="name"
5757
:items="rates" label="Rate" :dense="true" :hide-selected="true"
58-
@change="onUpdateRate({_id: props.item._id, rate: $event})"
58+
@change="onUpdateRate({id: props.item.id, rate: $event})"
5959
/>
6060
</v-edit-dialog>
6161
<!-- {{ props.item.rate }} -->
@@ -80,6 +80,7 @@
8080

8181
<script>
8282
import { mapState } from 'vuex'
83+
import moment from 'moment-timezone'
8384
8485
export default {
8586
data: () => ({
@@ -146,39 +147,39 @@
146147
methods: {
147148
onUpdateProject (newValue) {
148149
let payload = {
149-
id: newValue._id,
150+
id: newValue.id,
150151
type: 'project',
151152
value: newValue.project
152153
}
153154
this.$store.dispatch('reportedHours/updateAttributeValue', payload)
154155
},
155156
onUpdateDate (newValue) {
156157
let payload = {
157-
id: newValue._id,
158+
id: newValue.id,
158159
type: 'date',
159160
value: newValue.date
160161
}
161162
this.$store.dispatch('reportedHours/updateAttributeValue', payload)
162163
},
163164
onUpdateHours (newValue) {
164165
let payload = {
165-
id: newValue._id,
166+
id: newValue.id,
166167
type: 'hours',
167168
value: newValue.hours
168169
}
169170
this.$store.dispatch('reportedHours/updateAttributeValue', payload)
170171
},
171172
onUpdateDescription (newValue) {
172173
let payload = {
173-
id: newValue._id,
174+
id: newValue.id,
174175
type: 'description',
175176
value: newValue.description
176177
}
177178
this.$store.dispatch('reportedHours/updateAttributeValue', payload)
178179
},
179180
onUpdateRate (newValue) {
180181
let payload = {
181-
id: newValue._id,
182+
id: newValue.id,
182183
type: 'rate',
183184
value: newValue.rate
184185
}
@@ -195,24 +196,25 @@
195196
},
196197
addItem (item) {
197198
let newRecord = {}
198-
newRecord._id = null
199+
newRecord.id = null
199200
newRecord.consultant = this.selectedConsultants
200201
console.log('from:', this.dateFrom) /* eslint-disable-line no-console */
201-
newRecord.date = this.dateFrom.format('YYYY-MM-DD')
202+
newRecord.date = this.dateFrom.format('YYYY-MM-DDTHH:mm:ssZ')
202203
console.log('date to ISO:', newRecord.date) /* eslint-disable-line no-console */
203204
newRecord.hours = 8
204205
newRecord.rate = 'Off-site'
206+
newRecord.project = ''
205207
this.$store.dispatch('reportedHours/addRecord', newRecord)
206208
},
207209
duplicateItem (item) {
208210
let newRecord = Object.assign({}, item)
209-
// let index = this.selectedReportedHours.indexOf(item)
210-
newRecord._id = null
211+
newRecord.id = null
212+
newRecord.date = moment(item.date).format('YYYY-MM-DDTHH:mm:ssZ')
211213
console.log(newRecord) /* eslint-disable-line no-console */
212214
this.$store.dispatch('reportedHours/addRecord', newRecord)
213215
},
214216
deleteItem (item) {
215-
confirm('Are you sure you want to delete the record?') && this.$store.dispatch('reportedHours/removeRecord', item._id)
217+
confirm('Are you sure you want to delete the record?') && this.$store.dispatch('reportedHours/removeRecord', parseInt(item.id, 10))
216218
this.$store.dispatch('context/setNotification', { text: this.formatDate(item.date) + ', ' + item.hours + ' hrs - record deleted', type: 'success' })
217219
}
218220
}

client/src/store/modules/projects.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import api from '../../api/axiosSettings'
22

33
// initial state
44
const state = {
5-
all: [ '' ] // _id, name
5+
all: [ '' ] // id, name
66
}
77

88
const getters = {}

client/src/store/modules/reportedHours.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import api from '../../api/axiosSettings'
22

33
// initial state
44
const state = {
5-
all: [], // _id, date, hours, project, description, rate, consultant
5+
all: [], // id, date, hours, project, description, rate, consultant
66
loading: true
77
}
88

@@ -28,7 +28,7 @@ const actions = {
2828
})
2929
},
3030
removeRecord ({ commit, dispatch }, id) {
31-
const index = state.all.findIndex(records => records._id === id)
31+
const index = state.all.findIndex(records => records.id === id)
3232
api.apiClient.delete('/api/reported/' + id)
3333
.then(response => {
3434
commit('REMOVE_RECORD', index)
@@ -40,8 +40,6 @@ const actions = {
4040
},
4141
addRecord ({ commit, dispatch }, payload) {
4242
console.log(payload) /* eslint-disable-line no-console */
43-
// payload.date = '2018–09–22T12:42:31+07:00'
44-
// console.log(payload) /* eslint-disable-line no-console */
4543
api.apiClient.post('/api/reported', payload)
4644
.then(response => {
4745
commit('ADD_RECORD', payload)
@@ -76,7 +74,7 @@ const mutations = {
7674
state.all.splice(index, 1)
7775
},
7876
UPDATE_ATTRIBUTE_VALUE (state, payload) {
79-
let index = state.all.findIndex(obj => obj._id === payload.id)
77+
let index = state.all.findIndex(obj => obj.id === payload.id)
8078
switch (payload.type) {
8179
case 'description':
8280
state.all[index].description = payload.value

cmd/root.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ var cfgFile string
1717
// rootCmd represents the base command when called without any subcommands
1818
var rootCmd = &cobra.Command{
1919
Use: "timesheet",
20-
Version: "timesheet 0.0.3",
20+
Version: "timesheet 0.0.4",
2121
Short: "Web based timesheet application with DB persistence",
2222
Long: `Web based timesheet application with DB persistence.
2323

data/consultants_demo.csv

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
id,created_at,name
2-
1,"2016-06-22 19:10:25","Stanislav Valasek"
3-
2,"2016-06-22 19:10:25","John Lasseter"
1+
created_at,name
2+
"2016-06-22 19:10:25","Stanislav Valasek"
3+
"2016-06-22 19:10:25","John Lasseter"

data/holidays_cz_2018.csv

+14-14
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
id,created_at,date,description
2-
1,"2018-12-30 20:00:00","2018-01-01","New Year's Day"
3-
2,"2018-12-30 20:00:00","2018-03-30","Good Friday"
4-
3,"2018-12-30 20:00:00","2018-04-02","Easter Monday"
5-
4,"2018-12-30 20:00:00","2018-05-01","May Day"
6-
5,"2018-12-30 20:00:00","2018-05-08","Liberation Day"
7-
6,"2018-12-30 20:00:00","2018-07-05","St Cyril and St Methodius Day"
8-
7,"2018-12-30 20:00:00","2018-07-06","Jan Hus Day"
9-
8,"2018-12-30 20:00:00","2018-09-28","Statehood Day"
10-
9,"2018-12-30 20:00:00","2018-10-28","Independence Day"
11-
10,"2018-12-30 20:00:00","2018-11-17","Freedom and Democracy Day"
12-
11,"2018-12-30 20:00:00","2018-12-24","Christmas Eve"
13-
12,"2018-12-30 20:00:00","2018-12-25","Christmas Day"
14-
13,"2018-12-30 20:00:00","2018-12-26","2nd Day of Christmas"
1+
created_at,date,description
2+
"2018-12-30 20:00:00","2018-01-01","New Year's Day"
3+
"2018-12-30 20:00:00","2018-03-30","Good Friday"
4+
"2018-12-30 20:00:00","2018-04-02","Easter Monday"
5+
"2018-12-30 20:00:00","2018-05-01","May Day"
6+
"2018-12-30 20:00:00","2018-05-08","Liberation Day"
7+
"2018-12-30 20:00:00","2018-07-05","St Cyril and St Methodius Day"
8+
"2018-12-30 20:00:00","2018-07-06","Jan Hus Day"
9+
"2018-12-30 20:00:00","2018-09-28","Statehood Day"
10+
"2018-12-30 20:00:00","2018-10-28","Independence Day"
11+
"2018-12-30 20:00:00","2018-11-17","Freedom and Democracy Day"
12+
"2018-12-30 20:00:00","2018-12-24","Christmas Eve"
13+
"2018-12-30 20:00:00","2018-12-25","Christmas Day"
14+
"2018-12-30 20:00:00","2018-12-26","2nd Day of Christmas"

data/holidays_us_2019.csv

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
id,created_at,date,description
2-
1,"2018-12-30 20:00:00","2019-01-01","New Year’s Day"
3-
2,"2018-12-30 20:00:00","2019-01-21","Birthday of Martin Luther King, Jr."
4-
3,"2018-12-30 20:00:00","2019-02-18","Washington’s Birthday"
5-
4,"2018-12-30 20:00:00","2019-05-27","Memorial Day"
6-
5,"2018-12-30 20:00:00","2019-07-04","Independence Day"
7-
6,"2018-12-30 20:00:00","2019-09-02","Labor Day"
8-
7,"2018-12-30 20:00:00","2019-10-14","Columbus Day"
9-
8,"2018-12-30 20:00:00","2019-11-11","Veterans Day"
10-
9,"2018-12-30 20:00:00","2019-11-28","Thanksgiving Day"
11-
10,"2018-12-30 20:00:00","2019-12-25","Christmas Day"
1+
created_at,date,description
2+
"2018-12-30 20:00:00","2019-01-01","New Year’s Day"
3+
"2018-12-30 20:00:00","2019-01-21","Birthday of Martin Luther King, Jr."
4+
"2018-12-30 20:00:00","2019-02-18","Washington’s Birthday"
5+
"2018-12-30 20:00:00","2019-05-27","Memorial Day"
6+
"2018-12-30 20:00:00","2019-07-04","Independence Day"
7+
"2018-12-30 20:00:00","2019-09-02","Labor Day"
8+
"2018-12-30 20:00:00","2019-10-14","Columbus Day"
9+
"2018-12-30 20:00:00","2019-11-11","Veterans Day"
10+
"2018-12-30 20:00:00","2019-11-28","Thanksgiving Day"
11+
"2018-12-30 20:00:00","2019-12-25","Christmas Day"

data/projects_demo.csv

+11-11
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
id,created_at,name
2-
1,"2018-12-19 20:00:00","Apple"
3-
2,"2018-12-19 20:00:00","Microsoft"
4-
3,"2018-12-19 20:00:00","_Travel Time"
5-
4,"2018-12-19 20:00:00","_Vacation"
6-
5,"2018-12-19 20:00:00","_Sick"
7-
6,"2018-12-19 20:00:00","_Unpaid Leave"
8-
7,"2018-12-19 20:00:00","_Public Holiday"
9-
8,"2018-12-19 20:00:00","_Sick Day"
10-
9,"2018-12-19 20:00:00","_Personal Day"
11-
10,"2018-12-19 20:00:00","_Internal"
1+
created_at,name
2+
"2018-12-19 20:00:00","Apple"
3+
"2018-12-19 20:00:00","Microsoft"
4+
"2018-12-19 20:00:00","_Travel Time"
5+
"2018-12-19 20:00:00","_Vacation"
6+
"2018-12-19 20:00:00","_Sick"
7+
"2018-12-19 20:00:00","_Unpaid Leave"
8+
"2018-12-19 20:00:00","_Public Holiday"
9+
"2018-12-19 20:00:00","_Sick Day"
10+
"2018-12-19 20:00:00","_Personal Day"
11+
"2018-12-19 20:00:00","_Internal"

0 commit comments

Comments
 (0)