Skip to content

Commit 56d1e60

Browse files
authored
feat: Introduce schedule management and calendar components (#1327)
* feat: Introduce schedule management and calendar components - Add `WorksiteCalendar.vue` to manage worksite schedules with calendar, map, and upcoming view modes - Implement schedule exporting in CSV, PDF, and ICS formats - Integrate `@schedule-x` for calendar functionality - Enable schedule editing and creation for worksite assignments - Add detailed work type interfaces in `types` fix: Improve existing components for better integration - Modify `AjaxTable.vue` to emit `rowClick` and `selectionChanged` events - Update `WorksiteTable.vue` styling and support passing custom table body styles - Adjust incident handling in `AddFromList` and `List` components for more consistent query parameters chore: Update dependencies for calendar integration - Add `@schedule-x` dependencies to `package.json` * refactor: remove Vuex ORM integration and simplify team data fetching - Removed Vuex ORM and related dependencies in `AddScheduleDialog` test setup. - Replaced Vuex ORM team fetching with direct `axios` call in `AddScheduleDialog.vue`. - Cleaned up unused test code and adjusted mocking for consistency. These changes simplify dependencies by removing Vuex ORM, streamlining the team data fetching process, and improving test maintainability.
1 parent 64eaca0 commit 56d1e60

22 files changed

+2252
-101
lines changed

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@
6464
"@pixi/settings": "6.5.10",
6565
"@pixi/ticker": "6.5.10",
6666
"@pixi/utils": "6.5.10",
67+
"@schedule-x/calendar": "^2.18.0",
68+
"@schedule-x/current-time": "^2.19.0",
69+
"@schedule-x/drag-and-drop": "^2.18.0",
70+
"@schedule-x/events-service": "^2.18.0",
71+
"@schedule-x/resize": "^2.18.0",
72+
"@schedule-x/theme-default": "^2.18.0",
73+
"@schedule-x/vue": "^2.16.0",
6774
"@sentry/integrations": "^7.111.0",
6875
"@sentry/vue": "^8.27.0",
6976
"@sipec/vue3-tags-input": "^3.0.4",

pnpm-lock.yaml

Lines changed: 137 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/AjaxTable.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ export default defineComponent({
111111
default: '',
112112
},
113113
},
114+
emits: ['rowClick', 'selectionChanged'],
114115
setup(props) {
115116
const { emitter } = useEmitter();
116117
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<template>
2+
<div class="flex flex-col">
3+
<div class="flex items-center justify-between p-2">
4+
<div></div>
5+
<div class="flex gap-2 mr-2">
6+
<ccu-icon
7+
:alt="$t('casesVue.map_view')"
8+
data-testid="testMapViewIcon"
9+
size="medium"
10+
class="cursor-pointer"
11+
:class="showingMap ? 'filter-yellow' : 'filter-gray'"
12+
type="map"
13+
ccu-event="user_ui-view-map"
14+
@click="() => showMap(true)"
15+
/>
16+
<ccu-icon
17+
:alt="$t('casesVue.table_view')"
18+
data-testid="testTableViewIcon"
19+
size="medium"
20+
class="cursor-pointer"
21+
:class="showingTable ? 'filter-yellow' : 'filter-gray'"
22+
type="table"
23+
ccu-event="user_ui-view-table"
24+
@click="showTable"
25+
/>
26+
</div>
27+
</div>
28+
<div class="relative flex-grow border-t">
29+
<SimpleMap
30+
v-show="showingMap"
31+
:map-loading="false"
32+
data-testid="mapPopupTest"
33+
/>
34+
<WorksiteTable
35+
v-show="showingTable"
36+
:worksite-query="props.query"
37+
:body-style="{ height: '32rem' }"
38+
@row-click="onSelectWorksite"
39+
/>
40+
</div>
41+
</div>
42+
</template>
43+
44+
<script setup lang="ts">
45+
import SimpleMap from '@/components/SimpleMap.vue';
46+
import useWorksiteMap from '@/hooks/worksite/useWorksiteMap';
47+
import CcuIcon from '@/components/BaseIcon.vue';
48+
import WorksiteTable from '@/components/work/WorksiteTable.vue';
49+
import { loadCasesCached } from '@/utils/worksite';
50+
import BaseCheckbox from '@/components/BaseCheckbox.vue';
51+
52+
const props = defineProps<{
53+
incidentId: number;
54+
query?: Record<string, any> | null;
55+
}>();
56+
57+
const emits = defineEmits(['selectWorksite']);
58+
59+
let mapUtils: ReturnType<typeof useWorksiteMap> | null = null;
60+
61+
const showingMap = ref(true);
62+
const showingTable = ref(false);
63+
64+
const showMap = (show: boolean) => {
65+
showingMap.value = show;
66+
showingTable.value = !show;
67+
};
68+
69+
const showTable = () => {
70+
showingTable.value = true;
71+
showingMap.value = false;
72+
};
73+
74+
const onSelectWorksite = (worksite: any) => {
75+
emits('selectWorksite', worksite);
76+
};
77+
78+
async function getWorksites() {
79+
const response = await loadCasesCached(props.query);
80+
return response.results;
81+
}
82+
/**
83+
* Minimal example that fetches worksites for the given incident
84+
* and sets a default or custom center.
85+
*/
86+
onMounted(async () => {
87+
try {
88+
// 1. Fetch worksites by incident
89+
const worksites = await getWorksites(props.incidentId);
90+
91+
// 2. Initialize the map composable
92+
mapUtils = useWorksiteMap(
93+
worksites,
94+
worksites.map((ws) => ws.id),
95+
(w) => {
96+
onSelectWorksite(w);
97+
},
98+
(_, map) => {
99+
// Map centering logic
100+
if (props.incidentCenter) {
101+
// incidentCenter is typically [longitude, latitude]
102+
map.setView([props.incidentCenter[1], props.incidentCenter[0]], 5);
103+
} else {
104+
// Fallback center if none is provided
105+
map.setView([35.7465, -96.4115], 5);
106+
}
107+
},
108+
);
109+
} catch (error) {
110+
console.error('Error fetching worksites for popup map:', error);
111+
}
112+
});
113+
</script>
114+
115+
<style scoped>
116+
/* You can style your popup container here if needed */
117+
</style>

src/components/locations/LocationViewer.vue

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ export default defineComponent({
2323
useGoogleMaps: {
2424
type: Boolean,
2525
},
26+
allowReposition: {
27+
type: Boolean,
28+
default: true,
29+
},
30+
customSvgIcon: {
31+
type: String,
32+
},
2633
},
2734
emits: ['updatedLocation'],
2835
setup(props, { emit }) {
@@ -40,7 +47,7 @@ export default defineComponent({
4047
function addMarkerToMap() {
4148
const svgIcon = L.divIcon({
4249
className: 'crisiscleanup-map-marker',
43-
html: templates.map_marker,
50+
html: props.customSvgIcon || templates.map_marker,
4451
iconAnchor: [20, 40],
4552
iconSize: [50, 50],
4653
});
@@ -50,7 +57,7 @@ export default defineComponent({
5057
markerLayer.value.clearLayers();
5158
const marker = new (L.marker as any)(
5259
[markerLocation.coordinates[1], markerLocation.coordinates[0]],
53-
{ draggable: 'true', icon: svgIcon },
60+
{ draggable: props.allowReposition, icon: svgIcon },
5461
);
5562
marker.addTo(markerLayer.value);
5663
marker.on('dragend', (event: LeafletEvent) => {
@@ -60,12 +67,14 @@ export default defineComponent({
6067
[markerLocation.coordinates[1], markerLocation.coordinates[0]],
6168
15,
6269
);
63-
marker
64-
.bindTooltip(t('casesVue.drag_pin_to_correct_location'), {
65-
direction: 'top',
66-
offset: L.point({ x: 0, y: -40 }),
67-
})
68-
.openTooltip();
70+
if (props.allowReposition) {
71+
marker
72+
.bindTooltip(t('casesVue.drag_pin_to_correct_location'), {
73+
direction: 'top',
74+
offset: L.point({ x: 0, y: -40 }),
75+
})
76+
.openTooltip();
77+
}
6978
}
7079
7180
onMounted(() => {

0 commit comments

Comments
 (0)