Skip to content
Draft
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@

![image](https://github.com/user-attachments/assets/86e3cc34-4aac-4d04-8ce9-053afa0232d8)

## Features

### Todo 관리
- **eCampus 연동**: eCampusμ—μ„œ μˆ˜κ°• 쀑인 κ³Όλͺ©μ˜ ν•  일을 μžλ™μœΌλ‘œ κ°€μ Έμ˜΅λ‹ˆλ‹€
- **μ‚¬μš©μž μ •μ˜ Todo**: 직접 ν•  일을 μΆ”κ°€ν•˜κ³  관리할 수 μžˆμŠ΅λ‹ˆλ‹€
- **κ³Όλͺ© 라벨 μ‹œμŠ€ν…œ**: κ³Όλͺ©λͺ…을 색상과 ν•¨κ»˜ 라벨둜 μ €μž₯ν•˜μ—¬ 관리할 수 μžˆμŠ΅λ‹ˆλ‹€
- eCampus κ³Όλͺ© μžλ™ μ œμ•ˆ
- 인라인 라벨 생성 (GitHub 이슈 라벨과 μœ μ‚¬)
- 8κ°€μ§€ 색상 νŒ”λ ˆνŠΈ 제곡
- μ‚¬μš© λΉˆλ„ 기반 μžλ™ μ •λ ¬
- **μ •λ ¬ 및 내보내기**: D-Day κΈ°μ€€ μ •λ ¬, λ§ˆν¬λ‹€μš΄ ν˜•μ‹μœΌλ‘œ 내보내기

μžμ„Έν•œ λ‚΄μš©μ€ [Subject Label Feature Documentation](./docs/SUBJECT_LABEL_FEATURE.md)λ₯Ό μ°Έκ³ ν•˜μ„Έμš”.

### ν‚€λ³΄λ“œ 단좕킀

LinKU ν™•μž₯ ν”„λ‘œκ·Έλž¨μ„ λΉ λ₯΄κ²Œ μ—΄ 수 μžˆλŠ” 단좕킀:
Expand Down
303 changes: 303 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
# Subject Label Feature - Architecture

## Component Hierarchy

```
TodoList
β”œβ”€ TodoAddButton
β”‚ └─ TodoAddDialog
β”‚ └─ SubjectInput ← NEW COMPONENT
β”‚
└─ TodoItem (multiple)
└─ displays subject with color dot
```

## Data Flow

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ TodoList β”‚
β”‚ β”‚
β”‚ 1. Fetches eCampus todos β”‚
β”‚ 2. Extracts unique subjects β†’ eCampusSubjects[] β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ TodoAddButton β”‚ β”‚
β”‚ β”‚ Props: { eCampusSubjects } β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ β”‚ TodoAddDialog β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ Props: { eCampusSubjects } β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ SubjectInput β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ Props: { eCampusSubjects } β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ On Mount: β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Load saved labels from storage β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Sort by usage count β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ On Input: β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Filter labels + eCampus subjects β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Show dropdown with suggestions β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ On Select: β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Increment usage count β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Fill input field β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ On Create Label: β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Save to Chrome Storage β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ - Reload labels list β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ TodoItem β”‚ β”‚
β”‚ β”‚ Props: { todo } β”‚ β”‚
β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ On Mount: β”‚ β”‚
β”‚ β”‚ - If todo.subject exists β”‚ β”‚
β”‚ β”‚ β†’ Load matching label from storage β”‚ β”‚
β”‚ β”‚ β†’ Display color dot if label found β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## Storage Layer

```
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Chrome Storage Local API β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ β”‚
β”‚ Key: "subjectLabels" β”‚
β”‚ Value: SubjectLabel[] β”‚
β”‚ β”‚
β”‚ [ β”‚
β”‚ { β”‚
β”‚ id: "label-1696834567890-abc123", β”‚
β”‚ name: "μ›Ήν”„λ‘œκ·Έλž˜λ°", β”‚
β”‚ color: "#007a30", β”‚
β”‚ createdAt: 1696834567890, β”‚
β”‚ usageCount: 5 β”‚
β”‚ }, β”‚
β”‚ ... β”‚
β”‚ ] β”‚
β”‚ β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Utility Functions (subjectLabel.ts) β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ - getSubjectLabels() β†’ Read all labels β”‚
β”‚ - addSubjectLabel() β†’ Create new label β”‚
β”‚ - updateSubjectLabel() β†’ Update existing label β”‚
β”‚ - deleteSubjectLabel() β†’ Remove label β”‚
β”‚ - incrementSubjectLabelUsage() β†’ Track usage β”‚
β”‚ - getSubjectLabelByName() β†’ Find by name β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```

## State Management

### SubjectInput Component State

```typescript
const [labels, setLabels] = useState<SubjectLabel[]>([])
↓
Loaded from Chrome Storage on mount
Sorted by usageCount (descending)

const [showDropdown, setShowDropdown] = useState(false)
↓
Opens on input focus
Closes on outside click or selection

const [showColorPicker, setShowColorPicker] = useState(false)
↓
Opens when clicking "라벨둜 μΆ”κ°€"
Closes on color selection or cancel

const [newLabelColor, setNewLabelColor] = useState(DEFAULT_COLORS[0])
↓
Selected color for new label
Resets to default after creation
```

### TodoItem Component State

```typescript
const [subjectLabel, setSubjectLabel] = useState<SubjectLabel | null>(null)
↓
Loaded on mount if todo.subject exists
Used to display color dot
```

## Event Flow

### Create Label Flow

```
User types "μ›Ήν”„λ‘œκ·Έλž˜λ°"
↓
Dropdown shows "'μ›Ήν”„λ‘œκ·Έλž˜λ°' 라벨둜 μΆ”κ°€"
↓
User clicks on it
↓
setShowColorPicker(true)
↓
Color picker appears with 8 colors
↓
User selects green (#007a30)
↓
setNewLabelColor("#007a30")
↓
User clicks "생성"
↓
addSubjectLabel("μ›Ήν”„λ‘œκ·Έλž˜λ°", "#007a30")
↓
Chrome Storage: save label
↓
loadLabels() - refresh state
↓
setShowColorPicker(false)
↓
setShowDropdown(false)
↓
Input keeps "μ›Ήν”„λ‘œκ·Έλž˜λ°" value
```

### Select Label Flow

```
User clicks input field
↓
setShowDropdown(true)
↓
Dropdown shows:
- Saved labels (with colors)
- eCampus subjects (no colors)
↓
User types "μ›Ή" to filter
↓
filteredSuggestions() runs
↓
Only matching items shown
↓
User clicks "μ›Ήν”„λ‘œκ·Έλž˜λ°"
↓
handleSelectSuggestion("μ›Ήν”„λ‘œκ·Έλž˜λ°")
↓
onChange("μ›Ήν”„λ‘œκ·Έλž˜λ°")
↓
incrementSubjectLabelUsage("μ›Ήν”„λ‘œκ·Έλž˜λ°")
↓
Chrome Storage: update usageCount
↓
loadLabels() - refresh with new counts
↓
setShowDropdown(false)
```

## Integration Points

### With eCampus API

```
eCampusTodoListAPI()
↓
Returns: { todoList: ECampusTodoItem[] }
↓
Each item has: { subject: "μ›Ήν”„λ‘œκ·Έλž˜λ°", ... }
↓
TodoList extracts unique subjects
↓
const eCampusSubjects = useMemo(() =>
Array.from(new Set(
ecampusTodos.map(todo => todo.subject).filter(Boolean)
))
, [ecampusTodos])
↓
Passed down to SubjectInput as suggestions
```

### With Custom Todo Storage

```
addCustomTodo(title, dueDate, dueTime, subject)
↓
subject is optional string
↓
Stored in Chrome Storage under "customTodos"
↓
When rendered in TodoItem:
- Load matching SubjectLabel
- Display with color dot if label exists
```

## Type Safety

```typescript
// Type Definitions
interface SubjectLabel {
id: string
name: string
color: string
createdAt: number
usageCount: number
}

// Component Props
interface SubjectInputProps {
value: string
onChange: (value: string) => void
disabled?: boolean
eCampusSubjects?: string[]
}

// Utility Functions
addSubjectLabel(name: string, color: string): Promise<SubjectLabel>
getSubjectLabelByName(name: string): Promise<SubjectLabel | undefined>

// All interactions are type-checked at compile time
```

## Performance Optimization

1. **useMemo** for eCampus subject extraction (TodoList)
2. **useCallback** for event handlers (click outside)
3. **Conditional rendering** - dropdown only when open
4. **Efficient filtering** - simple string includes
5. **Minimal re-renders** - state updates only when needed
6. **Async loading** - labels loaded once on mount
7. **Cleanup** - event listeners properly removed

## Error Handling

```typescript
try {
await addSubjectLabel(name, color)
// Success: refresh labels
} catch (error) {
console.error("Failed to create label:", error)
// Label creation failed but doesn't break UI
}
```

All storage operations are wrapped in try-catch blocks with fallback to empty arrays/undefined.

## Accessibility

- βœ… Keyboard navigation (Tab, Enter, Escape)
- βœ… ARIA labels on buttons
- βœ… Semantic HTML (button, input)
- βœ… Focus management (auto-focus on input)
- βœ… Color contrast (WCAG AA compliant)

## Browser Compatibility

- βœ… Chrome 90+ (target platform)
- βœ… Uses standard APIs:
- Chrome Storage API
- DOM events
- Fetch API (for eCampus)
- ⚠️ Chrome Extension specific (not web-compatible)
Loading