-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:add manageuser org modify (#305)
Signed-off-by: laixingyou <[email protected]>
- Loading branch information
1 parent
a2079a2
commit c12c016
Showing
18 changed files
with
1,144 additions
and
9 deletions.
There are no files selected for viewing
Submodule i18n
updated
4 files
+4 −1 | en/analyze.json | |
+8 −2 | en/common.json | |
+4 −1 | zh/analyze.json | |
+17 −11 | zh/common.json |
88 changes: 88 additions & 0 deletions
88
apps/web/src/common/components/OrgEdit/DateRangePicker.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
143
apps/web/src/common/components/OrgEdit/ManageOrgEdit.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Oops, something went wrong.
c12c016
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Successfully deployed to the following URLs:
compass-web – ./
compass-web-compass-team.vercel.app
compass-web-preview.vercel.app
compass-web-git-main-compass-team.vercel.app