Skip to content

Commit de7e9cb

Browse files
committed
feature: shows availability of objects when lending through event settings
1 parent db5dcbf commit de7e9cb

File tree

7 files changed

+120
-10
lines changed

7 files changed

+120
-10
lines changed

lego-webapp/components/Form/SelectInput.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type Props<Option, IsMulti extends boolean = false> = {
2626
isClearable?: boolean;
2727
filter?: string[];
2828
SuggestionComponent?: SuggestionComponent<Option>;
29+
formatOptionLabel?: ComponentProps<typeof Select>['formatOptionLabel'];
30+
isOptionDisabled?: ComponentProps<typeof Select>['isOptionDisabled'];
2931
} & Pick<ComponentProps<Creatable>, 'onBlur'>;
3032

3133
export type SuggestionComponent<Option = { label: string; value: number }> =
@@ -96,6 +98,7 @@ const SelectInput = <
9698
creatable,
9799
onSearch,
98100
SuggestionComponent,
101+
isOptionDisabled,
99102
...props
100103
}: Props<Option, IsMulti>) => {
101104
if (props.tags) {
@@ -117,6 +120,7 @@ const SelectInput = <
117120
value={value}
118121
isValidNewOption={isValidNewOption}
119122
options={options}
123+
isOptionDisabled={isOptionDisabled}
120124
isLoading={fetching}
121125
styles={selectStyle ?? selectStyles}
122126
theme={selectTheme}
@@ -143,6 +147,7 @@ const SelectInput = <
143147
instanceId={name}
144148
value={value}
145149
options={options}
150+
isOptionDisabled={isOptionDisabled}
146151
isLoading={fetching}
147152
onInputChange={(value) => {
148153
onSearch?.(value);

lego-webapp/pages/events/src/EventEditor/EditorSection/LendingSection.tsx

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,102 @@ import { usePreparedEffect } from '@webkom/react-prepare';
22
import { Field } from 'react-final-form';
33
import { SelectInput, TextInput } from '~/components/Form';
44
import { type EditingEvent } from '~/pages/events/utils';
5-
import { fetchAllLendableObjects } from '~/redux/actions/LendableObjectActions';
5+
import { fetchAllLendableObjects, fetchAvailableLendableObjectIdsByDate } from '~/redux/actions/LendableObjectActions';
66
import { useAppDispatch, useAppSelector } from '~/redux/hooks';
7-
import { selectAllLendableObjects } from '~/redux/slices/lendableObjects';
7+
import {
8+
selectAllLendableObjects,
9+
selectAvailableLendableObjectIds,
10+
} from '~/redux/slices/lendableObjects';
11+
import type { FormatOptionLabelMeta, StylesConfig } from 'react-select';
812
import styles from '../EventEditor.module.css';
13+
import type { EntityId } from '@reduxjs/toolkit';
914

1015
type Props = {
1116
values: EditingEvent;
1217
};
1318

19+
type LendingObjectOption = {
20+
label: string;
21+
value: EntityId;
22+
isAvailable: boolean;
23+
};
24+
1425
const LendingSection: React.FC<Props> = ({ values }) => {
1526
const dispatch = useAppDispatch();
27+
const availableLendableObjectIds = useAppSelector(
28+
selectAvailableLendableObjectIds,
29+
);
1630

1731
usePreparedEffect(
1832
'fetchAllLendableObjects',
1933
() => dispatch(fetchAllLendableObjects()),
2034
[],
2135
);
2236

37+
usePreparedEffect(
38+
'fetchAvailableLendableObjectIdsByDate',
39+
() => {
40+
if (values.startTime && values.endTime) {
41+
dispatch(
42+
fetchAvailableLendableObjectIdsByDate(
43+
values.startTime,
44+
values.endTime,
45+
),
46+
);
47+
}
48+
},
49+
[values.startTime, values.endTime],
50+
);
51+
2352
const lendableObjects = useAppSelector(selectAllLendableObjects);
2453
const availableObjects = lendableObjects.filter((obj) => obj.canLend);
54+
const availableObjectIds = new Set(availableLendableObjectIds ?? []);
55+
const availabilityKnown = availableLendableObjectIds !== null;
56+
const lendingObjectOptions: LendingObjectOption[] = availableObjects.map(
57+
(obj) => ({
58+
label: obj.title,
59+
value: obj.id,
60+
isAvailable: !availabilityKnown || availableObjectIds.has(obj.id),
61+
}),
62+
);
63+
64+
const lendingSelectStyle: StylesConfig<
65+
LendingObjectOption,
66+
true
67+
> = {
68+
option: (base, state) => ({
69+
...base,
70+
display: 'flex',
71+
alignItems: 'center',
72+
justifyContent: 'space-between',
73+
gap: '0.75rem',
74+
color: state.data.isAvailable ? 'var(--lego-font-color)' : 'var(--placeholder-color)',
75+
fontWeight: state.data.isAvailable ? 500 : 400,
76+
opacity: state.data.isAvailable ? 1 : 0.65,
77+
}),
78+
menuPortal: (base) => ({
79+
...base,
80+
zIndex: 100,
81+
}),
82+
};
83+
84+
const formatLendingOptionLabel = (
85+
option: LendingObjectOption,
86+
{ context }: FormatOptionLabelMeta<LendingObjectOption>,
87+
) => {
88+
if (context === 'value' || option.isAvailable) {
89+
return option.label;
90+
}
91+
92+
return (
93+
<div style={{ display: 'flex', width: '100%', justifyContent: 'space-between', gap: '0.75rem' }}>
94+
<span style={{ textDecoration: option.isAvailable ? 'none' : 'line-through' }}>{option.label}</span>
95+
<span style={{ fontSize: '12px', color: 'var(--placeholder-color)', textDecoration: 'none' }}>
96+
Ikke tilgjengelig i den valgte perioden
97+
</span>
98+
</div>
99+
);
100+
};
25101

26102
return (
27103
<>
@@ -30,14 +106,16 @@ const LendingSection: React.FC<Props> = ({ values }) => {
30106
label="Søk etter utlånsobjekt"
31107
fieldClassName={styles.metaField}
32108
component={SelectInput.Field}
33-
options={availableObjects.map((obj) => ({
34-
label: obj.title,
35-
value: obj.id,
36-
}))}
109+
options={lendingObjectOptions}
110+
selectStyle={lendingSelectStyle}
111+
formatOptionLabel={formatLendingOptionLabel}
112+
isOptionDisabled={(option) => !option.isAvailable}
37113
placeholder="Utlånsobjekt"
38114
isMulti
39115
description="Dette gjør det mulig for arrangementet å låne objekter for et arrangmenet. Dette gjøres ved å lage en separat utlånsforespørsel etter at arrangementet er opprettet, i ditt navn. Objektene vil ikke være reservert før utlånsforespørselen er godkjent. Objektene vil heller ikke endres hvis arrangment endres på. Du er selv ansvarlig for å sjekke at objektene er tilgjengelige for utlån på det tidspunktet arrangementet skal være. "
40116
/>
117+
{console.log(values)}
118+
{console.log(values.lendingObjects)}
41119

42120
{values.lendingObjects &&
43121
values.lendingObjects.length > 0 &&

lego-webapp/pages/events/src/EventEditor/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ const EventEditor = () => {
393393
<EditorSection title="Påmelding" initiallyExpanded={!isEditPage}>
394394
<Registration values={values} />
395395
</EditorSection>
396-
<EditorSection title="Utstyrslån" initiallyExpanded={true}>
396+
<EditorSection title="Utstyrslån" initiallyExpanded={!isEditPage}>
397397
<LendingSection values={values} />
398398
</EditorSection>
399399
<EditorSection title="Beskrivelse" collapsible={false}>

lego-webapp/redux/actionTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export const Joblistings = {
8888

8989
export const LendableObjects = {
9090
FETCH: generateStatuses('LendableObject.FETCH_ALL'),
91+
FETCH_AVAILABLE: generateStatuses('LendableObject.FETCH_AVAILABLE'),
9192
FETCH_AVAILABILITY: generateStatuses('LendableObject.FETCH_AVAILABILITY'),
9293
CREATE: generateStatuses('LendableObject.CREATE'),
9394
EDIT: generateStatuses('LendableObject.EDIT'),

lego-webapp/redux/actions/LendableObjectActions.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
EditLendableObject,
99
ListLendableObject,
1010
} from '~/redux/models/LendableObject';
11+
import { Dateish } from 'app/models';
1112

1213
export const fetchAllLendableObjects = () =>
1314
callAPI<ListLendableObject[]>({
@@ -20,6 +21,21 @@ export const fetchAllLendableObjects = () =>
2021
propagateError: true,
2122
});
2223

24+
25+
export const fetchAvailableLendableObjectIdsByDate = (
26+
start_date: string | Dateish,
27+
end_date: string | Dateish,
28+
) =>
29+
callAPI<EntityId[]>({
30+
types: LendableObjects.FETCH_AVAILABLE,
31+
endpoint: '/lending/objects/available/',
32+
query: { start_date, end_date },
33+
meta: {
34+
errorMessage: 'Henting av tilgjengelige utlånsobjekter feilet',
35+
},
36+
propagateError: true,
37+
});
38+
2339
export const fetchLendableObjectById = (id: EntityId) =>
2440
callAPI<DetailLendableObject>({
2541
types: LendableObjects.FETCH,

lego-webapp/redux/models/LendableObject.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export type ListLendableObject = Pick<
2525
| 'canLend'
2626
| 'availability'
2727
| 'category'
28-
> & { responsibleGroups: EntityId[] };
28+
> & { responsibleGroups: EntityId[], available?: boolean /* Currently used by event management to show availability of lending objects in given timeframe */ };
2929

3030
export type DetailLendableObject = ListLendableObject &
3131
Pick<LendableObject, 'actionGrant'> &

lego-webapp/redux/slices/lendableObjects.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,26 @@ import { createSlice } from '@reduxjs/toolkit';
22
import { LendableObjects } from '~/redux/actionTypes';
33
import createLegoAdapter from '~/redux/legoAdapter/createLegoAdapter';
44
import { EntityType } from '~/redux/models/entities';
5+
import type { EntityId } from '@reduxjs/toolkit';
6+
import type { AnyAction } from '@reduxjs/toolkit';
57
import type { RootState } from '~/redux/rootReducer';
68

79
const legoAdapter = createLegoAdapter(EntityType.LendableObjects);
810

911
const lendableObjectsSlice = createSlice({
1012
name: EntityType.LendableObjects,
11-
initialState: legoAdapter.getInitialState(),
13+
initialState: legoAdapter.getInitialState({
14+
availableIds: null as EntityId[] | null,
15+
}),
1216
reducers: {},
1317
extraReducers: legoAdapter.buildReducers({
1418
fetchActions: [LendableObjects.FETCH],
1519
deleteActions: [LendableObjects.DELETE],
1620
extraCases: (addCase) => {
17-
addCase(LendableObjects.FETCH_AVAILABILITY.SUCCESS, (state, action) => {
21+
addCase(LendableObjects.FETCH_AVAILABLE.SUCCESS, (state, action: AnyAction) => {
22+
state.availableIds = action.payload;
23+
});
24+
addCase(LendableObjects.FETCH_AVAILABILITY.SUCCESS, (state, action: AnyAction) => {
1825
const id = action.meta.id;
1926
legoAdapter.updateOne(state, {
2027
id,
@@ -32,3 +39,6 @@ export const {
3239
selectById: selectLendableObjectById,
3340
selectAll: selectAllLendableObjects,
3441
} = legoAdapter.getSelectors((state: RootState) => state.lendableObjects);
42+
43+
export const selectAvailableLendableObjectIds = (state: RootState) =>
44+
state.lendableObjects.availableIds;

0 commit comments

Comments
 (0)