Keep the closed box visible and tidy the order item card
- After closing, the box button stays disabled as "Box N closed" for 5 minutes, timed from a new picked_up_at so it survives a reload - Picked-up items keep showing their pickup box number - Hide the steps toggle on ready and picked-up items - Move "Buy again" and "Get help" into a dots menu in the item header Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "order_items" ADD COLUMN "picked_up_at" timestamp;
|
||||||
@@ -0,0 +1,948 @@
|
|||||||
|
{
|
||||||
|
"id": "4826f90c-c779-4b2d-ab6e-491e72dbaee8",
|
||||||
|
"prevId": "42d1f3c1-18f8-42a3-ac41-9e29a4b14add",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.allergens": {
|
||||||
|
"name": "allergens",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"allergens_name_idx": {
|
||||||
|
"name": "allergens_name_idx",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "name",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.brands": {
|
||||||
|
"name": "brands",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.contact_messages": {
|
||||||
|
"name": "contact_messages",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"name": "message",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.machine_tags": {
|
||||||
|
"name": "machine_tags",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"machine_tags_name_idx": {
|
||||||
|
"name": "machine_tags_name_idx",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "name",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.machines": {
|
||||||
|
"name": "machines",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"machine_tag_id": {
|
||||||
|
"name": "machine_tag_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"machines_machine_tag_id_machine_tags_id_fk": {
|
||||||
|
"name": "machines_machine_tag_id_machine_tags_id_fk",
|
||||||
|
"tableFrom": "machines",
|
||||||
|
"tableTo": "machine_tags",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_tag_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.menu_item_allergens": {
|
||||||
|
"name": "menu_item_allergens",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"menu_item_id": {
|
||||||
|
"name": "menu_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"allergen_id": {
|
||||||
|
"name": "allergen_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"menu_item_allergens_item_allergen_idx": {
|
||||||
|
"name": "menu_item_allergens_item_allergen_idx",
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"expression": "menu_item_id",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"expression": "allergen_id",
|
||||||
|
"isExpression": false,
|
||||||
|
"asc": true,
|
||||||
|
"nulls": "last"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"isUnique": true,
|
||||||
|
"concurrently": false,
|
||||||
|
"method": "btree",
|
||||||
|
"with": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"menu_item_allergens_menu_item_id_menu_items_id_fk": {
|
||||||
|
"name": "menu_item_allergens_menu_item_id_menu_items_id_fk",
|
||||||
|
"tableFrom": "menu_item_allergens",
|
||||||
|
"tableTo": "menu_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"menu_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"menu_item_allergens_allergen_id_allergens_id_fk": {
|
||||||
|
"name": "menu_item_allergens_allergen_id_allergens_id_fk",
|
||||||
|
"tableFrom": "menu_item_allergens",
|
||||||
|
"tableTo": "allergens",
|
||||||
|
"columnsFrom": [
|
||||||
|
"allergen_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.menu_item_options": {
|
||||||
|
"name": "menu_item_options",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"menu_item_id": {
|
||||||
|
"name": "menu_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"price_delta_cents": {
|
||||||
|
"name": "price_delta_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"menu_item_options_menu_item_id_menu_items_id_fk": {
|
||||||
|
"name": "menu_item_options_menu_item_id_menu_items_id_fk",
|
||||||
|
"tableFrom": "menu_item_options",
|
||||||
|
"tableTo": "menu_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"menu_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.menu_item_steps": {
|
||||||
|
"name": "menu_item_steps",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"menu_item_id": {
|
||||||
|
"name": "menu_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"name": "sequence",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"machine_tag_id": {
|
||||||
|
"name": "machine_tag_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"estimated_duration_seconds": {
|
||||||
|
"name": "estimated_duration_seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"menu_item_steps_menu_item_id_menu_items_id_fk": {
|
||||||
|
"name": "menu_item_steps_menu_item_id_menu_items_id_fk",
|
||||||
|
"tableFrom": "menu_item_steps",
|
||||||
|
"tableTo": "menu_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"menu_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"menu_item_steps_machine_tag_id_machine_tags_id_fk": {
|
||||||
|
"name": "menu_item_steps_machine_tag_id_machine_tags_id_fk",
|
||||||
|
"tableFrom": "menu_item_steps",
|
||||||
|
"tableTo": "machine_tags",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_tag_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.menu_items": {
|
||||||
|
"name": "menu_items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"brand_id": {
|
||||||
|
"name": "brand_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"price_cents": {
|
||||||
|
"name": "price_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"menu_items_brand_id_brands_id_fk": {
|
||||||
|
"name": "menu_items_brand_id_brands_id_fk",
|
||||||
|
"tableFrom": "menu_items",
|
||||||
|
"tableTo": "brands",
|
||||||
|
"columnsFrom": [
|
||||||
|
"brand_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.order_item_options": {
|
||||||
|
"name": "order_item_options",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"order_item_id": {
|
||||||
|
"name": "order_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"price_delta_cents": {
|
||||||
|
"name": "price_delta_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"order_item_options_order_item_id_order_items_id_fk": {
|
||||||
|
"name": "order_item_options_order_item_id_order_items_id_fk",
|
||||||
|
"tableFrom": "order_item_options",
|
||||||
|
"tableTo": "order_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"order_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.order_item_steps": {
|
||||||
|
"name": "order_item_steps",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"order_item_id": {
|
||||||
|
"name": "order_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"name": "sequence",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"machine_tag_id": {
|
||||||
|
"name": "machine_tag_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"estimated_duration_seconds": {
|
||||||
|
"name": "estimated_duration_seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"order_item_steps_order_item_id_order_items_id_fk": {
|
||||||
|
"name": "order_item_steps_order_item_id_order_items_id_fk",
|
||||||
|
"tableFrom": "order_item_steps",
|
||||||
|
"tableTo": "order_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"order_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"order_item_steps_machine_tag_id_machine_tags_id_fk": {
|
||||||
|
"name": "order_item_steps_machine_tag_id_machine_tags_id_fk",
|
||||||
|
"tableFrom": "order_item_steps",
|
||||||
|
"tableTo": "machine_tags",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_tag_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.order_items": {
|
||||||
|
"name": "order_items",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"order_id": {
|
||||||
|
"name": "order_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"menu_item_id": {
|
||||||
|
"name": "menu_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"price_cents": {
|
||||||
|
"name": "price_cents",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'placed'"
|
||||||
|
},
|
||||||
|
"cancelled_by": {
|
||||||
|
"name": "cancelled_by",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"pickup_box_id": {
|
||||||
|
"name": "pickup_box_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"picked_up_at": {
|
||||||
|
"name": "picked_up_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"order_items_order_id_orders_id_fk": {
|
||||||
|
"name": "order_items_order_id_orders_id_fk",
|
||||||
|
"tableFrom": "order_items",
|
||||||
|
"tableTo": "orders",
|
||||||
|
"columnsFrom": [
|
||||||
|
"order_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"order_items_menu_item_id_menu_items_id_fk": {
|
||||||
|
"name": "order_items_menu_item_id_menu_items_id_fk",
|
||||||
|
"tableFrom": "order_items",
|
||||||
|
"tableTo": "menu_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"menu_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"order_items_pickup_box_id_pickup_boxes_id_fk": {
|
||||||
|
"name": "order_items_pickup_box_id_pickup_boxes_id_fk",
|
||||||
|
"tableFrom": "order_items",
|
||||||
|
"tableTo": "pickup_boxes",
|
||||||
|
"columnsFrom": [
|
||||||
|
"pickup_box_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.orders": {
|
||||||
|
"name": "orders",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"name": "customer_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"guest_name": {
|
||||||
|
"name": "guest_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"guest_contact": {
|
||||||
|
"name": "guest_contact",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"guest_access_token": {
|
||||||
|
"name": "guest_access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'placed'"
|
||||||
|
},
|
||||||
|
"payment_charge_id": {
|
||||||
|
"name": "payment_charge_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.pickup_boxes": {
|
||||||
|
"name": "pickup_boxes",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"name": "number",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"is_open": {
|
||||||
|
"name": "is_open",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"pickup_boxes_number_unique": {
|
||||||
|
"name": "pickup_boxes_number_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"number"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.preparation_step_instances": {
|
||||||
|
"name": "preparation_step_instances",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"order_item_id": {
|
||||||
|
"name": "order_item_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"name": "sequence",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"machine_tag_id": {
|
||||||
|
"name": "machine_tag_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"estimated_duration_seconds": {
|
||||||
|
"name": "estimated_duration_seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"delay_seconds": {
|
||||||
|
"name": "delay_seconds",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'queued'"
|
||||||
|
},
|
||||||
|
"machine_id": {
|
||||||
|
"name": "machine_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"name": "started_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"done_at": {
|
||||||
|
"name": "done_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"preparation_step_instances_order_item_id_order_items_id_fk": {
|
||||||
|
"name": "preparation_step_instances_order_item_id_order_items_id_fk",
|
||||||
|
"tableFrom": "preparation_step_instances",
|
||||||
|
"tableTo": "order_items",
|
||||||
|
"columnsFrom": [
|
||||||
|
"order_item_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"preparation_step_instances_machine_tag_id_machine_tags_id_fk": {
|
||||||
|
"name": "preparation_step_instances_machine_tag_id_machine_tags_id_fk",
|
||||||
|
"tableFrom": "preparation_step_instances",
|
||||||
|
"tableTo": "machine_tags",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_tag_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"preparation_step_instances_machine_id_machines_id_fk": {
|
||||||
|
"name": "preparation_step_instances_machine_id_machines_id_fk",
|
||||||
|
"tableFrom": "preparation_step_instances",
|
||||||
|
"tableTo": "machines",
|
||||||
|
"columnsFrom": [
|
||||||
|
"machine_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,13 @@
|
|||||||
"when": 1790845995013,
|
"when": 1790845995013,
|
||||||
"tag": "0008_absurd_the_anarchist",
|
"tag": "0008_absurd_the_anarchist",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 9,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1790888552762,
|
||||||
|
"tag": "0009_damp_ender_wiggin",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -94,10 +94,81 @@
|
|||||||
.itemHeader {
|
.itemHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: $space-2;
|
margin-bottom: $space-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menuButton {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: (-$space-2) (-$space-2) (-$space-2) $space-2;
|
||||||
|
padding: $space-2;
|
||||||
|
border: none;
|
||||||
|
border-radius: $radius;
|
||||||
|
background: none;
|
||||||
|
color: $color-text-muted;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
stroke-width: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $color-text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
position: fixed;
|
||||||
|
position-area: bottom span-left;
|
||||||
|
position-try-fallbacks: flip-block;
|
||||||
|
inset: auto;
|
||||||
|
margin: $space-1 0;
|
||||||
|
padding: $space-1;
|
||||||
|
min-width: 160px;
|
||||||
|
border: 1px solid $color-border;
|
||||||
|
border-radius: $radius;
|
||||||
|
background: $color-surface;
|
||||||
|
color: $color-text;
|
||||||
|
font-weight: 400;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuItem {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: $space-2;
|
||||||
|
width: 100%;
|
||||||
|
padding: $space-2 $space-3;
|
||||||
|
border: none;
|
||||||
|
border-radius: calc(#{$radius} - 2px);
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-align: left;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: $color-background;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.itemOptions {
|
.itemOptions {
|
||||||
margin: 0 0 $space-2;
|
margin: 0 0 $space-2;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Link, useNavigate, useRouter } from '@tanstack/react-router'
|
import { Link, useNavigate, useRouter } from '@tanstack/react-router'
|
||||||
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'
|
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { HoldButton } from '#/components/ui/HoldButton.tsx'
|
import { HoldButton } from '#/components/ui/HoldButton.tsx'
|
||||||
import type { OrderStatus } from '#/db/schema.ts'
|
import type { OrderStatus } from '#/db/schema.ts'
|
||||||
@@ -43,6 +43,9 @@ const getItemBanner = (item: OrderItemStatusView) => {
|
|||||||
animationKey: 'done',
|
animationKey: 'done',
|
||||||
variant: 'pickedUp',
|
variant: 'pickedUp',
|
||||||
label: 'Picked up',
|
label: 'Picked up',
|
||||||
|
detail: item.pickupBox
|
||||||
|
? `Pickup box ${item.pickupBox.number}`
|
||||||
|
: undefined,
|
||||||
} as const
|
} as const
|
||||||
}
|
}
|
||||||
if (item.status === 'cancelled') {
|
if (item.status === 'cancelled') {
|
||||||
@@ -93,6 +96,7 @@ const actionIconPaths = {
|
|||||||
'M12 17h.01',
|
'M12 17h.01',
|
||||||
],
|
],
|
||||||
cancel: ['M18 6 6 18', 'M6 6l12 12'],
|
cancel: ['M18 6 6 18', 'M6 6l12 12'],
|
||||||
|
more: ['M12 12h.01', 'M19 12h.01', 'M5 12h.01'],
|
||||||
}
|
}
|
||||||
|
|
||||||
const ActionIcon = ({ name }: { name: keyof typeof actionIconPaths }) => (
|
const ActionIcon = ({ name }: { name: keyof typeof actionIconPaths }) => (
|
||||||
@@ -113,13 +117,12 @@ const ActionIcon = ({ name }: { name: keyof typeof actionIconPaths }) => (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const LOCK_DURATION_SECONDS = 0.25
|
const LOCK_DURATION_SECONDS = 0.25
|
||||||
|
const CLOSED_BOX_VISIBLE_MS = 5 * 60 * 1000
|
||||||
// Same commands in both so the shackle morphs: unlocked, the left leg
|
// Same commands in both so the shackle morphs: unlocked, the left leg
|
||||||
// stretches up from the body and the right leg shrinks to a stub.
|
// stretches up from the body and the right leg shrinks to a stub.
|
||||||
const LOCKED_SHACKLE = 'M7 11V7a5 5 0 0 1 10 0v4'
|
const LOCKED_SHACKLE = 'M7 11V7a5 5 0 0 1 10 0v4'
|
||||||
const UNLOCKED_SHACKLE = 'M7 11V4a5 5 0 0 1 10 0v1'
|
const UNLOCKED_SHACKLE = 'M7 11V4a5 5 0 0 1 10 0v1'
|
||||||
|
|
||||||
// The shackle lifts while the box is open, and drops back first when the
|
|
||||||
// button leaves after closing, so the lock visibly snaps shut.
|
|
||||||
const LockIcon = ({ open }: { open: boolean }) => {
|
const LockIcon = ({ open }: { open: boolean }) => {
|
||||||
const reduceMotion = useReducedMotion()
|
const reduceMotion = useReducedMotion()
|
||||||
const transition = reduceMotion
|
const transition = reduceMotion
|
||||||
@@ -140,7 +143,6 @@ const LockIcon = ({ open }: { open: boolean }) => {
|
|||||||
<motion.path
|
<motion.path
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{ d: open ? UNLOCKED_SHACKLE : LOCKED_SHACKLE }}
|
animate={{ d: open ? UNLOCKED_SHACKLE : LOCKED_SHACKLE }}
|
||||||
exit={{ d: LOCKED_SHACKLE }}
|
|
||||||
transition={transition}
|
transition={transition}
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
@@ -192,6 +194,24 @@ export const OrderStatusView = ({
|
|||||||
const [initialBannerKeys] = useState(
|
const [initialBannerKeys] = useState(
|
||||||
() => new Set(status.items.map(bannerKey).filter((key) => key !== null)),
|
() => new Set(status.items.map(bannerKey).filter((key) => key !== null)),
|
||||||
)
|
)
|
||||||
|
// A closed box's button stays on screen, disabled and locked, for a while
|
||||||
|
// after pickup; re-render when the next one is due to animate out.
|
||||||
|
const [now, setNow] = useState(() => Date.now())
|
||||||
|
useEffect(() => {
|
||||||
|
const hideAt = status.items
|
||||||
|
.flatMap((item) =>
|
||||||
|
item.pickedUpAt
|
||||||
|
? [item.pickedUpAt.getTime() + CLOSED_BOX_VISIBLE_MS]
|
||||||
|
: [],
|
||||||
|
)
|
||||||
|
.filter((time) => time > now)
|
||||||
|
if (hideAt.length === 0) return
|
||||||
|
const timeout = setTimeout(
|
||||||
|
() => setNow(Date.now()),
|
||||||
|
Math.min(...hideAt) - Date.now(),
|
||||||
|
)
|
||||||
|
return () => clearTimeout(timeout)
|
||||||
|
}, [status.items, now])
|
||||||
const [expandedItemIds, setExpandedItemIds] = useState<ReadonlySet<number>>(
|
const [expandedItemIds, setExpandedItemIds] = useState<ReadonlySet<number>>(
|
||||||
new Set(),
|
new Set(),
|
||||||
)
|
)
|
||||||
@@ -246,11 +266,7 @@ export const OrderStatusView = ({
|
|||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
marginRight: -8,
|
marginRight: -8,
|
||||||
marginBottom: -8,
|
marginBottom: -8,
|
||||||
transition: {
|
transition: { duration: 0.3, ease: 'easeInOut' as const },
|
||||||
delay: LOCK_DURATION_SECONDS + 0.1,
|
|
||||||
duration: 0.3,
|
|
||||||
ease: 'easeInOut' as const,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const togglePickupBox = (item: OrderItemStatusView) => {
|
const togglePickupBox = (item: OrderItemStatusView) => {
|
||||||
@@ -328,10 +344,58 @@ export const OrderStatusView = ({
|
|||||||
<ul className={styles.items}>
|
<ul className={styles.items}>
|
||||||
{status.items.map((item) => {
|
{status.items.map((item) => {
|
||||||
const banner = getItemBanner(item)
|
const banner = getItemBanner(item)
|
||||||
|
const boxClosed = item.status !== 'ready'
|
||||||
|
const pickupBox =
|
||||||
|
item.status === 'ready' ||
|
||||||
|
(item.pickedUpAt !== null &&
|
||||||
|
now - item.pickedUpAt.getTime() < CLOSED_BOX_VISIBLE_MS)
|
||||||
|
? item.pickupBox
|
||||||
|
: null
|
||||||
return (
|
return (
|
||||||
<li key={item.id} className={styles.item}>
|
<li key={item.id} className={styles.item}>
|
||||||
<div className={styles.itemHeader}>
|
<div className={styles.itemHeader}>
|
||||||
<span className={styles.itemName}>{item.menuItemName}</span>
|
<span className={styles.itemName}>{item.menuItemName}</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={styles.menuButton}
|
||||||
|
aria-label={`More actions for ${item.menuItemName}`}
|
||||||
|
aria-haspopup="menu"
|
||||||
|
popoverTarget={`item-menu-${item.id}`}
|
||||||
|
style={{ anchorName: `--item-menu-${item.id}` }}
|
||||||
|
>
|
||||||
|
<ActionIcon name="more" />
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
id={`item-menu-${item.id}`}
|
||||||
|
popover="auto"
|
||||||
|
role="menu"
|
||||||
|
className={styles.menu}
|
||||||
|
style={{ positionAnchor: `--item-menu-${item.id}` }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
role="menuitem"
|
||||||
|
className={styles.menuItem}
|
||||||
|
disabled={pendingItemId === item.id}
|
||||||
|
onClick={() => buyAgain(item)}
|
||||||
|
>
|
||||||
|
<ActionIcon name="buyAgain" />
|
||||||
|
Buy again
|
||||||
|
</button>
|
||||||
|
<Link
|
||||||
|
role="menuitem"
|
||||||
|
className={styles.menuItem}
|
||||||
|
to="/help"
|
||||||
|
search={{
|
||||||
|
orderId: status.id,
|
||||||
|
item: item.menuItemName,
|
||||||
|
status: item.status,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ActionIcon name="help" />
|
||||||
|
Get help
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{item.options.length > 0 && (
|
{item.options.length > 0 && (
|
||||||
<p className={styles.itemOptions}>
|
<p className={styles.itemOptions}>
|
||||||
@@ -396,7 +460,7 @@ export const OrderStatusView = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{isSettled(item) && (
|
{item.status === 'cancelled' && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.stepsToggle}
|
className={styles.stepsToggle}
|
||||||
@@ -414,43 +478,25 @@ export const OrderStatusView = ({
|
|||||||
)}
|
)}
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{item.pickupBox && (
|
{pickupBox && (
|
||||||
<HoldButton
|
<HoldButton
|
||||||
key="pickupBox"
|
key="pickupBox"
|
||||||
className={styles.pickupButton}
|
className={styles.pickupButton}
|
||||||
disabled={pendingItemId === item.id}
|
disabled={pendingItemId === item.id || boxClosed}
|
||||||
onHold={() =>
|
onHold={() =>
|
||||||
void runAction(item.id, () => togglePickupBox(item))
|
void runAction(item.id, () => togglePickupBox(item))
|
||||||
}
|
}
|
||||||
exit={pickupButtonExit}
|
exit={pickupButtonExit}
|
||||||
>
|
>
|
||||||
<LockIcon open={item.pickupBox.isOpen} />
|
<LockIcon open={pickupBox.isOpen} />
|
||||||
{item.pickupBox.isOpen
|
{boxClosed
|
||||||
? `Hold to close box ${item.pickupBox.number}`
|
? `Box ${pickupBox.number} closed`
|
||||||
: `Hold to open box ${item.pickupBox.number}`}
|
: pickupBox.isOpen
|
||||||
|
? `Hold to close box ${pickupBox.number}`
|
||||||
|
: `Hold to open box ${pickupBox.number}`}
|
||||||
</HoldButton>
|
</HoldButton>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
disabled={pendingItemId === item.id}
|
|
||||||
onClick={() => buyAgain(item)}
|
|
||||||
>
|
|
||||||
<ActionIcon name="buyAgain" />
|
|
||||||
Buy again
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
to="/help"
|
|
||||||
search={{
|
|
||||||
orderId: status.id,
|
|
||||||
item: item.menuItemName,
|
|
||||||
status: item.status,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ActionIcon name="help" />
|
|
||||||
Get help
|
|
||||||
</Link>
|
|
||||||
{item.cancellable && (
|
{item.cancellable && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ export const orderItems = pgTable('order_items', {
|
|||||||
cancelledBy: text('cancelled_by').$type<OrderItemCancelledBy>(),
|
cancelledBy: text('cancelled_by').$type<OrderItemCancelledBy>(),
|
||||||
// Set once the item is ready and a Pickup Box is free; kept after pickup.
|
// Set once the item is ready and a Pickup Box is free; kept after pickup.
|
||||||
pickupBoxId: integer('pickup_box_id').references(() => pickupBoxes.id),
|
pickupBoxId: integer('pickup_box_id').references(() => pickupBoxes.id),
|
||||||
|
pickedUpAt: timestamp('picked_up_at'),
|
||||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
+12
-5
@@ -880,7 +880,7 @@ export const closePickupBox = async (
|
|||||||
.where(eq(schema.pickupBoxes.id, box.id))
|
.where(eq(schema.pickupBoxes.id, box.id))
|
||||||
await tx
|
await tx
|
||||||
.update(schema.orderItems)
|
.update(schema.orderItems)
|
||||||
.set({ status: 'picked_up' })
|
.set({ status: 'picked_up', pickedUpAt: new Date() })
|
||||||
.where(eq(schema.orderItems.id, orderItemId))
|
.where(eq(schema.orderItems.id, orderItemId))
|
||||||
|
|
||||||
await updateOrderCompleteIfEverySiblingIsDone(tx, orderItem.orderId)
|
await updateOrderCompleteIfEverySiblingIsDone(tx, orderItem.orderId)
|
||||||
@@ -908,8 +908,10 @@ export type OrderItemStatusView = {
|
|||||||
// Menu Item recreates its Options under new ids, so the ids at order time
|
// Menu Item recreates its Options under new ids, so the ids at order time
|
||||||
// can't be reused. Options since removed from the menu are left out.
|
// can't be reused. Options since removed from the menu are left out.
|
||||||
buyAgainOptionIds: Array<number>
|
buyAgainOptionIds: Array<number>
|
||||||
// Only set while the item is ready and waiting in a box.
|
// The box a ready item waits in, still reported once it's picked up (with
|
||||||
|
// isOpen false, since the box may since have gone to another item).
|
||||||
pickupBox: { number: number; isOpen: boolean } | null
|
pickupBox: { number: number; isOpen: boolean } | null
|
||||||
|
pickedUpAt: Date | null
|
||||||
etaSeconds: number | null
|
etaSeconds: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -950,7 +952,7 @@ export const getOrderStatus = async (
|
|||||||
.where(inArray(schema.orderItemOptions.orderItemId, orderItemIds))
|
.where(inArray(schema.orderItemOptions.orderItemId, orderItemIds))
|
||||||
|
|
||||||
const pickupBoxIds = items
|
const pickupBoxIds = items
|
||||||
.filter((item) => item.status === 'ready')
|
.filter((item) => item.status === 'ready' || item.status === 'picked_up')
|
||||||
.map((item) => item.pickupBoxId)
|
.map((item) => item.pickupBoxId)
|
||||||
.filter((id) => id !== null)
|
.filter((id) => id !== null)
|
||||||
const pickupBoxesById = new Map(
|
const pickupBoxesById = new Map(
|
||||||
@@ -1142,7 +1144,8 @@ export const getOrderStatus = async (
|
|||||||
(option) => option.orderItemId === item.id,
|
(option) => option.orderItemId === item.id,
|
||||||
)
|
)
|
||||||
const pickupBox =
|
const pickupBox =
|
||||||
item.status === 'ready' && item.pickupBoxId !== null
|
(item.status === 'ready' || item.status === 'picked_up') &&
|
||||||
|
item.pickupBoxId !== null
|
||||||
? pickupBoxesById.get(item.pickupBoxId)
|
? pickupBoxesById.get(item.pickupBoxId)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
@@ -1170,8 +1173,12 @@ export const getOrderStatus = async (
|
|||||||
)
|
)
|
||||||
.map((current) => current.id),
|
.map((current) => current.id),
|
||||||
pickupBox: pickupBox
|
pickupBox: pickupBox
|
||||||
? { number: pickupBox.number, isOpen: pickupBox.isOpen }
|
? {
|
||||||
|
number: pickupBox.number,
|
||||||
|
isOpen: item.status === 'ready' && pickupBox.isOpen,
|
||||||
|
}
|
||||||
: null,
|
: null,
|
||||||
|
pickedUpAt: item.pickedUpAt,
|
||||||
etaSeconds,
|
etaSeconds,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -639,7 +639,8 @@ describe('pickup boxes', () => {
|
|||||||
await ordering.closePickupBox(testDb.db, itemId, accessFor(order))
|
await ordering.closePickupBox(testDb.db, itemId, accessFor(order))
|
||||||
const status = await getStatus(order)
|
const status = await getStatus(order)
|
||||||
expect(status.items[0].status).toBe('picked_up')
|
expect(status.items[0].status).toBe('picked_up')
|
||||||
expect(status.items[0].pickupBox).toBeNull()
|
expect(status.items[0].pickupBox).toEqual({ number: 1, isOpen: false })
|
||||||
|
expect(status.items[0].pickedUpAt).toBeInstanceOf(Date)
|
||||||
expect(status.status).toBe('complete')
|
expect(status.status).toBe('complete')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user