Skip to content

Commit a9630b6

Browse files
authored
Merge pull request #476 from gitroomhq/feat/customer
Add customers seperation
2 parents b263299 + ddc0aa0 commit a9630b6

File tree

11 files changed

+616
-131
lines changed

11 files changed

+616
-131
lines changed

apps/backend/src/api/routes/integrations.controller.ts

+33
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
Get,
66
Param,
77
Post,
8+
Put,
89
Query,
910
UseFilters,
1011
} from '@nestjs/common';
@@ -48,6 +49,37 @@ export class IntegrationsController {
4849
return this._integrationManager.getAllIntegrations();
4950
}
5051

52+
@Get('/customers')
53+
getCustomers(@GetOrgFromRequest() org: Organization) {
54+
return this._integrationService.customers(org.id);
55+
}
56+
57+
@Put('/:id/group')
58+
async updateIntegrationGroup(
59+
@GetOrgFromRequest() org: Organization,
60+
@Param('id') id: string,
61+
@Body() body: { group: string }
62+
) {
63+
return this._integrationService.updateIntegrationGroup(
64+
org.id,
65+
id,
66+
body.group
67+
);
68+
}
69+
70+
@Put('/:id/customer-name')
71+
async updateOnCustomerName(
72+
@GetOrgFromRequest() org: Organization,
73+
@Param('id') id: string,
74+
@Body() body: { name: string }
75+
) {
76+
return this._integrationService.updateOnCustomerName(
77+
org.id,
78+
id,
79+
body.name
80+
);
81+
}
82+
5183
@Get('/list')
5284
async getIntegrationList(@GetOrgFromRequest() org: Organization) {
5385
return {
@@ -71,6 +103,7 @@ export class IntegrationsController {
71103
time: JSON.parse(p.postingTimes),
72104
changeProfilePicture: !!findIntegration?.changeProfilePicture,
73105
changeNickName: !!findIntegration?.changeNickname,
106+
customer: p.customer,
74107
};
75108
}),
76109
};

apps/frontend/src/app/global.scss

+11
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,14 @@ div div .set-font-family {
384384
font-style: normal !important;
385385
font-weight: 400 !important;
386386
}
387+
388+
.col-calendar:hover:before {
389+
content: "Date passed";
390+
color: white;
391+
position: absolute;
392+
left: 50%;
393+
top: 50%;
394+
transform: translate(-50%, -50%);
395+
white-space: nowrap;
396+
opacity: 30%;
397+
}

apps/frontend/src/components/launches/add.edit.model.tsx

+52-10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import { useUser } from '@gitroom/frontend/components/layout/user.context';
5050
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
5151
import Image from 'next/image';
5252
import { weightedLength } from '@gitroom/helpers/utils/count.length';
53+
import { uniqBy } from 'lodash';
54+
import { Select } from '@gitroom/react/form/select';
5355

5456
function countCharacters(text: string, type: string): number {
5557
if (type !== 'x') {
@@ -65,17 +67,36 @@ export const AddEditModal: FC<{
6567
reopenModal: () => void;
6668
mutate: () => void;
6769
}> = (props) => {
68-
const { date, integrations, reopenModal, mutate } = props;
69-
const [dateState, setDateState] = useState(date);
70-
71-
// hook to open a new modal
72-
const modal = useModals();
70+
const { date, integrations: ints, reopenModal, mutate } = props;
71+
const [customer, setCustomer] = useState('');
7372

7473
// selected integrations to allow edit
7574
const [selectedIntegrations, setSelectedIntegrations] = useStateCallback<
7675
Integrations[]
7776
>([]);
7877

78+
const integrations = useMemo(() => {
79+
if (!customer) {
80+
return ints;
81+
}
82+
83+
const list = ints.filter((f) => f?.customer?.id === customer);
84+
if (list.length === 1) {
85+
setSelectedIntegrations([list[0]]);
86+
}
87+
88+
return list;
89+
}, [customer, ints]);
90+
91+
const totalCustomers = useMemo(() => {
92+
return uniqBy(ints, (i) => i?.customer?.id).length;
93+
}, [ints]);
94+
95+
const [dateState, setDateState] = useState(date);
96+
97+
// hook to open a new modal
98+
const modal = useModals();
99+
79100
// value of each editor
80101
const [value, setValue] = useState<
81102
Array<{
@@ -286,11 +307,12 @@ export const AddEditModal: FC<{
286307
}
287308

288309
if (
289-
key.value.some(
290-
(p) => {
291-
return countCharacters(p.content, key?.integration?.identifier || '') > (key.maximumCharacters || 1000000);
292-
}
293-
)
310+
key.value.some((p) => {
311+
return (
312+
countCharacters(p.content, key?.integration?.identifier || '') >
313+
(key.maximumCharacters || 1000000)
314+
);
315+
})
294316
) {
295317
if (
296318
!(await deleteDialog(
@@ -417,6 +439,26 @@ export const AddEditModal: FC<{
417439
information={data}
418440
onChange={setPostFor}
419441
/>
442+
{totalCustomers > 1 && (
443+
<Select
444+
hideErrors={true}
445+
label=""
446+
name="customer"
447+
value={customer}
448+
onChange={(e) => {
449+
setCustomer(e.target.value);
450+
setSelectedIntegrations([]);
451+
}}
452+
disableForm={true}
453+
>
454+
<option value="">Selected Customer</option>
455+
{uniqBy(ints, (u) => u?.customer?.name).map((p) => (
456+
<option key={p.customer?.id} value={p.customer?.id}>
457+
Customer: {p.customer?.name}
458+
</option>
459+
))}
460+
</Select>
461+
)}
420462
<DatePicker onChange={setDateState} date={dateState} />
421463
</div>
422464
</TopTitle>

apps/frontend/src/components/launches/calendar.context.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ export interface Integrations {
6262
changeProfilePicture: boolean;
6363
changeNickName: boolean;
6464
time: { time: number }[];
65+
customer?: {
66+
name?: string;
67+
id?: string;
68+
}
6569
}
6670

6771
function getWeekNumber(date: Date) {

apps/frontend/src/components/launches/calendar.tsx

+12-11
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import clsx from 'clsx';
1313
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
1414
import { ExistingDataContextProvider } from '@gitroom/frontend/components/launches/helpers/use.existing.data';
1515
import { useDrag, useDrop } from 'react-dnd';
16-
import { DNDProvider } from '@gitroom/frontend/components/launches/helpers/dnd.provider';
1716
import { Integration, Post, State } from '@prisma/client';
1817
import { useAddProvider } from '@gitroom/frontend/components/launches/add.provider.component';
1918
import { CommentComponent } from '@gitroom/frontend/components/launches/comments/comment.component';
@@ -33,11 +32,11 @@ extend(isSameOrBefore);
3332

3433
const convertTimeFormatBasedOnLocality = (time: number) => {
3534
if (isUSCitizen()) {
36-
return `${time === 12 ? 12 : time%12}:00 ${time >= 12 ? "PM" : "AM"}`
35+
return `${time === 12 ? 12 : time % 12}:00 ${time >= 12 ? 'PM' : 'AM'}`;
3736
} else {
38-
return `${time}:00`
37+
return `${time}:00`;
3938
}
40-
}
39+
};
4140

4241
export const days = [
4342
'Monday',
@@ -100,7 +99,7 @@ export const DayView = () => {
10099
.startOf('day')
101100
.add(option[0].time, 'minute')
102101
.local()
103-
.format(isUSCitizen() ? "hh:mm A": "HH:mm")}
102+
.format(isUSCitizen() ? 'hh:mm A' : 'HH:mm')}
104103
</div>
105104
<div
106105
key={option[0].time}
@@ -241,15 +240,15 @@ export const Calendar = () => {
241240
const { display } = useCalendar();
242241

243242
return (
244-
<DNDProvider>
243+
<>
245244
{display === 'day' ? (
246245
<DayView />
247246
) : display === 'week' ? (
248247
<WeekView />
249248
) : (
250249
<MonthView />
251250
)}
252-
</DNDProvider>
251+
</>
253252
);
254253
};
255254

@@ -443,8 +442,9 @@ export const CalendarColumn: FC<{
443442
)}
444443
<div
445444
className={clsx(
446-
'relative flex flex-col flex-1',
447-
canDrop && 'bg-white/80'
445+
'relative flex flex-col flex-1 text-white',
446+
canDrop && 'bg-white/80',
447+
isBeforeNow && postList.length === 0 && 'cursor-not-allowed'
448448
)}
449449
>
450450
<div
@@ -455,8 +455,9 @@ export const CalendarColumn: FC<{
455455
}
456456
: {})}
457457
className={clsx(
458-
'flex-col text-[12px] pointer w-full cursor-pointer overflow-hidden overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
459-
isBeforeNow && 'bg-customColor23 flex-1',
458+
'flex-col text-[12px] pointer w-full overflow-hidden overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
459+
isBeforeNow ? 'bg-customColor23 flex-1' : 'cursor-pointer',
460+
isBeforeNow && postList.length === 0 && 'col-calendar',
460461
canBeTrending && 'bg-customColor24'
461462
)}
462463
>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
2+
import React, { FC, useCallback, useEffect, useState } from 'react';
3+
import { useModals } from '@mantine/modals';
4+
import { Integration } from '@prisma/client';
5+
import { Autocomplete } from '@mantine/core';
6+
import useSWR from 'swr';
7+
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
8+
import { Button } from '@gitroom/react/form/button';
9+
10+
export const CustomerModal: FC<{
11+
integration: Integration & { customer?: { id: string; name: string } };
12+
onClose: () => void;
13+
}> = (props) => {
14+
const fetch = useFetch();
15+
const { onClose, integration } = props;
16+
const [customer, setCustomer] = useState(
17+
integration.customer?.name || undefined
18+
);
19+
const modal = useModals();
20+
21+
const loadCustomers = useCallback(async () => {
22+
return (await fetch('/integrations/customers')).json();
23+
}, []);
24+
25+
const removeFromCustomer = useCallback(async () => {
26+
saveCustomer(true);
27+
}, []);
28+
29+
const saveCustomer = useCallback(async (removeCustomer?: boolean) => {
30+
if (!customer) {
31+
return;
32+
}
33+
34+
await fetch(`/integrations/${integration.id}/customer-name`, {
35+
method: 'PUT',
36+
body: JSON.stringify({ name: removeCustomer ? '' : customer }),
37+
});
38+
39+
modal.closeAll();
40+
onClose();
41+
}, [customer]);
42+
43+
const { data } = useSWR('/customers', loadCustomers);
44+
45+
return (
46+
<div className="rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative w-full">
47+
<TopTitle title={`Move / Add to customer`} />
48+
<button
49+
className="outline-none absolute right-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
50+
type="button"
51+
onClick={() => modal.closeAll()}
52+
>
53+
<svg
54+
viewBox="0 0 15 15"
55+
fill="none"
56+
xmlns="http://www.w3.org/2000/svg"
57+
width="16"
58+
height="16"
59+
>
60+
<path
61+
d="M11.7816 4.03157C12.0062 3.80702 12.0062 3.44295 11.7816 3.2184C11.5571 2.99385 11.193 2.99385 10.9685 3.2184L7.50005 6.68682L4.03164 3.2184C3.80708 2.99385 3.44301 2.99385 3.21846 3.2184C2.99391 3.44295 2.99391 3.80702 3.21846 4.03157L6.68688 7.49999L3.21846 10.9684C2.99391 11.193 2.99391 11.557 3.21846 11.7816C3.44301 12.0061 3.80708 12.0061 4.03164 11.7816L7.50005 8.31316L10.9685 11.7816C11.193 12.0061 11.5571 12.0061 11.7816 11.7816C12.0062 11.557 12.0062 11.193 11.7816 10.9684L8.31322 7.49999L11.7816 4.03157Z"
62+
fill="currentColor"
63+
fillRule="evenodd"
64+
clipRule="evenodd"
65+
></path>
66+
</svg>
67+
</button>
68+
69+
<div className="mt-[16px]">
70+
<Autocomplete
71+
value={customer}
72+
onChange={setCustomer}
73+
classNames={{
74+
label: 'text-white',
75+
}}
76+
label="Select Customer"
77+
placeholder="Start typing..."
78+
data={data?.map((p: any) => p.name) || []}
79+
/>
80+
</div>
81+
82+
<div className="my-[16px] flex gap-[10px]">
83+
<Button onClick={() => saveCustomer()}>Save</Button>
84+
{!!integration?.customer?.name && <Button className="bg-red-700" onClick={removeFromCustomer}>Remove from customer</Button>}
85+
</div>
86+
</div>
87+
);
88+
};

0 commit comments

Comments
 (0)