Merge branch 'main' into feature-farm-setup

This commit is contained in:
Sosokker 2025-04-01 17:42:49 +07:00
commit f8752a94de
15 changed files with 718 additions and 275 deletions

View File

@ -26,11 +26,12 @@ type api struct {
httpClient *http.Client httpClient *http.Client
eventPublisher domain.EventPublisher eventPublisher domain.EventPublisher
userRepo domain.UserRepository userRepo domain.UserRepository
cropRepo domain.CroplandRepository cropRepo domain.CroplandRepository
farmRepo domain.FarmRepository farmRepo domain.FarmRepository
plantRepo domain.PlantRepository plantRepo domain.PlantRepository
inventoryRepo domain.InventoryRepository inventoryRepo domain.InventoryRepository
harvestRepo domain.HarvestRepository
} }
func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool, eventPublisher domain.EventPublisher) *api { func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool, eventPublisher domain.EventPublisher) *api {
@ -42,6 +43,7 @@ func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool, eventP
farmRepository := repository.NewPostgresFarm(pool) farmRepository := repository.NewPostgresFarm(pool)
plantRepository := repository.NewPostgresPlant(pool) plantRepository := repository.NewPostgresPlant(pool)
inventoryRepository := repository.NewPostgresInventory(pool) inventoryRepository := repository.NewPostgresInventory(pool)
harvestRepository := repository.NewPostgresHarvest(pool)
farmRepository.SetEventPublisher(eventPublisher) farmRepository.SetEventPublisher(eventPublisher)
@ -50,11 +52,12 @@ func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool, eventP
httpClient: client, httpClient: client,
eventPublisher: eventPublisher, eventPublisher: eventPublisher,
userRepo: userRepository, userRepo: userRepository,
cropRepo: croplandRepository, cropRepo: croplandRepository,
farmRepo: farmRepository, farmRepo: farmRepository,
plantRepo: plantRepository, plantRepo: plantRepository,
inventoryRepo: inventoryRepository, inventoryRepo: inventoryRepository,
harvestRepo: harvestRepository,
} }
} }

View File

@ -22,11 +22,11 @@ func (a *api) registerInventoryRoutes(_ chi.Router, api huma.API) {
}, a.createInventoryItemHandler) }, a.createInventoryItemHandler)
huma.Register(api, huma.Operation{ huma.Register(api, huma.Operation{
OperationID: "getInventoryItems", OperationID: "getInventoryItemsByUser",
Method: http.MethodGet, Method: http.MethodGet,
Path: prefix, Path: prefix,
Tags: tags, Tags: tags,
}, a.getInventoryItemsHandler) }, a.getInventoryItemsByUserHandler)
huma.Register(api, huma.Operation{ huma.Register(api, huma.Operation{
OperationID: "getInventoryItem", OperationID: "getInventoryItem",
@ -48,18 +48,66 @@ func (a *api) registerInventoryRoutes(_ chi.Router, api huma.API) {
Path: prefix + "/{id}", Path: prefix + "/{id}",
Tags: tags, Tags: tags,
}, a.deleteInventoryItemHandler) }, a.deleteInventoryItemHandler)
huma.Register(api, huma.Operation{
OperationID: "getInventoryStatus",
Method: http.MethodGet,
Path: prefix + "/status",
Tags: tags,
}, a.getInventoryStatusHandler)
huma.Register(api, huma.Operation{
OperationID: "getInventoryCategory",
Method: http.MethodGet,
Path: prefix + "/category",
Tags: tags,
}, a.getInventoryCategoryHandler)
huma.Register(api, huma.Operation{
OperationID: "getHarvestUnits",
Method: http.MethodGet,
Path: "/harvest/units",
Tags: []string{"harvest"},
}, a.getHarvestUnitsHandler)
}
type InventoryItemResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Category InventoryCategory `json:"category"`
Quantity float64 `json:"quantity"`
Unit HarvestUnit `json:"unit"`
DateAdded time.Time `json:"date_added"`
Status InventoryStatus `json:"status"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
type InventoryStatus struct {
ID int `json:"id"`
Name string `json:"name"`
}
type InventoryCategory struct {
ID int `json:"id"`
Name string `json:"name"`
}
type HarvestUnit struct {
ID int `json:"id"`
Name string `json:"name"`
} }
type CreateInventoryItemInput struct { type CreateInventoryItemInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"` Header string `header:"Authorization" required:"true" example:"Bearer token"`
UserID string `header:"user_id" required:"true" example:"user-uuid"`
Body struct { Body struct {
Name string `json:"name" required:"true"` Name string `json:"name" required:"true"`
Category string `json:"category" required:"true"` CategoryID int `json:"category_id" required:"true"`
Type string `json:"type" required:"true"` Quantity float64 `json:"quantity" required:"true"`
Quantity float64 `json:"quantity" required:"true"` UnitID int `json:"unit_id" required:"true"`
Unit string `json:"unit" required:"true"` DateAdded time.Time `json:"date_added" required:"true"`
DateAdded time.Time `json:"date_added" required:"true"` StatusID int `json:"status_id" required:"true"`
Status string `json:"status" required:"true" enum:"In Stock,Low Stock,Out of Stock"`
} }
} }
@ -69,15 +117,83 @@ type CreateInventoryItemOutput struct {
} }
} }
type UpdateInventoryItemInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
UserID string `header:"user_id" required:"true" example:"user-uuid"`
ID string `path:"id"`
Body struct {
Name string `json:"name"`
CategoryID int `json:"category_id"`
Quantity float64 `json:"quantity"`
UnitID int `json:"unit_id"`
DateAdded time.Time `json:"date_added"`
StatusID int `json:"status_id"`
}
}
type UpdateInventoryItemOutput struct {
Body InventoryItemResponse
}
type GetInventoryItemsInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
UserID string `header:"user_id" required:"true" example:"user-uuid"`
CategoryID int `query:"category_id"`
StatusID int `query:"status_id"`
StartDate time.Time `query:"start_date" format:"date-time"`
EndDate time.Time `query:"end_date" format:"date-time"`
SearchQuery string `query:"search"`
SortBy string `query:"sort_by" enum:"name,quantity,date_added,created_at"`
SortOrder string `query:"sort_order" enum:"asc,desc" default:"desc"`
}
type GetInventoryItemsOutput struct {
Body []InventoryItemResponse
}
type GetInventoryItemInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
UserID string `header:"user_id" required:"true" example:"user-uuid"`
ID string `path:"id"`
}
type GetInventoryItemOutput struct {
Body InventoryItemResponse
}
type DeleteInventoryItemInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
UserID string `header:"user_id" required:"true" example:"user-uuid"`
ID string `path:"id"`
}
type DeleteInventoryItemOutput struct {
Body struct {
Message string `json:"message"`
}
}
type GetInventoryStatusOutput struct {
Body []InventoryStatus
}
type GetInventoryCategoryOutput struct {
Body []InventoryCategory
}
type GetHarvestUnitsOutput struct {
Body []HarvestUnit
}
func (a *api) createInventoryItemHandler(ctx context.Context, input *CreateInventoryItemInput) (*CreateInventoryItemOutput, error) { func (a *api) createInventoryItemHandler(ctx context.Context, input *CreateInventoryItemInput) (*CreateInventoryItemOutput, error) {
item := &domain.InventoryItem{ item := &domain.InventoryItem{
Name: input.Body.Name, UserID: input.UserID,
Category: input.Body.Category, Name: input.Body.Name,
Type: input.Body.Type, CategoryID: input.Body.CategoryID,
Quantity: input.Body.Quantity, Quantity: input.Body.Quantity,
Unit: input.Body.Unit, UnitID: input.Body.UnitID,
DateAdded: input.Body.DateAdded, DateAdded: input.Body.DateAdded,
Status: domain.InventoryStatus(input.Body.Status), StatusID: input.Body.StatusID,
} }
if err := item.Validate(); err != nil { if err := item.Validate(); err != nil {
@ -94,40 +210,11 @@ func (a *api) createInventoryItemHandler(ctx context.Context, input *CreateInven
}{ID: item.ID}}, nil }{ID: item.ID}}, nil
} }
type GetInventoryItemsInput struct { func (a *api) getInventoryItemsByUserHandler(ctx context.Context, input *GetInventoryItemsInput) (*GetInventoryItemsOutput, error) {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
Category string `query:"category"`
Type string `query:"type"`
Status string `query:"status" enum:"In Stock,Low Stock,Out of Stock"`
StartDate time.Time `query:"start_date" format:"date-time"`
EndDate time.Time `query:"end_date" format:"date-time"`
SearchQuery string `query:"search"`
SortBy string `query:"sort_by" enum:"name,category,type,quantity,date_added,status,created_at"`
SortOrder string `query:"sort_order" enum:"asc,desc" default:"desc"`
}
type InventoryItemResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Type string `json:"type"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
DateAdded time.Time `json:"date_added"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
type GetInventoryItemsOutput struct {
Body []InventoryItemResponse
}
func (a *api) getInventoryItemsHandler(ctx context.Context, input *GetInventoryItemsInput) (*GetInventoryItemsOutput, error) {
filter := domain.InventoryFilter{ filter := domain.InventoryFilter{
Category: input.Category, UserID: input.UserID,
Type: input.Type, CategoryID: input.CategoryID,
Status: domain.InventoryStatus(input.Status), StatusID: input.StatusID,
StartDate: input.StartDate, StartDate: input.StartDate,
EndDate: input.EndDate, EndDate: input.EndDate,
SearchQuery: input.SearchQuery, SearchQuery: input.SearchQuery,
@ -138,7 +225,7 @@ func (a *api) getInventoryItemsHandler(ctx context.Context, input *GetInventoryI
Direction: input.SortOrder, Direction: input.SortOrder,
} }
items, err := a.inventoryRepo.GetWithFilter(ctx, filter, sort) items, err := a.inventoryRepo.GetByUserID(ctx, input.UserID, filter, sort)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -146,14 +233,22 @@ func (a *api) getInventoryItemsHandler(ctx context.Context, input *GetInventoryI
response := make([]InventoryItemResponse, len(items)) response := make([]InventoryItemResponse, len(items))
for i, item := range items { for i, item := range items {
response[i] = InventoryItemResponse{ response[i] = InventoryItemResponse{
ID: item.ID, ID: item.ID,
Name: item.Name, Name: item.Name,
Category: item.Category, Category: InventoryCategory{
Type: item.Type, ID: item.Category.ID,
Quantity: item.Quantity, Name: item.Category.Name,
Unit: item.Unit, },
Quantity: item.Quantity,
Unit: HarvestUnit{
ID: item.Unit.ID,
Name: item.Unit.Name,
},
DateAdded: item.DateAdded, DateAdded: item.DateAdded,
Status: string(item.Status), Status: InventoryStatus{
ID: item.Status.ID,
Name: item.Status.Name,
},
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,
} }
@ -162,55 +257,36 @@ func (a *api) getInventoryItemsHandler(ctx context.Context, input *GetInventoryI
return &GetInventoryItemsOutput{Body: response}, nil return &GetInventoryItemsOutput{Body: response}, nil
} }
type GetInventoryItemInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
ID string `path:"id"`
}
type GetInventoryItemOutput struct {
Body InventoryItemResponse
}
func (a *api) getInventoryItemHandler(ctx context.Context, input *GetInventoryItemInput) (*GetInventoryItemOutput, error) { func (a *api) getInventoryItemHandler(ctx context.Context, input *GetInventoryItemInput) (*GetInventoryItemOutput, error) {
item, err := a.inventoryRepo.GetByID(ctx, input.ID) item, err := a.inventoryRepo.GetByID(ctx, input.ID, input.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &GetInventoryItemOutput{Body: InventoryItemResponse{ return &GetInventoryItemOutput{Body: InventoryItemResponse{
ID: item.ID, ID: item.ID,
Name: item.Name, Name: item.Name,
Category: item.Category, Category: InventoryCategory{
Type: item.Type, ID: item.Category.ID,
Quantity: item.Quantity, Name: item.Category.Name,
Unit: item.Unit, },
Quantity: item.Quantity,
Unit: HarvestUnit{
ID: item.Unit.ID,
Name: item.Unit.Name,
},
DateAdded: item.DateAdded, DateAdded: item.DateAdded,
Status: string(item.Status), Status: InventoryStatus{
ID: item.Status.ID,
Name: item.Status.Name,
},
CreatedAt: item.CreatedAt, CreatedAt: item.CreatedAt,
UpdatedAt: item.UpdatedAt, UpdatedAt: item.UpdatedAt,
}}, nil }}, nil
} }
type UpdateInventoryItemInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
ID string `path:"id"`
Body struct {
Name string `json:"name"`
Category string `json:"category"`
Type string `json:"type"`
Quantity float64 `json:"quantity"`
Unit string `json:"unit"`
DateAdded time.Time `json:"date_added"`
Status string `json:"status" enum:"In Stock,Low Stock,Out of Stock"`
}
}
type UpdateInventoryItemOutput struct {
Body InventoryItemResponse
}
func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInventoryItemInput) (*UpdateInventoryItemOutput, error) { func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInventoryItemInput) (*UpdateInventoryItemOutput, error) {
item, err := a.inventoryRepo.GetByID(ctx, input.ID) item, err := a.inventoryRepo.GetByID(ctx, input.ID, input.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -218,23 +294,20 @@ func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInven
if input.Body.Name != "" { if input.Body.Name != "" {
item.Name = input.Body.Name item.Name = input.Body.Name
} }
if input.Body.Category != "" { if input.Body.CategoryID != 0 {
item.Category = input.Body.Category item.CategoryID = input.Body.CategoryID
}
if input.Body.Type != "" {
item.Type = input.Body.Type
} }
if input.Body.Quantity != 0 { if input.Body.Quantity != 0 {
item.Quantity = input.Body.Quantity item.Quantity = input.Body.Quantity
} }
if input.Body.Unit != "" { if input.Body.UnitID != 0 {
item.Unit = input.Body.Unit item.UnitID = input.Body.UnitID
} }
if !input.Body.DateAdded.IsZero() { if !input.Body.DateAdded.IsZero() {
item.DateAdded = input.Body.DateAdded item.DateAdded = input.Body.DateAdded
} }
if input.Body.Status != "" { if input.Body.StatusID != 0 {
item.Status = domain.InventoryStatus(input.Body.Status) item.StatusID = input.Body.StatusID
} }
if err := item.Validate(); err != nil { if err := item.Validate(); err != nil {
@ -246,38 +319,35 @@ func (a *api) updateInventoryItemHandler(ctx context.Context, input *UpdateInven
return nil, err return nil, err
} }
updatedItem, err := a.inventoryRepo.GetByID(ctx, input.ID) updatedItem, err := a.inventoryRepo.GetByID(ctx, input.ID, input.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &UpdateInventoryItemOutput{Body: InventoryItemResponse{ return &UpdateInventoryItemOutput{Body: InventoryItemResponse{
ID: updatedItem.ID, ID: updatedItem.ID,
Name: updatedItem.Name, Name: updatedItem.Name,
Category: updatedItem.Category, Category: InventoryCategory{
Type: updatedItem.Type, ID: updatedItem.Category.ID,
Quantity: updatedItem.Quantity, Name: updatedItem.Category.Name,
Unit: updatedItem.Unit, },
Quantity: updatedItem.Quantity,
Unit: HarvestUnit{
ID: updatedItem.Unit.ID,
Name: updatedItem.Unit.Name,
},
DateAdded: updatedItem.DateAdded, DateAdded: updatedItem.DateAdded,
Status: string(updatedItem.Status), Status: InventoryStatus{
ID: updatedItem.Status.ID,
Name: updatedItem.Status.Name,
},
CreatedAt: updatedItem.CreatedAt, CreatedAt: updatedItem.CreatedAt,
UpdatedAt: updatedItem.UpdatedAt, UpdatedAt: updatedItem.UpdatedAt,
}}, nil }}, nil
} }
type DeleteInventoryItemInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
ID string `path:"id"`
}
type DeleteInventoryItemOutput struct {
Body struct {
Message string `json:"message"`
}
}
func (a *api) deleteInventoryItemHandler(ctx context.Context, input *DeleteInventoryItemInput) (*DeleteInventoryItemOutput, error) { func (a *api) deleteInventoryItemHandler(ctx context.Context, input *DeleteInventoryItemInput) (*DeleteInventoryItemOutput, error) {
err := a.inventoryRepo.Delete(ctx, input.ID) err := a.inventoryRepo.Delete(ctx, input.ID, input.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -286,3 +356,54 @@ func (a *api) deleteInventoryItemHandler(ctx context.Context, input *DeleteInven
Message string `json:"message"` Message string `json:"message"`
}{Message: "Inventory item deleted successfully"}}, nil }{Message: "Inventory item deleted successfully"}}, nil
} }
func (a *api) getInventoryStatusHandler(ctx context.Context, input *struct{}) (*GetInventoryStatusOutput, error) {
statuses, err := a.inventoryRepo.GetStatuses(ctx)
if err != nil {
return nil, err
}
response := make([]InventoryStatus, len(statuses))
for i, status := range statuses {
response[i] = InventoryStatus{
ID: status.ID,
Name: status.Name,
}
}
return &GetInventoryStatusOutput{Body: response}, nil
}
func (a *api) getInventoryCategoryHandler(ctx context.Context, input *struct{}) (*GetInventoryCategoryOutput, error) {
categories, err := a.inventoryRepo.GetCategories(ctx)
if err != nil {
return nil, err
}
response := make([]InventoryCategory, len(categories))
for i, category := range categories {
response[i] = InventoryCategory{
ID: category.ID,
Name: category.Name,
}
}
return &GetInventoryCategoryOutput{Body: response}, nil
}
func (a *api) getHarvestUnitsHandler(ctx context.Context, input *struct{}) (*GetHarvestUnitsOutput, error) {
units, err := a.harvestRepo.GetUnits(ctx)
if err != nil {
return nil, err
}
response := make([]HarvestUnit, len(units))
for i, unit := range units {
response[i] = HarvestUnit{
ID: unit.ID,
Name: unit.Name,
}
}
return &GetHarvestUnitsOutput{Body: response}, nil
}

View File

@ -7,31 +7,41 @@ import (
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
) )
type InventoryStatus string type InventoryStatus struct {
ID int `json:"id"`
Name string `json:"name"`
}
const ( type InventoryCategory struct {
StatusInStock InventoryStatus = "In Stock" ID int `json:"id"`
StatusLowStock InventoryStatus = "Low Stock" Name string `json:"name"`
StatusOutOfStock InventoryStatus = "Out of Stock" }
)
type HarvestUnit struct {
ID int `json:"id"`
Name string `json:"name"`
}
type InventoryItem struct { type InventoryItem struct {
ID string ID string
Name string UserID string
Category string Name string
Type string CategoryID int
Quantity float64 Category InventoryCategory
Unit string Quantity float64
DateAdded time.Time UnitID int
Status InventoryStatus Unit HarvestUnit
CreatedAt time.Time DateAdded time.Time
UpdatedAt time.Time StatusID int
Status InventoryStatus
CreatedAt time.Time
UpdatedAt time.Time
} }
type InventoryFilter struct { type InventoryFilter struct {
Category string UserID string
Type string CategoryID int
Status InventoryStatus StatusID int
StartDate time.Time StartDate time.Time
EndDate time.Time EndDate time.Time
SearchQuery string SearchQuery string
@ -44,18 +54,26 @@ type InventorySort struct {
func (i *InventoryItem) Validate() error { func (i *InventoryItem) Validate() error {
return validation.ValidateStruct(i, return validation.ValidateStruct(i,
validation.Field(&i.UserID, validation.Required),
validation.Field(&i.Name, validation.Required), validation.Field(&i.Name, validation.Required),
validation.Field(&i.Category, validation.Required), validation.Field(&i.CategoryID, validation.Required),
validation.Field(&i.Type, validation.Required),
validation.Field(&i.Quantity, validation.Required, validation.Min(0.0)), validation.Field(&i.Quantity, validation.Required, validation.Min(0.0)),
validation.Field(&i.Unit, validation.Required), validation.Field(&i.UnitID, validation.Required),
validation.Field(&i.Status, validation.Required, validation.In(StatusInStock, StatusLowStock, StatusOutOfStock)), validation.Field(&i.StatusID, validation.Required),
validation.Field(&i.DateAdded, validation.Required),
) )
} }
type InventoryRepository interface { type InventoryRepository interface {
GetByID(ctx context.Context, id string) (InventoryItem, error) GetByID(ctx context.Context, id, userID string) (InventoryItem, error)
GetWithFilter(ctx context.Context, filter InventoryFilter, sort InventorySort) ([]InventoryItem, error) GetByUserID(ctx context.Context, userID string, filter InventoryFilter, sort InventorySort) ([]InventoryItem, error)
GetAll(ctx context.Context) ([]InventoryItem, error)
CreateOrUpdate(ctx context.Context, item *InventoryItem) error CreateOrUpdate(ctx context.Context, item *InventoryItem) error
Delete(ctx context.Context, id string) error Delete(ctx context.Context, id, userID string) error
GetStatuses(ctx context.Context) ([]InventoryStatus, error)
GetCategories(ctx context.Context) ([]InventoryCategory, error)
}
type HarvestRepository interface {
GetUnits(ctx context.Context) ([]HarvestUnit, error)
} }

View File

@ -0,0 +1,34 @@
package repository
import (
"context"
"github.com/forfarm/backend/internal/domain"
)
type postgresHarvestRepository struct {
conn Connection
}
func NewPostgresHarvest(conn Connection) domain.HarvestRepository {
return &postgresHarvestRepository{conn: conn}
}
func (p *postgresHarvestRepository) GetUnits(ctx context.Context) ([]domain.HarvestUnit, error) {
query := `SELECT id, name FROM harvest_units ORDER BY id`
rows, err := p.conn.Query(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var units []domain.HarvestUnit
for rows.Next() {
var u domain.HarvestUnit
if err := rows.Scan(&u.ID, &u.Name); err != nil {
return nil, err
}
units = append(units, u)
}
return units, nil
}

View File

@ -29,13 +29,13 @@ func (p *postgresInventoryRepository) fetch(ctx context.Context, query string, a
var i domain.InventoryItem var i domain.InventoryItem
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.ID,
&i.UserID,
&i.Name, &i.Name,
&i.Category, &i.CategoryID,
&i.Type,
&i.Quantity, &i.Quantity,
&i.Unit, &i.UnitID,
&i.DateAdded, &i.DateAdded,
&i.Status, &i.StatusID,
&i.CreatedAt, &i.CreatedAt,
&i.UpdatedAt, &i.UpdatedAt,
); err != nil { ); err != nil {
@ -46,80 +46,115 @@ func (p *postgresInventoryRepository) fetch(ctx context.Context, query string, a
return items, nil return items, nil
} }
func (p *postgresInventoryRepository) GetByID(ctx context.Context, id string) (domain.InventoryItem, error) { func (p *postgresInventoryRepository) GetByID(ctx context.Context, id, userID string) (domain.InventoryItem, error) {
query := ` query := `
SELECT id, name, category, type, quantity, unit, date_added, status, created_at, updated_at SELECT
FROM inventory_items i.id, i.user_id, i.name, i.category_id, i.quantity, i.unit_id,
WHERE id = $1` i.date_added, i.status_id, i.created_at, i.updated_at,
c.name as category_name,
s.name as status_name,
u.name as unit_name
FROM inventory_items i
LEFT JOIN inventory_category c ON i.category_id = c.id
LEFT JOIN inventory_status s ON i.status_id = s.id
LEFT JOIN harvest_units u ON i.unit_id = u.id
WHERE i.id = $1 AND i.user_id = $2`
items, err := p.fetch(ctx, query, id) rows, err := p.conn.Query(ctx, query, id, userID)
if err != nil { if err != nil {
return domain.InventoryItem{}, err return domain.InventoryItem{}, err
} }
if len(items) == 0 { defer rows.Close()
if !rows.Next() {
return domain.InventoryItem{}, domain.ErrNotFound return domain.InventoryItem{}, domain.ErrNotFound
} }
return items[0], nil
var item domain.InventoryItem
err = rows.Scan(
&item.ID,
&item.UserID,
&item.Name,
&item.CategoryID,
&item.Quantity,
&item.UnitID,
&item.DateAdded,
&item.StatusID,
&item.CreatedAt,
&item.UpdatedAt,
&item.Category.Name,
&item.Status.Name,
&item.Unit.Name,
)
if err != nil {
return domain.InventoryItem{}, err
}
return item, nil
} }
func (p *postgresInventoryRepository) GetWithFilter(ctx context.Context, filter domain.InventoryFilter, sort domain.InventorySort) ([]domain.InventoryItem, error) { func (p *postgresInventoryRepository) GetByUserID(
ctx context.Context,
userID string,
filter domain.InventoryFilter,
sort domain.InventorySort,
) ([]domain.InventoryItem, error) {
var query strings.Builder var query strings.Builder
args := []interface{}{} args := []interface{}{userID}
argPos := 1 argPos := 2
query.WriteString(` query.WriteString(`
SELECT id, name, category, type, quantity, unit, date_added, status, created_at, updated_at SELECT
FROM inventory_items i.id, i.user_id, i.name, i.category_id, i.quantity, i.unit_id,
WHERE 1=1`) i.date_added, i.status_id, i.created_at, i.updated_at,
c.name as category_name,
s.name as status_name,
u.name as unit_name
FROM inventory_items i
LEFT JOIN inventory_category c ON i.category_id = c.id
LEFT JOIN inventory_status s ON i.status_id = s.id
LEFT JOIN harvest_units u ON i.unit_id = u.id
WHERE i.user_id = $1`)
if filter.Category != "" { if filter.CategoryID != 0 {
query.WriteString(fmt.Sprintf(" AND category = $%d", argPos)) query.WriteString(fmt.Sprintf(" AND i.category_id = $%d", argPos))
args = append(args, filter.Category) args = append(args, filter.CategoryID)
argPos++ argPos++
} }
if filter.Type != "" { if filter.StatusID != 0 {
query.WriteString(fmt.Sprintf(" AND type = $%d", argPos)) query.WriteString(fmt.Sprintf(" AND i.status_id = $%d", argPos))
args = append(args, filter.Type) args = append(args, filter.StatusID)
argPos++
}
if filter.Status != "" {
query.WriteString(fmt.Sprintf(" AND status = $%d", argPos))
args = append(args, filter.Status)
argPos++ argPos++
} }
if !filter.StartDate.IsZero() { if !filter.StartDate.IsZero() {
query.WriteString(fmt.Sprintf(" AND date_added >= $%d", argPos)) query.WriteString(fmt.Sprintf(" AND i.date_added >= $%d", argPos))
args = append(args, filter.StartDate) args = append(args, filter.StartDate)
argPos++ argPos++
} }
if !filter.EndDate.IsZero() { if !filter.EndDate.IsZero() {
query.WriteString(fmt.Sprintf(" AND date_added <= $%d", argPos)) query.WriteString(fmt.Sprintf(" AND i.date_added <= $%d", argPos))
args = append(args, filter.EndDate) args = append(args, filter.EndDate)
argPos++ argPos++
} }
if filter.SearchQuery != "" { if filter.SearchQuery != "" {
query.WriteString(fmt.Sprintf(" AND name ILIKE $%d", argPos)) query.WriteString(fmt.Sprintf(" AND i.name ILIKE $%d", argPos))
args = append(args, "%"+filter.SearchQuery+"%") args = append(args, "%"+filter.SearchQuery+"%")
argPos++ argPos++
} }
if sort.Field == "" { if sort.Field == "" {
sort.Field = "date_added" sort.Field = "i.date_added"
sort.Direction = "desc" sort.Direction = "desc"
} }
validSortFields := map[string]bool{ validSortFields := map[string]bool{
"name": true, "name": true,
"category": true,
"type": true,
"quantity": true, "quantity": true,
"date_added": true, "date_added": true,
"status": true,
"created_at": true, "created_at": true,
} }
@ -132,7 +167,53 @@ func (p *postgresInventoryRepository) GetWithFilter(ctx context.Context, filter
} }
} }
return p.fetch(ctx, query.String(), args...) rows, err := p.conn.Query(ctx, query.String(), args...)
if err != nil {
return nil, err
}
defer rows.Close()
var items []domain.InventoryItem
for rows.Next() {
var item domain.InventoryItem
err := rows.Scan(
&item.ID,
&item.UserID,
&item.Name,
&item.CategoryID,
&item.Quantity,
&item.UnitID,
&item.DateAdded,
&item.StatusID,
&item.CreatedAt,
&item.UpdatedAt,
&item.Category.Name,
&item.Status.Name,
&item.Unit.Name,
)
if err != nil {
return nil, err
}
items = append(items, item)
}
return items, nil
}
func (p *postgresInventoryRepository) GetAll(ctx context.Context) ([]domain.InventoryItem, error) {
query := `
SELECT
i.id, i.user_id, i.name, i.category_id, i.quantity, i.unit_id,
i.date_added, i.status_id, i.created_at, i.updated_at,
c.name as category_name,
s.name as status_name,
u.name as unit_name
FROM inventory_items i
LEFT JOIN inventory_category c ON i.category_id = c.id
LEFT JOIN inventory_status s ON i.status_id = s.id
LEFT JOIN harvest_units u ON i.unit_id = u.id
ORDER BY i.created_at DESC`
return p.fetch(ctx, query)
} }
func (p *postgresInventoryRepository) CreateOrUpdate(ctx context.Context, item *domain.InventoryItem) error { func (p *postgresInventoryRepository) CreateOrUpdate(ctx context.Context, item *domain.InventoryItem) error {
@ -143,19 +224,19 @@ func (p *postgresInventoryRepository) CreateOrUpdate(ctx context.Context, item *
item.CreatedAt = now item.CreatedAt = now
query := ` query := `
INSERT INTO inventory_items INSERT INTO inventory_items
(id, name, category, type, quantity, unit, date_added, status, created_at, updated_at) (id, user_id, name, category_id, quantity, unit_id, date_added, status_id, created_at, updated_at)
VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id` RETURNING id`
return p.conn.QueryRow( return p.conn.QueryRow(
ctx, ctx,
query, query,
item.UserID,
item.Name, item.Name,
item.Category, item.CategoryID,
item.Type,
item.Quantity, item.Quantity,
item.Unit, item.UnitID,
item.DateAdded, item.DateAdded,
item.Status, item.StatusID,
item.CreatedAt, item.CreatedAt,
item.UpdatedAt, item.UpdatedAt,
).Scan(&item.ID) ).Scan(&item.ID)
@ -164,33 +245,70 @@ func (p *postgresInventoryRepository) CreateOrUpdate(ctx context.Context, item *
query := ` query := `
UPDATE inventory_items UPDATE inventory_items
SET name = $1, SET name = $1,
category = $2, category_id = $2,
type = $3, quantity = $3,
quantity = $4, unit_id = $4,
unit = $5, date_added = $5,
date_added = $6, status_id = $6,
status = $7, updated_at = $7
updated_at = $8 WHERE id = $8 AND user_id = $9
WHERE id = $9
RETURNING id` RETURNING id`
return p.conn.QueryRow( return p.conn.QueryRow(
ctx, ctx,
query, query,
item.Name, item.Name,
item.Category, item.CategoryID,
item.Type,
item.Quantity, item.Quantity,
item.Unit, item.UnitID,
item.DateAdded, item.DateAdded,
item.Status, item.StatusID,
item.UpdatedAt, item.UpdatedAt,
item.ID, item.ID,
item.UserID,
).Scan(&item.ID) ).Scan(&item.ID)
} }
func (p *postgresInventoryRepository) Delete(ctx context.Context, id string) error { func (p *postgresInventoryRepository) Delete(ctx context.Context, id, userID string) error {
query := `DELETE FROM inventory_items WHERE id = $1` query := `DELETE FROM inventory_items WHERE id = $1 AND user_id = $2`
_, err := p.conn.Exec(ctx, query, id) _, err := p.conn.Exec(ctx, query, id, userID)
return err return err
} }
func (p *postgresInventoryRepository) GetStatuses(ctx context.Context) ([]domain.InventoryStatus, error) {
query := `SELECT id, name FROM inventory_status ORDER BY id`
rows, err := p.conn.Query(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var statuses []domain.InventoryStatus
for rows.Next() {
var s domain.InventoryStatus
if err := rows.Scan(&s.ID, &s.Name); err != nil {
return nil, err
}
statuses = append(statuses, s)
}
return statuses, nil
}
func (p *postgresInventoryRepository) GetCategories(ctx context.Context) ([]domain.InventoryCategory, error) {
query := `SELECT id, name FROM inventory_category ORDER BY id`
rows, err := p.conn.Query(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var categories []domain.InventoryCategory
for rows.Next() {
var c domain.InventoryCategory
if err := rows.Scan(&c.ID, &c.Name); err != nil {
return nil, err
}
categories = append(categories, c)
}
return categories, nil
}

View File

@ -1,6 +1,7 @@
-- +goose Up -- +goose Up
CREATE TABLE inventory_items ( CREATE TABLE inventory_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
category TEXT NOT NULL, category TEXT NOT NULL,
type TEXT NOT NULL, type TEXT NOT NULL,
@ -9,8 +10,11 @@ CREATE TABLE inventory_items (
date_added TIMESTAMPTZ NOT NULL, date_added TIMESTAMPTZ NOT NULL,
status TEXT NOT NULL, status TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT fk_inventory_items_user FOREIGN KEY (user_id) REFERENCES users(uuid) ON DELETE CASCADE
); );
CREATE INDEX idx_inventory_items_category ON inventory_items(category); -- Create indexes
CREATE INDEX idx_inventory_items_status ON inventory_items(status); CREATE INDEX idx_inventory_items_user_id ON inventory_items(user_id);
CREATE INDEX idx_inventory_items_user_category ON inventory_items(user_id, category);
CREATE INDEX idx_inventory_items_user_status ON inventory_items(user_id, status);

View File

@ -0,0 +1,5 @@
-- +goose Up
CREATE TABLE inventory_status (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);

View File

@ -0,0 +1,15 @@
-- +goose Up
ALTER TABLE inventory_items
ADD COLUMN status_id INT;
UPDATE inventory_items
SET status_id = (SELECT id FROM inventory_status WHERE name = inventory_items.status);
ALTER TABLE inventory_items
DROP COLUMN status;
ALTER TABLE inventory_items
ADD CONSTRAINT fk_inventory_items_status FOREIGN KEY (status_id) REFERENCES inventory_status(id) ON DELETE CASCADE;
CREATE INDEX idx_inventory_items_status_id ON inventory_items(status_id);

View File

@ -0,0 +1,7 @@
-- +goose Up
-- Insert default statuses into the inventory_status table
INSERT INTO inventory_status (name)
VALUES
('In Stock'),
('Low Stock'),
('Out Of Stock');

View File

@ -0,0 +1,70 @@
-- +goose Up
-- Step 1: Create inventory_category table
CREATE TABLE inventory_category (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE
);
-- Step 2: Insert sample categories
INSERT INTO inventory_category (name)
VALUES
('Seeds'),
('Tools'),
('Chemicals');
-- Step 3: Add category_id column to inventory_items
ALTER TABLE inventory_items
ADD COLUMN category_id INT;
-- Step 4: Link inventory_items to inventory_category
ALTER TABLE inventory_items
ADD CONSTRAINT fk_inventory_category FOREIGN KEY (category_id) REFERENCES inventory_category(id) ON DELETE SET NULL;
-- Step 5: Remove old columns (type, category, unit) from inventory_items
ALTER TABLE inventory_items
DROP COLUMN type,
DROP COLUMN category,
DROP COLUMN unit;
-- Step 6: Add unit_id column to inventory_items
ALTER TABLE inventory_items
ADD COLUMN unit_id INT;
-- Step 7: Link inventory_items to harvest_units
ALTER TABLE inventory_items
ADD CONSTRAINT fk_inventory_unit FOREIGN KEY (unit_id) REFERENCES harvest_units(id) ON DELETE SET NULL;
-- Step 8: Insert new unit values into harvest_units
INSERT INTO harvest_units (name)
VALUES
('Tonne'),
('KG');
-- +goose Down
-- Reverse Step 8: Remove inserted unit values
DELETE FROM harvest_units WHERE name IN ('Tonne', 'KG');
-- Reverse Step 7: Remove the foreign key constraint
ALTER TABLE inventory_items
DROP CONSTRAINT fk_inventory_unit;
-- Reverse Step 6: Remove unit_id column from inventory_items
ALTER TABLE inventory_items
DROP COLUMN unit_id;
-- Reverse Step 5: Add back type, category, and unit columns
ALTER TABLE inventory_items
ADD COLUMN type TEXT NOT NULL,
ADD COLUMN category TEXT NOT NULL,
ADD COLUMN unit TEXT NOT NULL;
-- Reverse Step 4: Remove foreign key constraint from inventory_items
ALTER TABLE inventory_items
DROP CONSTRAINT fk_inventory_category;
-- Reverse Step 3: Remove category_id column from inventory_items
ALTER TABLE inventory_items
DROP COLUMN category_id;
-- Reverse Step 2: Drop inventory_category table
DROP TABLE inventory_category;

View File

@ -1,11 +1,29 @@
import axiosInstance from "./config"; import axiosInstance from "./config";
import type { InventoryItem, CreateInventoryItemInput } from "@/types"; import type {
InventoryItem,
CreateInventoryItemInput,
InventoryItemStatus,
} from "@/types";
/** /**
* Simulates an API call to fetch inventory items. * Simulates an API call to fetch inventory items.
* Waits for a simulated delay and then attempts an axios GET request. * Waits for a simulated delay and then attempts an axios GET request.
* If the request fails, returns fallback dummy data. * If the request fails, returns fallback dummy data.
*
*
*/ */
export async function fetchInventoryStatus(): Promise<InventoryItemStatus[]> {
try {
const response = await axiosInstance.get<InventoryItemStatus[]>(
"/inventory/status"
);
return response.data;
} catch (error) {
console.error("Error fetching inventory status:", error);
return [];
}
}
export async function fetchInventoryItems(): Promise<InventoryItem[]> { export async function fetchInventoryItems(): Promise<InventoryItem[]> {
try { try {
const response = await axiosInstance.get<InventoryItem[]>("/api/inventory"); const response = await axiosInstance.get<InventoryItem[]>("/api/inventory");
@ -51,7 +69,7 @@ export async function fetchInventoryItems(): Promise<InventoryItem[]> {
quantity: 150, quantity: 150,
unit: "kg", unit: "kg",
lastUpdated: "2023-03-15", lastUpdated: "2023-03-15",
status: "In Stock", status: "Out Of Stock",
}, },
{ {
id: 5, id: 5,

View File

@ -36,14 +36,32 @@ import { cn } from "@/lib/utils";
// import { updateInventoryItem } from "@/api/inventory"; // import { updateInventoryItem } from "@/api/inventory";
// import type { UpdateInventoryItemInput } from "@/types"; // import type { UpdateInventoryItemInput } from "@/types";
export function EditInventoryItem() { export interface EditInventoryItemProps {
const [date, setDate] = useState<Date | undefined>(); id: string;
name: string;
category: string;
status: string;
type: string;
unit: string;
quantity: number;
}
export function EditInventoryItem({
id,
name,
category,
status,
type,
unit,
quantity,
}: EditInventoryItemProps) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [itemName, setItemName] = useState(""); const [itemName, setItemName] = useState(name);
const [itemType, setItemType] = useState(""); const [itemType, setItemType] = useState(type);
const [itemCategory, setItemCategory] = useState(""); const [itemCategory, setItemCategory] = useState(category);
const [itemQuantity, setItemQuantity] = useState(0); const [itemQuantity, setItemQuantity] = useState(quantity);
const [itemUnit, setItemUnit] = useState(""); const [itemUnit, setItemUnit] = useState(unit);
const [itemStatus, setItemStatus] = useState(status);
// const queryClient = useQueryClient(); // const queryClient = useQueryClient();
@ -103,7 +121,7 @@ export function EditInventoryItem() {
<Label htmlFor="type" className="text-right"> <Label htmlFor="type" className="text-right">
Type Type
</Label> </Label>
<Select value={itemType} onValueChange={setItemType}> <Select value={itemType.toLowerCase()} onValueChange={setItemType}>
<SelectTrigger className="col-span-3"> <SelectTrigger className="col-span-3">
<SelectValue placeholder="Select type" /> <SelectValue placeholder="Select type" />
</SelectTrigger> </SelectTrigger>
@ -116,6 +134,27 @@ export function EditInventoryItem() {
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="type" className="text-right">
Status
</Label>
<Select
value={itemStatus.toLowerCase()}
onValueChange={setItemStatus}
>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Status</SelectLabel>
<SelectItem value="in stock">In Stock</SelectItem>
<SelectItem value="low stock">Low Stock</SelectItem>
<SelectItem value="out of stock">Out Of Stock</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4"> <div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="category" className="text-right"> <Label htmlFor="category" className="text-right">
Category Category
@ -152,33 +191,6 @@ export function EditInventoryItem() {
onChange={(e) => setItemUnit(e.target.value)} onChange={(e) => setItemUnit(e.target.value)}
/> />
</div> </div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="date" className="text-right">
Date
</Label>
<Popover>
<PopoverTrigger asChild>
<Button
variant={"outline"}
className={cn(
"col-span-3 justify-start text-left font-normal",
!date && "text-muted-foreground",
)}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : "Pick a date"}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
initialFocus
/>
</PopoverContent>
</Popover>
</div>
</div> </div>
<DialogFooter> <DialogFooter>
<Button type="submit" onClick={handleEdit}> <Button type="submit" onClick={handleEdit}>

View File

@ -1,13 +1,6 @@
"use client"; "use client";
import { import { useState, useMemo } from "react";
useState,
JSXElementConstructor,
ReactElement,
ReactNode,
ReactPortal,
useMemo,
} from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { import {
useReactTable, useReactTable,
@ -38,9 +31,12 @@ import { Search } from "lucide-react";
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa"; import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { fetchInventoryItems } from "@/api/inventory"; import { fetchInventoryItems, fetchInventoryStatus } from "@/api/inventory";
import { AddInventoryItem } from "./add-inventory-item"; import { AddInventoryItem } from "./add-inventory-item";
import { EditInventoryItem } from "./edit-inventory-item"; import {
EditInventoryItem,
EditInventoryItemProps,
} from "./edit-inventory-item";
import { DeleteInventoryItem } from "./delete-inventory-item"; import { DeleteInventoryItem } from "./delete-inventory-item";
export default function InventoryPage() { export default function InventoryPage() {
@ -52,26 +48,42 @@ export default function InventoryPage() {
const { const {
data: inventoryItems = [], data: inventoryItems = [],
isLoading, isLoading: isItemLoading,
isError, isError: isItemError,
} = useQuery({ } = useQuery({
queryKey: ["inventoryItems"], queryKey: ["inventoryItems"],
queryFn: fetchInventoryItems, queryFn: fetchInventoryItems,
staleTime: 60 * 1000, staleTime: 60 * 1000,
}); });
const {
data: inventoryStatus = [],
isLoading: isLoadingStatus,
isError: isErrorStatus,
} = useQuery({
queryKey: ["inventoryStatus"],
queryFn: fetchInventoryStatus,
staleTime: 60 * 1000,
});
// console.table(inventoryItems); // console.table(inventoryItems);
console.table(inventoryStatus);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const filteredItems = useMemo(() => { const filteredItems = useMemo(() => {
return inventoryItems.filter((item) => return inventoryItems
item.name.toLowerCase().includes(searchTerm.toLowerCase()) .map((item) => ({
); ...item,
id: String(item.id), // Convert `id` to string here
}))
.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [inventoryItems, searchTerm]); }, [inventoryItems, searchTerm]);
const columns = [ const columns = [
{ accessorKey: "name", header: "Name" }, { accessorKey: "name", header: "Name" },
{ accessorKey: "category", header: "Category" }, { accessorKey: "category", header: "Category" },
{ accessorKey: "type", header: "Type" },
{ accessorKey: "quantity", header: "Quantity" }, { accessorKey: "quantity", header: "Quantity" },
{ accessorKey: "unit", header: "Unit" },
{ accessorKey: "lastUpdated", header: "Last Updated" }, { accessorKey: "lastUpdated", header: "Last Updated" },
{ {
accessorKey: "status", accessorKey: "status",
@ -83,7 +95,7 @@ export default function InventoryPage() {
if (status === "Low Stock") { if (status === "Low Stock") {
statusClass = "bg-yellow-300"; // yellow for low stock statusClass = "bg-yellow-300"; // yellow for low stock
} else if (status === "Out of Stock") { } else if (status === "Out Of Stock") {
statusClass = "bg-red-500 text-white"; // red for out of stock statusClass = "bg-red-500 text-white"; // red for out of stock
} }
@ -97,7 +109,9 @@ export default function InventoryPage() {
{ {
accessorKey: "edit", accessorKey: "edit",
header: "Edit", header: "Edit",
cell: () => <EditInventoryItem />, cell: ({ row }: { row: { original: EditInventoryItemProps } }) => (
<EditInventoryItem {...row.original} />
),
enableSorting: false, enableSorting: false,
}, },
{ {
@ -119,13 +133,13 @@ export default function InventoryPage() {
onPaginationChange: setPagination, onPaginationChange: setPagination,
}); });
if (isLoading) if (isItemLoading || isLoadingStatus)
return ( return (
<div className="flex min-h-screen items-center justify-center"> <div className="flex min-h-screen items-center justify-center">
Loading... Loading...
</div> </div>
); );
if (isError) if (isItemError || isErrorStatus)
return ( return (
<div className="flex min-h-screen items-center justify-center"> <div className="flex min-h-screen items-center justify-center">
Error loading inventory data. Error loading inventory data.
@ -183,7 +197,7 @@ export default function InventoryPage() {
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{table.getRowModel().rows.map((row) => ( {table.getRowModel().rows.map((row) => (
<TableRow key={row.id}> <TableRow key={row.id} className="even:bg-gray-800">
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}> <TableCell key={cell.id}>
{flexRender( {flexRender(

View File

@ -120,6 +120,10 @@ export type InventoryItem = {
lastUpdated: string; lastUpdated: string;
status: string; status: string;
}; };
export type InventoryItemStatus = {
id: number;
name: string;
};
export type CreateInventoryItemInput = Omit<InventoryItem, "id" | "lastUpdated" | "status">; export type CreateInventoryItemInput = Omit<InventoryItem, "id" | "lastUpdated" | "status">;