Skip to content

Commit

Permalink
feat:add manageuser org modify (#305)
Browse files Browse the repository at this point in the history
Signed-off-by: laixingyou <[email protected]>
  • Loading branch information
coder-sett authored Jan 26, 2024
1 parent a2079a2 commit c12c016
Show file tree
Hide file tree
Showing 18 changed files with 1,144 additions and 9 deletions.
2 changes: 1 addition & 1 deletion apps/web/i18n
88 changes: 88 additions & 0 deletions apps/web/src/common/components/OrgEdit/DateRangePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState, useRef, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { DatePicker, ConfigProvider } from 'antd';
import classnames from 'classnames';
import zhCN from 'antd/locale/zh_CN';
import enUS from 'antd/locale/en_US';
import 'dayjs/locale/zh-cn';
import dayjs from 'dayjs';
import type { Dayjs } from 'dayjs';
import type { DatePickerProps } from 'antd';

type RangeValue = [Dayjs | null, Dayjs | null] | null;
const FORMAT_YMD = 'YYYY-MM-DD';

const DateRangePicker = ({
value,
onChange,
inputClass,
disabledDate,
}: {
value?: RangeValue;
onChange?: (value: RangeValue) => void;
disabledDate?: (value: Dayjs) => boolean;
inputClass?: string;
}) => {
const { t, i18n } = useTranslation();
const inputRef = useRef(null);
const { RangePicker } = DatePicker;
const [local, setLocale] = useState(zhCN);
useEffect(() => {
setLocale(i18n.language === 'zh' ? zhCN : enUS);
}, [i18n]);

const [dates, setDates] = useState<RangeValue>(null);
const [datesValue, setDatesValue] = useState<RangeValue>(value || null);

const onOpenChange = (open: boolean) => {
if (!open) {
setDates(null);
}
};
const setToNow = () => {
setDates([dates[0], dayjs('2099/01/01', FORMAT_YMD)]);
setDatesValue([dates[0], dayjs('2099/01/01', FORMAT_YMD)]);
onChange([dates[0], dayjs('2099/01/01', FORMAT_YMD)]);
inputRef.current!.blur();
};
const customFormat: DatePickerProps['format'] = (value) => {
const date = value.format(FORMAT_YMD);
if (date.startsWith('2099')) {
return t('common:up_to_now');
}
return date;
};

return (
<ConfigProvider locale={local}>
<RangePicker
ref={inputRef}
className={classnames(inputClass ? inputClass : 'ant-org-input')}
value={dates || value}
onOpenChange={onOpenChange}
onCalendarChange={(val) => {
setDates(val);
}}
disabledDate={disabledDate}
onChange={(val) => {
setDatesValue(val);
onChange(val);
}}
format={customFormat}
renderExtraFooter={() => (
<div
className={classnames(
'cursor-pointer pr-4 text-right text-[#1677ff]',
{ hidden: !dates?.[0] }
)}
>
<span className="text-[#1677ff]" onClick={setToNow}>
{t('common:up_to_now')}
</span>
</div>
)}
/>
</ConfigProvider>
);
};
export default DateRangePicker;
143 changes: 143 additions & 0 deletions apps/web/src/common/components/OrgEdit/ManageOrgEdit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import React, { useState } from 'react';
import { Button } from '@oss-compass/ui';
import { useTranslation } from 'react-i18next';
import OrgInput from './OrgInput';
import DateRangePicker from './DateRangePicker';
import { Form } from 'antd';
import { useManageUserOrgsMutation } from '@oss-compass/graphql';
import client from '@common/gqlClient';
import { toast } from 'react-hot-toast';
import type { Dayjs } from 'dayjs';
const FORMAT_YMD = 'YYYY-MM-DD';

type RangeValue = [Dayjs | null, Dayjs | null] | null;

const ManageOrgEdit = ({
label,
level,
contributor,
name,
setShowEdit,
}: {
label?: string;
level?: string;
contributor?: string;
name?: string;
setShowEdit: (b: boolean) => void;
}) => {
const { t } = useTranslation();
const [orgName, setOrgName] = useState(name || '');
const [date, setDate] = useState<RangeValue>(null);
const [form] = Form.useForm();
const onCheck = async () => {
try {
const values = await form.validateFields();
const firstDate = values['date'][0].format(FORMAT_YMD);
const lastDate = values['date'][1].format(FORMAT_YMD);
const orgName = values['orgName'];
mutation.mutate({
label,
level,
contributor,
platform: 'github',
organizations: [{ orgName, firstDate, lastDate }],
});
} catch (errorInfo) {
console.log('Failed:', errorInfo);
}
};
const mutation = useManageUserOrgsMutation(client, {
onSuccess(res) {
if (res.manageUserOrgs?.status === 'true') {
setShowEdit(false);
if (res.manageUserOrgs?.prUrl) {
let message = (
<div className="flex w-full max-w-[460px] flex-col overflow-hidden text-green-500">
{t('common:toast.modification_successful')}
<a
className="underline underline-offset-2"
target="_blank"
rel="noopener noreferrer"
href={res.manageUserOrgs?.prUrl}
>
{res.manageUserOrgs?.prUrl}
</a>
</div>
);
toast.success(message, {
duration: 5000,
className: '!max-w-[500px]',
});
} else {
toast.success(t('common:toast.modification_successful'));
}
}
if (res.manageUserOrgs?.status === 'false') {
toast.error(
res.manageUserOrgs?.message || t('common:toast.modification_failed')
);
}
},
});
return (
<div className="border-b-0 border-[#E7E7E7] py-4">
<div className="flex flex-col items-start justify-between gap-6">
<Form
form={form}
layout={'vertical'}
style={{ width: '100%', maxWidth: 600 }}
>
<Form.Item
label={t('analyze:org_name')}
name="orgName"
rules={[{ required: true, message: '' }]}
initialValue={orgName}
>
<OrgInput
className="h-full w-full"
inputClass="daisy-input-bordered daisy-input h-12 w-full flex-1 border-2 px-4 text-base outline-none border-black"
dropClass="top-[50px] border-2"
value={orgName}
onChange={(e) => {
setOrgName(e);
}}
placeholder={''}
/>
</Form.Item>
<Form.Item
label={t('analyze:date')}
name="date"
rules={[
{
type: 'array' as const,
required: true,
message: '',
},
]}
initialValue={date}
>
<DateRangePicker
inputClass="daisy-input-bordered rounded-none daisy-input h-12 w-full flex-1 border-2 pr-2.5 text-base outline-none border-black"
value={date}
onChange={(e) => {
setDate(e);
}}
/>
</Form.Item>
<Form.Item>
<Button
className="flex w-[120px] cursor-pointer items-center justify-center rounded-none bg-black px-3 py-2 text-base text-white hover:opacity-80"
loading={mutation.isLoading}
onClick={() => {
onCheck();
}}
>
{t('common:btn.confirm')}
</Button>
</Form.Item>
</Form>
</div>
</div>
);
};
export default ManageOrgEdit;
97 changes: 97 additions & 0 deletions apps/web/src/common/components/OrgEdit/OrgInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { PropsWithChildren, useState } from 'react';
import classnames from 'classnames';
import { Input } from 'antd';
import { useThrottle } from 'ahooks';
import { useOrgSearchQuery } from '@oss-compass/graphql';
import client from '@common/gqlClient';
import { AiOutlineLoading } from 'react-icons/ai';

const Select: React.FC<
PropsWithChildren<{
value?: string;
onChange?: (value: string) => void;
className?: string;
inputClass?: string;
dropClass?: string;
placeholder?: string;
onClick?: () => void;
}>
> = ({ value, onChange, className, inputClass, dropClass, placeholder }) => {
const [showList, setShowlist] = useState(false);

const [keyword, setKeyword] = useState(value);
const throttledKeyword = useThrottle(keyword, { wait: 300 });
const { isLoading, data, fetchStatus } = useOrgSearchQuery(
client,
{
keyword: throttledKeyword,
},
{
enabled: Boolean(throttledKeyword),
onSuccess(res) {
if (res?.orgFuzzySearch?.length === 0) {
onChange(throttledKeyword);
}
},
}
);
const showLoading = isLoading && fetchStatus === 'fetching';

return (
<div className={classnames(className, 'group relative')}>
<Input
value={keyword}
onChange={(e) => {
setKeyword(e.target.value);
}}
onFocus={() => {
setShowlist(true);
}}
onBlur={() => {
setTimeout(() => {
setShowlist(false);
}, 200);
}}
placeholder={placeholder}
className={classnames(inputClass ? inputClass : 'ant-org-input')}
autoComplete={'off'}
/>
{data?.orgFuzzySearch?.length > 0 && (
<div
className={classnames(
'z-modal absolute max-h-36 w-full min-w-[300px] flex-1 overflow-auto border-black bg-white text-base outline-none',
dropClass,
{ hidden: !showList }
)}
id="option-list"
>
{data?.orgFuzzySearch?.map(({ orgName }) => {
return (
<div
key={orgName}
className="flex h-10 cursor-pointer flex-nowrap items-center px-4 hover:bg-gray-100"
onClick={() => {
setKeyword(orgName);
onChange(orgName);
}}
>
<span className="line-clamp-1"> {orgName}</span>
</div>
);
})}
</div>
)}
<div
className={classnames(
'absolute top-1.5 right-2 cursor-pointer text-[#CCCCCC]'
)}
>
{showLoading ? (
<AiOutlineLoading className="mr-1 animate-spin" />
) : null}
</div>
</div>
);
};

export default Select;
Loading

1 comment on commit c12c016

@vercel
Copy link

@vercel vercel bot commented on c12c016 Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.