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
6 changes: 3 additions & 3 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

셀프호스팅 가정용 재고·바코드 관리 — 기존 제품 바코드(UPC/EAN), 직접 발급한 QR 라벨, Matter 페어링 코드를 스캔해서 집안 물건을 관리합니다. 연속 스캔으로 입고·소비를 처리하고, 대시보드에서 재고부족·유통기한을 한눈에 보고, 라벨을 인쇄합니다.

> 현재 릴리스: **v0.6.2**
> 현재 릴리스: **v0.6.3**

문서: [`docs/ROADMAP.md`](./docs/ROADMAP.md)

Expand All @@ -20,10 +20,10 @@
## 기능

- 중첩 위치(방 / 선반 / 상자)를 들여쓰기 트리로 표시, 카테고리
- 수량, 재고부족 기준 수량, 유통기한·보증 만료일, 가격, 사진, 메모 — 이름·단위는 아이템 상세에서 바로 인라인 편집. 직접 등록 폼에서 촬영 또는 갤러리 선택으로 바로 사진을 넣을 수 있고(업로드 시 자동 리사이즈), 가격은 매번 입력하는 대신 설정에서 한 번 고른 기본 통화(원/달러)를 그대로 씀
- 수량, 재고부족 기준 수량, 유통기한·보증 만료일, 가격, 메모 — 이름·단위는 아이템 상세에서 바로 인라인 편집. 직접 등록 폼에서 촬영 또는 갤러리 선택으로 사진을 하나 골라둘 수 있고(업로드 시 자동 리사이즈), 가격은 매번 입력하는 대신 설정에서 한 번 고른 기본 통화(원/달러)를 그대로 씀
- 통합 `Item` + `Barcode` 모델 — 기존 UPC/EAN, 직접 발급 내부 QR(아이템 딥링크), Matter 페어링 코드, 수동 입력 시리얼번호(Matter 코드는 실제 커미셔닝이 아니라 식별자 용도임을 안내하는 문구 포함)를 하나의 바코드 타입으로
- 수량 관리와 별개로 자산(기기) 모드 — 아이템을 **자산**으로 전환하면 개별 기기 하나를 추적: 상태(신품/사용중/수리필요/폐기), 시리얼번호 바코드 등록, 정비 이력(날짜·내용·비용) 기록. 자산은 수량 스텝퍼·재고부족 필드가 숨겨지고 장보기/재고부족 로직에서 제외되며, 자산 바코드를 스캔하면 수량 조정 없이 해당 자산의 상세 페이지로 바로 이동합니다.
- 영수증·설명서·보증서 등 파일 첨부 — 아이템이나 자산에 PDF/이미지 형식의 여러 문서를 직접 업로드하여 보관할 수 있고, 업로드 시 이미지는 자동 리사이즈됩니다.
- 영수증·설명서·보증서·사진 등 파일 첨부 — 업로드 창구를 하나로 통합해 아이템·자산마다 PDF/이미지 문서를 여러 개 올려 보관하고, 파일 링크가 아니라 실제 썸네일로 미리 보여줍니다(업로드 시 이미지는 자동 리사이즈). 이미지 첨부 중 아무거나 대표 사진으로 지정할 수 있고, 이름 옆에 프로필 사진처럼 작게 표시됩니다 — 이미지가 하나뿐이면 자동으로 대표가 되고, 여러 장이면 직접 골라야 합니다.
- 연속 카메라 스캔: **입고(+1)** / **소비(−1)** 모드, 화면 전환 없이 계속 스캔 — 직접 등록 폼이나 바코드/Matter 코드 추가에서도 카메라 스캔 지원. 실사용 인식률/속도를 위해 포맷 제한·고해상도·연속 오토포커스로 튜닝했고, 스캔 성공 시 삑 소리·진동, 저조도용 플래시 토글 지원. 새로 자동 생성된 아이템은 그 자리에서 바로 위치·기준수량을 채우는 미니시트 제공
- 외부 제품 조회 연동을 다중 선택 가능(Open Food Facts, UPCItemDB, 네이버쇼핑) — 원하는 제공자만 켜거나 아예 꺼서 조회 자체를 생략
- 대시보드: 총 자산가치, 재고부족·유통기한 임박 항목을 전면에, 첫 사용 시 온보딩 체크리스트(위치·알림·공개URL) 제공
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

Self-hosted home inventory & barcode manager — track everything you own by scanning existing product barcodes (UPC/EAN), self-issued QR labels, or Matter pairing codes. Restock/consume with one continuous scan, watch low-stock and expiry from the dashboard, and print labels.

> Current release: **v0.6.2**
> Current release: **v0.6.3**

Docs: [`docs/ROADMAP.md`](./docs/ROADMAP.md)

Expand All @@ -20,10 +20,10 @@ Docs: [`docs/ROADMAP.md`](./docs/ROADMAP.md)
## Features

- Nested locations (rooms / shelves / boxes) rendered as an indented tree, and categories
- Items with quantity, low-stock threshold, expiry & warranty dates, price, photo, notes — name and unit are inline-editable right on the item detail page. Photos can be taken *or* picked from the gallery right in the "add manually" form (auto-resized on upload), and price uses a default currency (KRW/USD, set once in Settings) instead of typing it on every item
- Items with quantity, low-stock threshold, expiry & warranty dates, price, notes — name and unit are inline-editable right on the item detail page. An optional photo can be taken *or* picked from the gallery right in the "add manually" form (auto-resized on upload), and price uses a default currency (KRW/USD, set once in Settings) instead of typing it on every item
- Unified `Item` + `Barcode` model — existing UPC/EAN, self-issued internal QR (deep-links to the item), a Matter pairing code, or a manually-entered serial number (with inline copy explaining Matter codes are an identifier only, not real commissioning), all as one barcode type
- Asset mode alongside quantity tracking — flip an item to **Asset** to track a single physical device: condition (new / in use / needs repair / retired), a serial-number barcode entry, and a maintenance-history log (date, description, cost). Asset items hide the quantity stepper/low-stock fields, are excluded from shopping-list/low-stock logic, and scanning an asset's barcode redirects to its detail page instead of adjusting quantity
- File attachments for receipts, manuals, and warranties — upload and store multiple PDF or image documents per item/asset, with image auto-resizing on upload
- File attachments for receipts, manuals, warranties, and photosa single upload flow stores multiple PDF or image documents per item/asset, shown as real thumbnails (not just file links), with image auto-resizing on upload. Any image attachment can be set as the item's representative photo — shown as a small profile-style avatar next to the item name, automatic when there's only one image and a manual picker once there are several
- Continuous camera scanning: **Restock (+1)** / **Consume (−1)** modes, no screen transition between scans; camera scan is also available when registering an item manually or adding a barcode/Matter code. Tuned for real-world speed/accuracy (format hints, higher resolution, continuous autofocus), with an audible beep, haptic buzz, and a low-light flash toggle. A newly auto-created item gets a quick inline sheet to set its location/threshold on the spot
- Pluggable external product lookup (Open Food Facts, UPCItemDB, Naver Shopping) — pick which providers to use per barcode scan, or turn lookup off entirely
- Dashboard: total inventory value, low-stock and expiring-soon items front and center, plus a first-run onboarding checklist (location, notifications, public URL)
Expand Down
2 changes: 1 addition & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stash/api",
"version": "0.6.2",
"version": "0.6.3",
"private": true,
"type": "module",
"scripts": {
Expand Down
5 changes: 4 additions & 1 deletion apps/api/src/routes/items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,10 @@ export async function itemRoutes(app: FastifyInstance) {
const item = await prisma.item.update({
where: { id },
data,
include: ITEM_INCLUDE,
// attachments도 함께 내려줘야 한다 — 그냥 ITEM_INCLUDE만 쓰면 이름/위치 등 아무
// 필드나 하나 수정할 때마다 응답에 attachments가 빠져서 화면에서 첨부파일 목록이
// 사라져 보이는 문제가 있었다(대표 사진 지정도 이 PATCH를 타므로 같은 증상 발생).
include: { ...ITEM_INCLUDE, attachments: true },
});
void fireInventoryWebhook("item.updated", item);
return item;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/app/categories/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default function CategoriesPage() {
{c.parentId && <span className="meta"> · {t("subOf", { name: parentName(c.parentId) ?? "" })}</span>}
<span className="meta"> · {t("itemCount", { n: c._count?.items ?? 0 })}</span>
</div>
<button className="secondary" onClick={() => handleDelete(c.id)}>
<button type="button" className="btn-action btn-action-danger" onClick={() => handleDelete(c.id)}>
{t("delete")}
</button>
</div>
Expand Down
125 changes: 125 additions & 0 deletions apps/web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ h1 {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
}

Expand Down Expand Up @@ -248,6 +249,18 @@ button:disabled {
gap: 12px;
}

/* 대표 사진은 아이템의 "메인 콘텐츠"가 아니라 이름 옆에 붙는 프로필 사진 같은 식별용
아이콘이다 — 전체 사진을 다 보여주려 하지 않고, 목록의 .thumb과 같은 방식(고정 크기 +
object-fit: cover)으로 헤더 안에 작게 자리잡게 한다. */
.item-avatar {
width: 64px;
height: 64px;
border-radius: 12px;
object-fit: cover;
flex-shrink: 0;
background: var(--color-surface-hover);
}

.item-card .thumb {
width: 56px;
height: 56px;
Expand Down Expand Up @@ -791,10 +804,122 @@ button:disabled {

.tree-row-actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}

/* 첨부파일/바코드 같은 목록 행의 보조 액션 — 기본 button보다 훨씬 작게, garage 프로젝트의
.btn-action 컨벤션을 그대로 따른다. */
.btn-action {
min-height: 24px;
height: 24px;
font-size: 0.75rem;
padding: 0 8px;
border-radius: 6px;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 500;
border: 1px solid var(--color-border);
background: var(--color-surface);
color: var(--color-text-secondary);
text-decoration: none;
}

.btn-action:hover {
background: var(--color-surface-hover);
}

.btn-action-danger {
color: var(--color-danger);
}

.btn-action-danger:hover {
background: var(--color-danger-bg);
border-color: var(--color-danger);
color: var(--color-danger-text);
}

/* 첨부파일을 텍스트 링크가 아니라 실제 썸네일로 미리보여준다 — garage 프로젝트의
AttachmentList 타일 컨벤션(정사각 썸네일 + 모서리에 겹친 삭제 배지)을 그대로 따른다. */
.attachment-grid {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 14px;
margin-bottom: 8px;
}

.attachment-tile {
position: relative;
width: 84px;
}

.attachment-thumb {
display: flex;
align-items: center;
justify-content: center;
width: 84px;
height: 84px;
border-radius: 12px;
overflow: hidden;
background: var(--color-surface-hover);
border: 1px solid var(--color-border);
}

.attachment-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}

.attachment-file-badge {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
font-size: 0.7rem;
font-weight: 600;
color: var(--color-text-muted);
}

.attachment-primary-badge {
position: absolute;
top: 4px;
left: 4px;
font-size: 0.65rem;
font-weight: 600;
padding: 1px 6px;
border-radius: 999px;
background: var(--color-primary);
color: var(--color-primary-text);
pointer-events: none;
}

.attachment-remove-btn {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
min-height: 24px;
padding: 0;
border-radius: 50%;
border: 2px solid var(--color-surface);
background: var(--color-danger);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}

.attachment-set-primary {
width: 100%;
margin-top: 6px;
}

.fab-row {
display: flex;
gap: 8px;
Expand Down
Loading