Skip to content

Commit 802e9d6

Browse files
Merge pull request #208 from Worxstr/shift-features
Shift features
2 parents 75dea56 + f2838c9 commit 802e9d6

File tree

13 files changed

+247
-123
lines changed

13 files changed

+247
-123
lines changed

src/components/ClockButtons.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
v-btn(text color='success' @click='clockOut(true)' data-cy='confirm-clock-out-button') Yes, clock out
2020

2121
v-fade-transition
22-
v-overlay(v-if="loading", absolute, opacity=".2")
22+
v-overlay(v-if='togglingClock' absolute opacity='.2')
2323
v-progress-circular(indeterminate)
2424

2525
.d-flex.flex-row.justify-center.gap-small

src/components/ShiftList.vue

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ div
2929
v-list-item-content
3030
v-list-item-title
3131
router-link.alt-style.my-1.font-weight-medium(
32-
:to="{name: 'shift', params: {shiftId: shift.id}}"
32+
:to="{name: 'shift', params: {jobId: shift.job_id, shiftId: shift.id}}"
3333
) {{ shift.site_location }}
3434

3535
v-list-item-subtitle
@@ -40,13 +40,14 @@ div
4040

4141
//- span(v-if='!userIsManager') {Job name} • {n} tasks
4242
43-
v-chip.mx-4.px-2.flex-grow-0(
44-
v-if="shift.active",
45-
label,
46-
outlined,
47-
small,
48-
color="green"
49-
) Active
43+
//- // TODO
44+
//- v-chip.mx-4.px-2.flex-grow-0(
45+
//- v-if="shift.active",
46+
//- label,
47+
//- outlined,
48+
//- small,
49+
//- color="green"
50+
//- ) Active
5051
5152
v-list-item-action(v-if='userIsManager || i != 0')
5253
.d-flex.flex-column.align-end
@@ -77,7 +78,7 @@ div
7778
v-list-item-action(:class="{'ml-0': userIsManager}")
7879
v-btn(
7980
icon
80-
:to="{name: 'shift', params: {shiftId: shift.id}}"
81+
:to="{name: 'shift', params: {jobId: shift.job_id, shiftId: shift.id}}"
8182
)
8283
v-icon mdi-chevron-right
8384
</template>

src/components/UpcomingShiftList.vue

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
v-toolbar-title
55
h6.text-h6 Upcoming shifts
66

7-
div(v-if='!upcomingShifts.length')
7+
div(v-if='!loading && !upcomingShifts.length')
88
p.text-center
99
| You have no shifts assigned. Go have fun! 🥂🎉
1010

1111
div(v-else)
12-
shift-list(:shifts='upcomingShifts')
12+
shift-list(:shifts='upcomingShifts' :loading='loading')
1313

1414
.my-4.d-flex.justify-center
1515
v-btn(
@@ -39,7 +39,6 @@ export default class UpcomingShiftList extends Vue {
3939
loading = false
4040
4141
async mounted() {
42-
console.log(getUpcomingShifts)
4342
this.loadUpcomingShifts()
4443
}
4544

src/components/inputs/RecurringDateInput.vue

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
outlined
99
label='Start'
1010
hide-details
11-
:rules='rules.start'
1211
)
1312
//- End date
1413
datetime-input(
@@ -241,12 +240,6 @@ export default class RecurringDateInput extends Vue {
241240
bymonthday: [exists('Day required')],
242241
bysetpos: [exists('Ordinal required')],
243242
byweekday: [exists('Day required')],
244-
start: [
245-
exists('Start date required'),
246-
(v: string) => {
247-
return this.duration > 0 || 'Start date must be before end date'
248-
},
249-
],
250243
end: [
251244
exists('End date required'),
252245
(v: string) => {

src/layouts/Breadcrumbs.vue

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ v-breadcrumbs.nav-breadcrumbs.pl-1.d-flex.flex-nowrap(
33
:items="breadcrumbs",
44
large,
55
v-if="!$route.meta.landing"
6+
ref='scrollContainer'
67
)
78
template(v-slot:item="{ item }")
89
v-breadcrumbs-item.subtitle-1.font-weight-medium(
@@ -14,10 +15,26 @@ v-breadcrumbs.nav-breadcrumbs.pl-1.d-flex.flex-nowrap(
1415
</template>
1516

1617
<script lang="ts">
17-
import { Component, Vue } from 'vue-property-decorator'
18+
import { Component, Vue, Watch } from 'vue-property-decorator'
19+
20+
// Find a nested object by dot-syntax string
21+
// ex. lookup(obj, 'a.b.c') => obj.a.b.c
22+
function lookup(obj: any, path: string): any {
23+
return path.split('.').reduce((prev, curr) => {
24+
return prev ? prev[curr] : undefined
25+
}, obj)
26+
}
1827
1928
@Component
2029
export default class Breadcrumbs extends Vue {
30+
31+
@Watch('breadcrumbs')
32+
breadcrumbsChanged(newVal: any, oldVal: any) {
33+
// Scroll to right of container automatically
34+
const container: any = this.$refs.scrollContainer
35+
container.$el.scrollLeft = container.$el.scrollWidth
36+
}
37+
2138
get breadcrumbs() {
2239
2340
/* This generates breadcrumbs for the toolbar dynamically
@@ -26,70 +43,73 @@ export default class Breadcrumbs extends Vue {
2643
the route metadata can be used to map the parameter name to an item
2744
in the store state. For example, the metadata
2845
meta: {
29-
paramMap: {
30-
jobId: 'jobs',
46+
paramMap: [{
47+
param: 'jobId',
48+
store: 'jobs',
3149
prop: 'name'
32-
}
50+
}]
3351
}
3452
will replace the :jobId param with the 'name' property of the job in state.jobs
3553
that matches the id given.
3654
paramMap can also contain a prop 'propBuilder' that will specify how to build
3755
the text string
3856
*/
39-
57+
58+
// ex. ['jobs', '114']
4059
const segments = this.$route.path
4160
.replace('/', '')
4261
.split('/')
4362
63+
// ex. ['jobs', ':jobId]
4464
const matched = this.$route.matched[this.$route.matched.length - 1].path
4565
.replace('/', '')
4666
.split('/')
4767
4868
return segments.map((pathSegment, i) => {
4969
let dynamicName
5070
try {
51-
// Get param mapping from route metadata
52-
const paramMap = this.$route.meta?.paramMap
5371
// Extract the param name
72+
// ex. jobId
5473
const param = matched[i].replace(':', '')
55-
56-
// Get the path in the state of the object
57-
const defaultStatePath = segments[0]
58-
const customStatePath = paramMap[param]?.split('.') // Split the param map into an array of the nested state keys
74+
75+
// Get param mapping from route metadata
76+
// ex. { param: 'jobId', store: 'jobs', prop: 'name' }
77+
const paramMap = this.$route.meta?.paramMap.find((m: any) => m.param === param)
5978
6079
// Find the item in the store state
61-
let item = this.$store.state
62-
if (customStatePath) {
63-
// A custom path in the store was defined, ex. 'messages.conversations'
64-
customStatePath.forEach((key: string) => {
65-
item = item[key]
66-
})
67-
}
68-
else {
69-
// Use the first name in the path
70-
item = item[defaultStatePath]
71-
}
72-
item = item.byId[pathSegment]
80+
const item = lookup(this.$store.state, paramMap.store)
81+
.byId[this.$route.params[param]]
7382
7483
// Use the specified prop or propBuilder to get the name of the object
75-
dynamicName = paramMap.propBuilder ? paramMap.propBuilder(item) : item[paramMap.prop || 'name']
84+
dynamicName = paramMap.propBuilder
85+
? paramMap.propBuilder(item)
86+
: lookup(item, paramMap.prop || 'name')
7687
}
7788
catch (e) {
7889
dynamicName = pathSegment
7990
}
8091
81-
return {
82-
text: matched[i]?.includes(':') ? dynamicName : pathSegment,
83-
to: '/' + segments.slice(0, i + 1).join('/'),
92+
// Build link text and path
93+
const text = matched[i]?.includes(':') ? dynamicName : pathSegment
94+
const to = '/' + segments.slice(0, i + 1).join('/')
95+
96+
// Check if the link exists. If not, we only send back the text with no link
97+
const link = this.$router.resolve(to)
98+
const hasActiveRoute = link?.resolved?.name !== 'notFound'
99+
100+
if (hasActiveRoute) {
101+
return { text, to }
84102
}
103+
return { text }
85104
})
86105
}
87106
}
88107
</script>
89108

90109
<style lang="scss">
91110
.nav-breadcrumbs {
92-
overflow: hidden;
111+
overflow-y: hidden;
112+
overflow-x: auto;
93113
94114
li {
95115
white-space: nowrap;

src/layouts/NavDrawer.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export default class NavDrawer extends Vue {
101101
}
102102
103103
get mini() {
104-
return this.$store.state.app.preferences.miniNav
104+
return this.$vuetify.breakpoint.mdAndUp ? this.$store.state.app.preferences.miniNav : false
105105
}
106106
107107
toggleMiniNav() {

src/router/index.ts

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -201,22 +201,27 @@ const routes = [
201201
name: 'job',
202202
component: Job,
203203
meta: {
204-
paramMap: {
205-
jobId: 'jobs',
204+
paramMap: [{
205+
param: 'jobId',
206+
store: 'jobs',
206207
prop: 'name'
207-
}
208+
}]
208209
}
209210
},
210211
{
211-
path: '/shifts/:shiftId',
212+
path: '/jobs/:jobId/shifts/:shiftId',
212213
name: 'shift',
213214
component: Shift,
214215
meta: {
215-
paramMap: {
216-
// TODO: Need to use a getter for this. Come back to it
217-
// shiftId: 'shifts',
218-
// prop: ''
219-
}
216+
paramMap: [{
217+
param: 'jobId',
218+
store: 'jobs',
219+
prop: 'name'
220+
}, {
221+
param: 'shiftId',
222+
store: 'shifts',
223+
prop: 'site_location'
224+
}]
220225
}
221226
},
222227
{
@@ -233,7 +238,6 @@ const routes = [
233238
component: Schedule,
234239
meta: {
235240
icon: 'mdi-calendar-multiselect',
236-
fullHeight: true,
237241
hideNav: true,
238242
}
239243
},
@@ -251,10 +255,11 @@ const routes = [
251255
name: 'user',
252256
component: User,
253257
meta: {
254-
paramMap: {
255-
userId: 'users',
258+
paramMap: [{
259+
param: 'userId',
260+
store: 'users',
256261
propBuilder: fullName
257-
},
262+
}],
258263
}
259264
},
260265
{
@@ -272,12 +277,13 @@ const routes = [
272277
component: Conversation,
273278
meta: {
274279
fullHeight: true,
275-
paramMap: {
276-
conversationId: 'messages.conversations',
280+
paramMap: [{
281+
param: 'conversationId',
282+
store: 'messages.conversations',
277283
propBuilder(conversation: MessagesTypes.Conversation) {
278284
return groupNameList(conversation, usersStore.getters.me(usersStore.state))
279285
},
280-
},
286+
}],
281287
},
282288
},
283289
],

src/store/schedule.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ const getters = {
5050
else
5151
color = hashColor(event.job_id)
5252
break
53-
case 'user':
54-
if (rootState.users.byId[event.contractor_id]?.contractor_info?.color)
55-
color = rootState.users.byId[event.contractor_id]?.contractor_info?.color
53+
case 'contractor':
54+
if (rootState.users.byId[event.contractor_id]?.additional_info?.color)
55+
color = rootState.users.byId[event.contractor_id]?.additional_info?.color
5656
else
5757
color = hashColor(event.contractor_id)
5858
break

src/util/helpers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const colorsToUse: any = {...colors}
3434
delete colorsToUse.grey
3535
delete colorsToUse.shades
3636
delete colorsToUse.brown
37-
delete colorsToUse['blueGrey']
37+
delete colorsToUse.blueGrey
3838

3939
const colorList = Object.keys(colorsToUse)
4040
const shades = ['base', /* 'accent1', */ 'accent2', 'accent3', 'accent4']
@@ -50,7 +50,7 @@ export function hashColor(input: number | string) {
5050
}
5151
else num = input
5252

53-
const i = Math.floor(Math.PI * 1.8 * num) % (colorList.length * shades.length)
53+
const i = Math.floor(Math.PI * 3548 * num) % (colorList.length * shades.length)
5454
const color = colorList[i % colorList.length]
5555
const shade = shades[Math.floor(i / colorList.length)]
5656

0 commit comments

Comments
 (0)