b34b0dd7d6
Validators (Pushes and Pull Requests) / UI & Storage Validation (pull_request) Failing after 12s
Validators (Pushes and Pull Requests) / Database & Schema Validation (pull_request) Successful in 13s
Validators (Pushes and Pull Requests) / Core Validators & Database Setup (pull_request) Failing after 28s
Validators (Pushes and Pull Requests) / WBS & Audit Validations (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / .NET Contracts (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / Calibration & Performance (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / Operational Report & Decision Packet (pull_request) Has been skipped
Validators (Pushes and Pull Requests) / CI Workflow Lint (pull_request) Failing after 10s
Validators (Pushes and Pull Requests) / Security & Secrets (pull_request) Successful in 12s
Validators (Pushes and Pull Requests) / Notify PR Results (pull_request) Successful in 2s
Frontend CI Pipeline / ci-frontend-8-steps (pull_request) Failing after 2m50s
614 lines
11 KiB
Markdown
614 lines
11 KiB
Markdown
# Development Guide
|
|
|
|
## Quick Start
|
|
|
|
### 1. First-Time Setup
|
|
|
|
```bash
|
|
# Clone repository
|
|
git clone <repo-url>
|
|
cd oms-wms-erp
|
|
|
|
# Install dependencies (once)
|
|
npm install
|
|
|
|
# Or use Makefile
|
|
make install
|
|
```
|
|
|
|
### 2. Start Development
|
|
|
|
**Option A: Using npm**
|
|
```bash
|
|
# Terminal 1: Start Vite dev server
|
|
npm run dev
|
|
# → http://localhost:5173
|
|
|
|
# Terminal 2: Start Storybook
|
|
npm run storybook
|
|
# → http://localhost:6006
|
|
```
|
|
|
|
**Option B: Using Makefile**
|
|
```bash
|
|
# Terminal 1
|
|
make dev
|
|
|
|
# Terminal 2
|
|
make storybook
|
|
```
|
|
|
|
### 3. Quality Checks
|
|
|
|
```bash
|
|
# Before committing, run:
|
|
make verify
|
|
|
|
# Or individual checks:
|
|
npm run lint # ESLint + Prettier
|
|
npm run type-check # TypeScript
|
|
npm run test:unit # Unit tests
|
|
npm run build # Production build
|
|
```
|
|
|
|
---
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
src/
|
|
├── components/
|
|
│ ├── primitives/ # Layer 1: UI building blocks
|
|
│ │ ├── Button/
|
|
│ │ ├── Input/
|
|
│ │ ├── Select/
|
|
│ │ └── ... (30 total)
|
|
│ ├── fields/
|
|
│ │ ├── typed/ # Layer 2: Type-safe inputs
|
|
│ │ │ ├── TextField/
|
|
│ │ │ └── ... (12 total)
|
|
│ │ └── domain/ # Layer 3: Business-specific
|
|
│ │ ├── OrderLineField/
|
|
│ │ └── ... (12 total)
|
|
│ └── composites/ # Layer 4: Full workflows
|
|
│ ├── Order/
|
|
│ └── ... (11 total)
|
|
├── stores/ # Pinia state management
|
|
│ └── modules/
|
|
│ ├── orders.ts
|
|
│ ├── inventory.ts
|
|
│ └── ... (10 total)
|
|
├── views/ # Page components
|
|
│ ├── Home.vue
|
|
│ ├── Order/OrderList.vue
|
|
│ ├── Order/OrderForm.vue
|
|
│ └── ...
|
|
├── services/ # API client, validators, formatters
|
|
│ ├── api/
|
|
│ ├── validators/
|
|
│ └── formatters/
|
|
├── router.ts # Vue Router configuration
|
|
├── App.vue # Root component
|
|
└── main.ts # Entry point
|
|
```
|
|
|
|
---
|
|
|
|
## Creating New Components
|
|
|
|
### 1. Primitive Component (Layer 1)
|
|
|
|
**Example: Create TextareaBase**
|
|
|
|
```bash
|
|
# Create folder
|
|
mkdir -p src/components/primitives/Textarea
|
|
|
|
# Create component files
|
|
cat > src/components/primitives/Textarea/TextareaBase.vue << 'EOF'
|
|
<template>
|
|
<div class="mb-3">
|
|
<label v-if="label" :for="id" class="form-label">
|
|
{{ label }}
|
|
<span v-if="required" class="text-danger">*</span>
|
|
</label>
|
|
<textarea
|
|
:id="id"
|
|
:value="modelValue"
|
|
:placeholder="placeholder"
|
|
:rows="rows"
|
|
:disabled="disabled"
|
|
:class="['form-control', { 'is-invalid': errorMessage }]"
|
|
@input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
|
|
@blur="$emit('blur')"
|
|
/>
|
|
<div v-if="errorMessage" :id="`error-${id}`" class="invalid-feedback d-block">
|
|
{{ errorMessage }}
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
|
|
interface Props {
|
|
modelValue: string
|
|
label?: string
|
|
placeholder?: string
|
|
rows?: number
|
|
disabled?: boolean
|
|
required?: boolean
|
|
errorMessage?: string
|
|
}
|
|
|
|
withDefaults(defineProps<Props>(), {
|
|
rows: 4
|
|
})
|
|
|
|
const id = ref(`textarea-${Math.random().toString(36).slice(2, 11)}`)
|
|
|
|
defineEmits<{
|
|
'update:modelValue': [value: string]
|
|
blur: []
|
|
}>()
|
|
</script>
|
|
EOF
|
|
|
|
# Create Storybook stories
|
|
cat > src/components/primitives/Textarea/TextareaBase.stories.ts << 'EOF'
|
|
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
import TextareaBase from './TextareaBase.vue'
|
|
|
|
const meta = {
|
|
title: 'Primitives/Textarea',
|
|
component: TextareaBase,
|
|
tags: ['autodocs']
|
|
} satisfies Meta<typeof TextareaBase>
|
|
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
export const Default: Story = {
|
|
args: {
|
|
label: 'Comments',
|
|
placeholder: 'Enter your comments here...',
|
|
rows: 4
|
|
}
|
|
}
|
|
|
|
export const Error: Story = {
|
|
args: {
|
|
label: 'Comments',
|
|
errorMessage: 'This field is required'
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Create unit tests
|
|
cat > src/components/primitives/Textarea/TextareaBase.spec.ts << 'EOF'
|
|
import { describe, it, expect } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
import TextareaBase from './TextareaBase.vue'
|
|
|
|
describe('TextareaBase', () => {
|
|
it('renders with label', () => {
|
|
const wrapper = mount(TextareaBase, {
|
|
props: {
|
|
label: 'Comments'
|
|
}
|
|
})
|
|
expect(wrapper.text()).toContain('Comments')
|
|
})
|
|
|
|
it('emits update:modelValue on input', async () => {
|
|
const wrapper = mount(TextareaBase, {
|
|
props: {
|
|
modelValue: ''
|
|
}
|
|
})
|
|
await wrapper.find('textarea').setValue('test text')
|
|
expect(wrapper.emitted('update:modelValue')).toBeDefined()
|
|
})
|
|
})
|
|
EOF
|
|
```
|
|
|
|
### 2. Typed Field Component (Layer 2)
|
|
|
|
**Example: Create TimeField**
|
|
|
|
```bash
|
|
mkdir -p src/components/fields/typed/TimeField
|
|
|
|
# Create TimeField.vue (similar to DateField but for time input)
|
|
# Create TimeField.stories.ts (time picker stories)
|
|
# Create TimeField.spec.ts (validation tests)
|
|
```
|
|
|
|
### 3. Domain Field Component (Layer 3)
|
|
|
|
**Example: Create PriceField**
|
|
|
|
```bash
|
|
mkdir -p src/components/fields/domain/PriceField
|
|
|
|
# Create PriceField.vue (extends CurrencyField with KIS tick rules)
|
|
# Create PriceField.stories.ts (price input with suggestions)
|
|
# Create PriceField.spec.ts (tick rule validation)
|
|
```
|
|
|
|
### 4. Business Composite Component (Layer 4)
|
|
|
|
**Example: Create CustomerForm**
|
|
|
|
```bash
|
|
mkdir -p src/components/composites/Customer
|
|
|
|
# Create CustomerForm.vue (full CRUD form)
|
|
# Create CustomerForm.stories.ts (create/edit modes)
|
|
# Create CustomerForm.spec.ts (form submission, validation)
|
|
```
|
|
|
|
---
|
|
|
|
## Component Best Practices
|
|
|
|
### Props & Events
|
|
|
|
```typescript
|
|
// Good: Strongly typed
|
|
interface Props {
|
|
modelValue: string
|
|
label?: string
|
|
disabled?: boolean
|
|
errorMessage?: string
|
|
}
|
|
|
|
// ❌ Bad: Loose typing
|
|
props: {
|
|
value: String, // No type definition
|
|
options: Array // Unclear structure
|
|
}
|
|
```
|
|
|
|
### Slots
|
|
|
|
```vue
|
|
<template>
|
|
<!-- Named slots for flexibility -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<slot name="header">Default Header</slot>
|
|
</div>
|
|
<div class="card-body">
|
|
<slot />
|
|
</div>
|
|
<div class="card-footer">
|
|
<slot name="footer" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### Accessibility
|
|
|
|
```vue
|
|
<template>
|
|
<!-- Always use labels -->
|
|
<label :for="id" class="form-label">Name</label>
|
|
<input :id="id" :aria-describedby="errorId" />
|
|
|
|
<!-- Describe error messages -->
|
|
<div :id="errorId" v-if="error" class="invalid-feedback">
|
|
{{ error }}
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### Testing
|
|
|
|
```typescript
|
|
import { describe, it, expect } from 'vitest'
|
|
import { mount } from '@vue/test-utils'
|
|
|
|
describe('MyComponent', () => {
|
|
it('renders with props', () => {
|
|
const wrapper = mount(MyComponent, {
|
|
props: { label: 'Test' }
|
|
})
|
|
expect(wrapper.find('label').text()).toBe('Test')
|
|
})
|
|
|
|
it('emits events', async () => {
|
|
const wrapper = mount(MyComponent)
|
|
await wrapper.find('button').trigger('click')
|
|
expect(wrapper.emitted('click')).toHaveLength(1)
|
|
})
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Storybook Stories
|
|
|
|
### Basic Story
|
|
|
|
```typescript
|
|
import type { Meta, StoryObj } from '@storybook/vue3'
|
|
import MyComponent from './MyComponent.vue'
|
|
|
|
const meta = {
|
|
title: 'Primitives/MyComponent',
|
|
component: MyComponent,
|
|
tags: ['autodocs'],
|
|
argTypes: {
|
|
variant: {
|
|
control: 'select',
|
|
options: ['primary', 'secondary', 'danger']
|
|
},
|
|
size: {
|
|
control: 'select',
|
|
options: ['sm', 'md', 'lg']
|
|
}
|
|
}
|
|
} satisfies Meta<typeof MyComponent>
|
|
|
|
export default meta
|
|
type Story = StoryObj<typeof meta>
|
|
|
|
export const Primary: Story = {
|
|
args: {
|
|
variant: 'primary',
|
|
size: 'md'
|
|
},
|
|
slots: {
|
|
default: 'Click me'
|
|
}
|
|
}
|
|
|
|
export const Secondary: Story = {
|
|
args: {
|
|
variant: 'secondary'
|
|
}
|
|
}
|
|
```
|
|
|
|
### Story with Controls
|
|
|
|
```typescript
|
|
export const Interactive: Story = {
|
|
args: {
|
|
label: 'Username',
|
|
placeholder: 'Enter username',
|
|
disabled: false,
|
|
required: true
|
|
},
|
|
argTypes: {
|
|
disabled: { control: 'boolean' },
|
|
required: { control: 'boolean' }
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Running Tests
|
|
|
|
### Unit Tests
|
|
|
|
```bash
|
|
# Run all tests
|
|
npm run test:unit
|
|
|
|
# Run specific test
|
|
npm run test:unit -- ButtonBase.spec.ts
|
|
|
|
# Watch mode (re-run on file changes)
|
|
npm run test:unit -- --watch
|
|
|
|
# Coverage report
|
|
npm run test:unit -- --coverage
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
```bash
|
|
# Run with Vitest + MSW mocks
|
|
npm run test:integration
|
|
```
|
|
|
|
### E2E Tests
|
|
|
|
```bash
|
|
# Run Playwright tests
|
|
npm run test:e2e
|
|
|
|
# Run specific test
|
|
npm run test:e2e -- order-crud.spec.ts
|
|
|
|
# Debug mode
|
|
npm run test:e2e -- --debug
|
|
|
|
# UI mode (interactive)
|
|
npm run test:e2e -- --ui
|
|
```
|
|
|
|
---
|
|
|
|
## Code Quality
|
|
|
|
### ESLint
|
|
|
|
```bash
|
|
# Check all files
|
|
npm run lint
|
|
|
|
# Fix automatically
|
|
npm run lint -- --fix
|
|
|
|
# Check specific file
|
|
npx eslint src/components/Button/ButtonBase.vue
|
|
```
|
|
|
|
### TypeScript
|
|
|
|
```bash
|
|
# Run type checker
|
|
npm run type-check
|
|
|
|
# Show errors
|
|
npx vue-tsc --noEmit --pretty
|
|
```
|
|
|
|
### Prettier
|
|
|
|
```bash
|
|
# Format all files
|
|
npm run format
|
|
|
|
# Check formatting
|
|
npm run format:check
|
|
```
|
|
|
|
---
|
|
|
|
## Build & Deployment
|
|
|
|
### Development Build
|
|
|
|
```bash
|
|
npm run build:dev
|
|
# Creates dist/ with source maps
|
|
```
|
|
|
|
### Production Build
|
|
|
|
```bash
|
|
npm run build
|
|
# Creates optimized dist/ <500KB (gzipped)
|
|
```
|
|
|
|
### Preview Production Build
|
|
|
|
```bash
|
|
npm run preview
|
|
# Serves dist/ on http://localhost:4173/
|
|
```
|
|
|
|
### Build Storybook
|
|
|
|
```bash
|
|
npm run build-storybook
|
|
# Creates storybook-static/
|
|
# Deploy to GitHub Pages or Chromatic
|
|
```
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### Port Already in Use
|
|
|
|
```bash
|
|
# Use different port
|
|
npm run dev -- --port 5174
|
|
npm run storybook -- -p 6007
|
|
```
|
|
|
|
### Clear Cache
|
|
|
|
```bash
|
|
# Remove Vite cache
|
|
rm -rf node_modules/.vite
|
|
|
|
# Remove node_modules completely
|
|
rm -rf node_modules/
|
|
npm install
|
|
```
|
|
|
|
### TypeScript Errors
|
|
|
|
```bash
|
|
# See detailed errors
|
|
npm run type-check
|
|
|
|
# Fix common issues
|
|
# 1. Remove unused imports
|
|
# 2. Add type definitions
|
|
# 3. Fix implicit `any` types
|
|
```
|
|
|
|
### Test Timeouts
|
|
|
|
```bash
|
|
# Increase timeout
|
|
npm run test:unit -- --timeout=20000
|
|
```
|
|
|
|
---
|
|
|
|
## Git Workflow
|
|
|
|
### Before Committing
|
|
|
|
```bash
|
|
# Run all checks
|
|
make verify
|
|
|
|
# This runs:
|
|
# 1. ESLint (auto-fix)
|
|
# 2. TypeScript check
|
|
# 3. Unit tests
|
|
# 4. Production build
|
|
```
|
|
|
|
### Commit Message Format
|
|
|
|
```
|
|
feat(component): Add new Button component
|
|
|
|
- Implement primary, secondary, danger variants
|
|
- Add loading and disabled states
|
|
- Add 7 Storybook stories
|
|
- Add 8 unit tests (100% coverage)
|
|
- Add WCAG 2.1 AA accessibility
|
|
|
|
Closes #123
|
|
```
|
|
|
|
### Pre-commit Hooks
|
|
|
|
Husky automatically runs lint-staged before each commit:
|
|
- ESLint auto-fixes staged files
|
|
- Prettier formats staged files
|
|
- Commit blocked if errors found
|
|
|
|
---
|
|
|
|
## Phase 1 Checklist
|
|
|
|
- [ ] Vite + Vue 3 + TypeScript configured
|
|
- [ ] Storybook 7.0 with 8 addons
|
|
- [ ] ESLint + Prettier setup
|
|
- [ ] Pre-commit hooks (husky + lint-staged)
|
|
- [ ] 5 initial Primitive components
|
|
- [ ] 8 unit tests passing
|
|
- [ ] 7 Storybook stories
|
|
- [ ] npm run dev works (http://localhost:5173)
|
|
- [ ] npm run storybook works (http://localhost:6006)
|
|
- [ ] npm run build succeeds (<500KB)
|
|
- [ ] CI/CD pipeline configured (GitHub Actions)
|
|
- [ ] All developers can build locally
|
|
|
|
---
|
|
|
|
## Next Steps
|
|
|
|
1. **Phase 2**: Create remaining 25 Primitive components (Weeks 3-4)
|
|
2. **Phase 3**: Create Typed & Domain Fields + Pinia (Weeks 5-6)
|
|
3. **Phase 4**: Create Business Composites + E2E tests (Weeks 7-8)
|
|
|
|
---
|
|
|
|
**Questions?** See README.md or PHASE1-STEP2-VERIFICATION.md
|