Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions moon/apps/web/components/CodeView/Table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ const table = <T extends DirectoryType>({
</Table.Header>

<Table.Body>
{datasource.map((d) => {
{datasource.map((d, index) => {
if (d) {
return (
<Table.Row className='hover:bg-gray-100' key={d.oid}>
// eslint-disable-next-line react/no-array-index-key
<Table.Row className='hover:bg-gray-100' key={index}>
{columns.map((c, index) => (
<Table.Cell
onClick={(e) => {
e.stopPropagation()
onClick?.(d)
}}
justify={justify}
key={c.key + d.oid}
// eslint-disable-next-line react/no-array-index-key
key={c.key + index}
>
{c.render ? c.render(c.dataIndex[0], d, index) : null}
</Table.Cell>
Expand Down
10 changes: 9 additions & 1 deletion moon/apps/web/components/IndexPages/components.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forwardRef, useRef } from 'react'

import { LayeredHotkeys, LazyLoadingSpinner, SearchIcon } from '@gitmono/ui'
import { LayeredHotkeys, LazyLoadingSpinner, LoadingSpinner, SearchIcon } from '@gitmono/ui'
import { cn } from '@gitmono/ui/src/utils'

import { ScrollableContainer, ScrollableContainerProps } from '../ScrollableContainer'
Expand Down Expand Up @@ -39,6 +39,14 @@ export function IndexPageLoading() {
)
}

export function IndexPageInstantLoading() {
return (
<div className='flex flex-1 flex-col items-center justify-center'>
<LoadingSpinner />
</div>
)
}

export function IndexPageEmptyState({ children }: { children: React.ReactNode }) {
return (
<div className='mx-auto flex w-full max-w-lg flex-1 items-center justify-center'>
Expand Down
123 changes: 123 additions & 0 deletions moon/apps/web/components/Issues/IssueIndex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { useState } from 'react'
import { useAtom } from 'jotai'
import { useRouter } from 'next/router'
import { useDebounce } from 'use-debounce'

import { Button, LayeredHotkeys, UIText } from '@gitmono/ui'
import { cn } from '@gitmono/ui/src/utils'

import { FloatingNewDocButton } from '@/components/FloatingButtons/NewDoc'
import {
IndexPageContainer,
IndexPageContent,
IndexPageEmptyState,
IndexSearchInput
} from '@/components/IndexPages/components'
import { SplitViewContainer, SplitViewDetail } from '@/components/SplitView'
import { IssueBreadcrumbIcon } from '@/components/Titlebar/BreadcrumbPageIcons'
import { BreadcrumbLabel, BreadcrumbTitlebar } from '@/components/Titlebar/BreadcrumbTitlebar'
import { useScope } from '@/contexts/scope'

import { IssuesContent } from './IssuesContent'
import { filterAtom } from './utils/store'

export const IssueIndex = () => {
const [query, setQuery] = useState('')
const [queryDebounced] = useDebounce(query, 150)

const isSearching = query.length > 0
// const isSearchLoading = queryDebounced.length > 0 && getNotes.isFetching
const isSearchLoading = queryDebounced.length > 0

return (
<>
<FloatingNewDocButton />

<SplitViewContainer>
<IndexPageContainer>
<BreadcrumbTitlebar className='justify-between'>
<IssueBreadcrumbIcon />
<BreadcrumbLabel>Issue</BreadcrumbLabel>
<IndexSearchInput query={query} setQuery={setQuery} isSearchLoading={isSearchLoading} />

<Button variant='primary' size={'base'}>
Labels
</Button>
<NewIssueButton />
</BreadcrumbTitlebar>
<IndexPageContent id='/[org]/notes' className={cn('@container', '3xl:max-w-7xl max-w-7xl')}>
<IssuesContent searching={isSearching} />
</IndexPageContent>
</IndexPageContainer>

<SplitViewDetail />
</SplitViewContainer>
</>
)
}

export function IssueIndexTabFilter({
fullWidth = false,
openNum,
closeNum
}: {
fullWidth?: boolean
openNum?: number
closeNum?: number
}) {
const { scope } = useScope()

const [filter, setFilter] = useAtom(filterAtom(scope))

return (
<>
<LayeredHotkeys keys='1' callback={() => setFilter('open')} />
<LayeredHotkeys keys='2' callback={() => setFilter('closed')} />

<Button
size='sm'
fullWidth={fullWidth}
onClick={() => setFilter('open')}
variant={filter === 'open' ? 'flat' : 'plain'}
tooltip='Issues that are still open and need attention'
>
Open {openNum}
</Button>
<Button
size='sm'
fullWidth={fullWidth}
onClick={() => setFilter('closed')}
variant={filter === 'closed' ? 'flat' : 'plain'}
tooltip='Closed'
>
Closed {closeNum}
</Button>
</>
)
}

export const NewIssueButton = () => {
const router = useRouter()
const { scope } = useScope()

return (
<Button variant='primary' className='bg-[#1f883d]' size={'base'} onClick={() => router.push(`/${scope}/issue/new`)}>
New Issue
</Button>
)
}

export function IssueIndexEmptyState() {
return (
<IndexPageEmptyState>
<div className='flex flex-col gap-1'>
<UIText size='text-base' weight='font-semibold'>
No results
</UIText>
<UIText size='text-base' tertiary>
Try adjusting your search filters.
</UIText>
</div>
</IndexPageEmptyState>
)
}
154 changes: 154 additions & 0 deletions moon/apps/web/components/Issues/IssueList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { ReactNode } from 'react'
import { formatDistance, fromUnixTime } from 'date-fns'
import { useAtom, useAtomValue } from 'jotai'
import { atomFamily } from 'jotai/utils'
import { useRouter } from 'next/router'

import {
Button,
ChatBubbleIcon,
CheckCircleFilledFlushIcon,
ChevronDownIcon,
Command,
ConditionalWrap,
useCommand
} from '@gitmono/ui'

import { Item } from '@/components/Issues/IssuesContent'
import { darkModeAtom } from '@/components/Issues/utils/store'
import { SubjectCommand } from '@/components/Subject/SubjectCommand'
import { BreadcrumbTitlebar } from '@/components/Titlebar/BreadcrumbTitlebar'
import { useScope } from '@/contexts/scope'
import { atomWithWebStorage } from '@/utils/atomWithWebStorage'

import { IssueIndexTabFilter } from './IssueIndex'

interface Props {
Issuelists: Item[]
hideProject?: boolean
}

export function IssueList({ Issuelists }: Props) {
const { scope } = useScope()
const router = useRouter()
// const filter = useAtomValue(filterAtom(scope))
// const sort = useAtomValue(sortAtom({ scope, filter }))
const needsCommandWrap = !useCommand()
const isDark = useAtomValue(darkModeAtom)

return (
<>
{!isDark ? (
<div className='issuecontainer overflow-hidden rounded-md border border-[#d0d7de]'>
<ListBanner />

<ConditionalWrap
condition={needsCommandWrap}
wrap={(children) => (
<SubjectCommand>
<Command.List className='flex flex-1 flex-col'>{children}</Command.List>
</SubjectCommand>
)}
>
{Issuelists.map((i) => {
return (
<ListItem
key={i.link}
title={i.title}
leftIcon={<CheckCircleFilledFlushIcon color='#378f50' size={16} />}
rightIcon={<ChatBubbleIcon />}
onClick={() => router.push(`/${scope}/issue/${i.link}`)}
>
<div className='text-xs text-[#59636e]'>
{i.link} · {i.owner} {i.status}{' '}
{formatDistance(fromUnixTime(i.open_timestamp), new Date(), { addSuffix: true })}
</div>
</ListItem>
)
})}
</ConditionalWrap>
</div>
) : (
<div>darkMode</div>
)}
</>
)
}

type IssuePickerType = 'Author' | 'Labels' | 'Projects' | 'Milestones' | 'Assignees' | 'Types'

const ListPicker = <T extends IssuePickerType>({ Sign }: { Sign: T }) => {
const filterAtom = atomFamily(() => atomWithWebStorage<T>(`${Sign}:picker`, Sign))
const [_filter, setFilter] = useAtom(filterAtom(Sign))

// TODO
// logic of onClick will change later
// storage will store the specific value from backend when chose the options
return (
<>
<Button size='sm' onClick={() => setFilter(Sign)} variant={'plain'} tooltipShortcut={Sign}>
<div className='flex items-center justify-center'>
{Sign}
<ChevronDownIcon />
</div>
</Button>
</>
)
}

export const ListBanner = () => {
// TODO: Authors, Labels, Projects, Milestones, Assignees need to be stored in storgae in future
const pickerTypes: IssuePickerType[] = ['Author', 'Labels', 'Projects', 'Milestones', 'Assignees', 'Types']

return (
<>
<BreadcrumbTitlebar className='justify-between'>
<ConditionalWrap condition={true} wrap={(c) => <div>{c}</div>}>
<IssueIndexTabFilter />
</ConditionalWrap>
<ConditionalWrap condition={true} wrap={(c) => <div>{c}</div>}>
{pickerTypes.map((p) => (
<ListPicker key={p} Sign={p} />
))}
</ConditionalWrap>
</BreadcrumbTitlebar>
</>
)
}

export const ListItem = ({
title,
children,
leftIcon,
rightIcon,
onClick
}: {
title: string
children?: ReactNode
leftIcon?: ReactNode
rightIcon?: ReactNode
onClick?: () => void
}) => {
return (
<>
<div className='container flex justify-between border-b border-gray-300 px-3.5 py-3 hover:bg-black/[0.08]'>
<div className='left flex gap-3'>
<div className='mt-1'>{leftIcon}</div>
<div
onClick={(e) => {
e.stopPropagation()
onClick?.()
}}
className='inner flex flex-col hover:cursor-pointer'
>
{title}
{children}
</div>
</div>
<div className='right'>
<div className='mt-1'>{rightIcon}</div>
</div>
</div>
</>
)
}
11 changes: 7 additions & 4 deletions moon/apps/web/components/Issues/IssueNewPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use client'

import { useCallback, useState } from 'react'
import { Button, Flex, Input, Space } from 'antd/lib'
import { Flex, Input, Space } from 'antd/lib'
import { useRouter } from 'next/router'
import toast from 'react-hot-toast'

import { Button, LargeTitle } from '@gitmono/ui'

import RichEditor from '@/components/MrView/rich-editor/RichEditor'
import { usePostIssueSubmit } from '@/hooks/issues/usePostIssueSubmit'
import { apiErrorToast } from '@/utils/apiErrorToast'
Expand All @@ -15,7 +17,7 @@ export default function IssueNewPage() {
const [loadings, setLoadings] = useState<boolean[]>([])
const router = useRouter()
const { mutate: submitNewIssue } = usePostIssueSubmit()
const [editorHasText, setEditorHasText] = useState(false);
const [editorHasText, setEditorHasText] = useState(false)
const set_to_loading = (index: number) => {
setLoadings((prevLoadings) => {
const newLoadings = [...prevLoadings]
Expand Down Expand Up @@ -60,6 +62,7 @@ export default function IssueNewPage() {
return (
<>
<div className='container p-10'>
<LargeTitle>Add a title</LargeTitle>
<Space direction='vertical' style={{ width: '100%' }}>
<h1>
Add a title
Expand All @@ -74,9 +77,9 @@ export default function IssueNewPage() {
</Space>
<Space direction='vertical' style={{ width: '100%' }}>
<h1>Add a description</h1>
<RichEditor setEditorState={setEditorState} setEditorHasText={setEditorHasText}/>
<RichEditor setEditorState={setEditorState} setEditorHasText={setEditorHasText} />
<Flex justify={'flex-end'}>
<Button disabled={!editorHasText} loading={loadings[3]} onClick={() => submit(editorState)}>
<Button type={'submit'} disabled={!editorHasText} loading={loadings[3]} onClick={() => submit(editorState)}>
Submit New Issue
</Button>
</Flex>
Expand Down
Loading