Skip to content

Commit

Permalink
Merge pull request #476 from gitroomhq/feat/customer
Browse files Browse the repository at this point in the history
Add customers seperation
  • Loading branch information
nevo-david authored Dec 7, 2024
2 parents b263299 + ddc0aa0 commit a9630b6
Show file tree
Hide file tree
Showing 11 changed files with 616 additions and 131 deletions.
33 changes: 33 additions & 0 deletions apps/backend/src/api/routes/integrations.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Get,
Param,
Post,
Put,
Query,
UseFilters,
} from '@nestjs/common';
Expand Down Expand Up @@ -48,6 +49,37 @@ export class IntegrationsController {
return this._integrationManager.getAllIntegrations();
}

@Get('/customers')
getCustomers(@GetOrgFromRequest() org: Organization) {
return this._integrationService.customers(org.id);
}

@Put('/:id/group')
async updateIntegrationGroup(
@GetOrgFromRequest() org: Organization,
@Param('id') id: string,
@Body() body: { group: string }
) {
return this._integrationService.updateIntegrationGroup(
org.id,
id,
body.group
);
}

@Put('/:id/customer-name')
async updateOnCustomerName(
@GetOrgFromRequest() org: Organization,
@Param('id') id: string,
@Body() body: { name: string }
) {
return this._integrationService.updateOnCustomerName(
org.id,
id,
body.name
);
}

@Get('/list')
async getIntegrationList(@GetOrgFromRequest() org: Organization) {
return {
Expand All @@ -71,6 +103,7 @@ export class IntegrationsController {
time: JSON.parse(p.postingTimes),
changeProfilePicture: !!findIntegration?.changeProfilePicture,
changeNickName: !!findIntegration?.changeNickname,
customer: p.customer,
};
}),
};
Expand Down
11 changes: 11 additions & 0 deletions apps/frontend/src/app/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,14 @@ div div .set-font-family {
font-style: normal !important;
font-weight: 400 !important;
}

.col-calendar:hover:before {
content: "Date passed";
color: white;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
white-space: nowrap;
opacity: 30%;
}
62 changes: 52 additions & 10 deletions apps/frontend/src/components/launches/add.edit.model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ import { useUser } from '@gitroom/frontend/components/layout/user.context';
import { makeId } from '@gitroom/nestjs-libraries/services/make.is';
import Image from 'next/image';
import { weightedLength } from '@gitroom/helpers/utils/count.length';
import { uniqBy } from 'lodash';
import { Select } from '@gitroom/react/form/select';

function countCharacters(text: string, type: string): number {
if (type !== 'x') {
Expand All @@ -65,17 +67,36 @@ export const AddEditModal: FC<{
reopenModal: () => void;
mutate: () => void;
}> = (props) => {
const { date, integrations, reopenModal, mutate } = props;
const [dateState, setDateState] = useState(date);

// hook to open a new modal
const modal = useModals();
const { date, integrations: ints, reopenModal, mutate } = props;
const [customer, setCustomer] = useState('');

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

const integrations = useMemo(() => {
if (!customer) {
return ints;
}

const list = ints.filter((f) => f?.customer?.id === customer);
if (list.length === 1) {
setSelectedIntegrations([list[0]]);
}

return list;
}, [customer, ints]);

const totalCustomers = useMemo(() => {
return uniqBy(ints, (i) => i?.customer?.id).length;
}, [ints]);

const [dateState, setDateState] = useState(date);

// hook to open a new modal
const modal = useModals();

// value of each editor
const [value, setValue] = useState<
Array<{
Expand Down Expand Up @@ -286,11 +307,12 @@ export const AddEditModal: FC<{
}

if (
key.value.some(
(p) => {
return countCharacters(p.content, key?.integration?.identifier || '') > (key.maximumCharacters || 1000000);
}
)
key.value.some((p) => {
return (
countCharacters(p.content, key?.integration?.identifier || '') >
(key.maximumCharacters || 1000000)
);
})
) {
if (
!(await deleteDialog(
Expand Down Expand Up @@ -417,6 +439,26 @@ export const AddEditModal: FC<{
information={data}
onChange={setPostFor}
/>
{totalCustomers > 1 && (
<Select
hideErrors={true}
label=""
name="customer"
value={customer}
onChange={(e) => {
setCustomer(e.target.value);
setSelectedIntegrations([]);
}}
disableForm={true}
>
<option value="">Selected Customer</option>
{uniqBy(ints, (u) => u?.customer?.name).map((p) => (
<option key={p.customer?.id} value={p.customer?.id}>
Customer: {p.customer?.name}
</option>
))}
</Select>
)}
<DatePicker onChange={setDateState} date={dateState} />
</div>
</TopTitle>
Expand Down
4 changes: 4 additions & 0 deletions apps/frontend/src/components/launches/calendar.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export interface Integrations {
changeProfilePicture: boolean;
changeNickName: boolean;
time: { time: number }[];
customer?: {
name?: string;
id?: string;
}
}

function getWeekNumber(date: Date) {
Expand Down
23 changes: 12 additions & 11 deletions apps/frontend/src/components/launches/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import clsx from 'clsx';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { ExistingDataContextProvider } from '@gitroom/frontend/components/launches/helpers/use.existing.data';
import { useDrag, useDrop } from 'react-dnd';
import { DNDProvider } from '@gitroom/frontend/components/launches/helpers/dnd.provider';
import { Integration, Post, State } from '@prisma/client';
import { useAddProvider } from '@gitroom/frontend/components/launches/add.provider.component';
import { CommentComponent } from '@gitroom/frontend/components/launches/comments/comment.component';
Expand All @@ -33,11 +32,11 @@ extend(isSameOrBefore);

const convertTimeFormatBasedOnLocality = (time: number) => {
if (isUSCitizen()) {
return `${time === 12 ? 12 : time%12}:00 ${time >= 12 ? "PM" : "AM"}`
return `${time === 12 ? 12 : time % 12}:00 ${time >= 12 ? 'PM' : 'AM'}`;
} else {
return `${time}:00`
return `${time}:00`;
}
}
};

export const days = [
'Monday',
Expand Down Expand Up @@ -100,7 +99,7 @@ export const DayView = () => {
.startOf('day')
.add(option[0].time, 'minute')
.local()
.format(isUSCitizen() ? "hh:mm A": "HH:mm")}
.format(isUSCitizen() ? 'hh:mm A' : 'HH:mm')}
</div>
<div
key={option[0].time}
Expand Down Expand Up @@ -241,15 +240,15 @@ export const Calendar = () => {
const { display } = useCalendar();

return (
<DNDProvider>
<>
{display === 'day' ? (
<DayView />
) : display === 'week' ? (
<WeekView />
) : (
<MonthView />
)}
</DNDProvider>
</>
);
};

Expand Down Expand Up @@ -443,8 +442,9 @@ export const CalendarColumn: FC<{
)}
<div
className={clsx(
'relative flex flex-col flex-1',
canDrop && 'bg-white/80'
'relative flex flex-col flex-1 text-white',
canDrop && 'bg-white/80',
isBeforeNow && postList.length === 0 && 'cursor-not-allowed'
)}
>
<div
Expand All @@ -455,8 +455,9 @@ export const CalendarColumn: FC<{
}
: {})}
className={clsx(
'flex-col text-[12px] pointer w-full cursor-pointer overflow-hidden overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
isBeforeNow && 'bg-customColor23 flex-1',
'flex-col text-[12px] pointer w-full overflow-hidden overflow-x-auto flex scrollbar scrollbar-thumb-tableBorder scrollbar-track-secondary',
isBeforeNow ? 'bg-customColor23 flex-1' : 'cursor-pointer',
isBeforeNow && postList.length === 0 && 'col-calendar',
canBeTrending && 'bg-customColor24'
)}
>
Expand Down
88 changes: 88 additions & 0 deletions apps/frontend/src/components/launches/customer.modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { TopTitle } from '@gitroom/frontend/components/launches/helpers/top.title.component';
import React, { FC, useCallback, useEffect, useState } from 'react';
import { useModals } from '@mantine/modals';
import { Integration } from '@prisma/client';
import { Autocomplete } from '@mantine/core';
import useSWR from 'swr';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { Button } from '@gitroom/react/form/button';

export const CustomerModal: FC<{
integration: Integration & { customer?: { id: string; name: string } };
onClose: () => void;
}> = (props) => {
const fetch = useFetch();
const { onClose, integration } = props;
const [customer, setCustomer] = useState(
integration.customer?.name || undefined
);
const modal = useModals();

const loadCustomers = useCallback(async () => {
return (await fetch('/integrations/customers')).json();
}, []);

const removeFromCustomer = useCallback(async () => {
saveCustomer(true);
}, []);

const saveCustomer = useCallback(async (removeCustomer?: boolean) => {
if (!customer) {
return;
}

await fetch(`/integrations/${integration.id}/customer-name`, {
method: 'PUT',
body: JSON.stringify({ name: removeCustomer ? '' : customer }),
});

modal.closeAll();
onClose();
}, [customer]);

const { data } = useSWR('/customers', loadCustomers);

return (
<div className="rounded-[4px] border border-customColor6 bg-sixth px-[16px] pb-[16px] relative w-full">
<TopTitle title={`Move / Add to customer`} />
<button
className="outline-none absolute right-[20px] top-[20px] mantine-UnstyledButton-root mantine-ActionIcon-root hover:bg-tableBorder cursor-pointer mantine-Modal-close mantine-1dcetaa"
type="button"
onClick={() => modal.closeAll()}
>
<svg
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
>
<path
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"
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
></path>
</svg>
</button>

<div className="mt-[16px]">
<Autocomplete
value={customer}
onChange={setCustomer}
classNames={{
label: 'text-white',
}}
label="Select Customer"
placeholder="Start typing..."
data={data?.map((p: any) => p.name) || []}
/>
</div>

<div className="my-[16px] flex gap-[10px]">
<Button onClick={() => saveCustomer()}>Save</Button>
{!!integration?.customer?.name && <Button className="bg-red-700" onClick={removeFromCustomer}>Remove from customer</Button>}
</div>
</div>
);
};
Loading

0 comments on commit a9630b6

Please sign in to comment.