Skip to content

Instantly share code, notes, and snippets.

@ximus

ximus/patch Secret

Created February 7, 2023 14:16
Show Gist options
  • Select an option

  • Save ximus/4cd643dcffa202eb95e16d96e1373bfb to your computer and use it in GitHub Desktop.

Select an option

Save ximus/4cd643dcffa202eb95e16d96e1373bfb to your computer and use it in GitHub Desktop.
vite hang
diff --git a/api/app/models/order.rb b/api/app/models/order.rb
index e8e24e9..3393bb0 100644
--- a/api/app/models/order.rb
+++ b/api/app/models/order.rb
@@ -38,9 +38,9 @@ class Order < ApplicationRecord
NEW = :new,
SUBMITTED = :submitted,
PACKED = :packed,
+ DELIVERED = :delivered,
BILLED = :billed,
CANCELLED = :cancelled,
- DELIVERED = :delivered,
].freeze
TO_RPC = {
diff --git a/api/app/rpc/orders_controller.rb b/api/app/rpc/orders_controller.rb
index e2c2148..8c94840 100644
--- a/api/app/rpc/orders_controller.rb
+++ b/api/app/rpc/orders_controller.rb
@@ -49,16 +49,20 @@ class OrdersController < ApplicationController
end
filter :state, as: :choice do
- values = self.values.map { |v| v[:value] }
+ values = self.values.map { |v| v[:value] }.map(&:to_i)
scope.where(name => values)
end
end
def list_orders
- orders = current_org.orders.includes(:client, order_products: :product)
+ orders = current_org.orders.includes(
+ client: [],
+ order_products: :product,
+ current_snapshot: []
+ )
orders = LIST_FILTERS.apply(
scope: orders,
- filters: message.dig(:filters, :values)
+ filters: message.dig(:filters, :entries)
)
orders = orders.order(id: :desc)
orders = orders.limit(message[:page_size] || DEFAULT_PAGE_SIZE)
diff --git a/api/vendor/rpc-definitions/bills_pb.rb b/api/vendor/rpc-definitions/bills_pb.rb
index 46cd9c6..93cee25 100644
--- a/api/vendor/rpc-definitions/bills_pb.rb
+++ b/api/vendor/rpc-definitions/bills_pb.rb
@@ -104,7 +104,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :bill, :message, 1, "gerard.Bill"
end
add_message "gerard.ListBillsRequest" do
- proto3_optional :filters, :message, 1, "gerard.FiltersState"
+ proto3_optional :filters, :message, 1, "gerard.FilterQuery"
proto3_optional :page_token, :string, 2
proto3_optional :page_size, :int32, 3
end
diff --git a/api/vendor/rpc-definitions/bills_pb.rbi b/api/vendor/rpc-definitions/bills_pb.rbi
index 6b18255..70f8133 100644
--- a/api/vendor/rpc-definitions/bills_pb.rbi
+++ b/api/vendor/rpc-definitions/bills_pb.rbi
@@ -1359,7 +1359,7 @@ class Rpc::ListBillsRequest
sig do
params(
- filters: T.nilable(Rpc::FiltersState),
+ filters: T.nilable(Rpc::FilterQuery),
page_token: T.nilable(String),
page_size: T.nilable(Integer)
).void
@@ -1371,11 +1371,11 @@ class Rpc::ListBillsRequest
)
end
- sig { returns(T.nilable(Rpc::FiltersState)) }
+ sig { returns(T.nilable(Rpc::FilterQuery)) }
def filters
end
- sig { params(value: T.nilable(Rpc::FiltersState)).void }
+ sig { params(value: T.nilable(Rpc::FilterQuery)).void }
def filters=(value)
end
diff --git a/api/vendor/rpc-definitions/filtering_pb.rb b/api/vendor/rpc-definitions/filtering_pb.rb
index e802086..34322b4 100644
--- a/api/vendor/rpc-definitions/filtering_pb.rb
+++ b/api/vendor/rpc-definitions/filtering_pb.rb
@@ -16,7 +16,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "gerard.ChoiceValue" do
optional :value, :string, 1
end
- add_message "gerard.FilterState" do
+ add_message "gerard.FilterEntry" do
optional :name, :string, 1
optional :type, :enum, 2, "gerard.FilterType"
oneof :value do
@@ -25,14 +25,23 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :choice_value, :message, 5, "gerard.ChoiceValue"
end
end
- add_message "gerard.FiltersState" do
- repeated :values, :message, 1, "gerard.FilterState"
+ add_message "gerard.FilterQuery" do
+ optional :id, :enum, 1, "gerard.FilterDomain"
+ repeated :entries, :message, 2, "gerard.FilterEntry"
+ end
+ add_message "gerard.FiltersUrlStore" do
+ repeated :queries, :message, 1, "gerard.FilterQuery"
end
add_enum "gerard.FilterType" do
value :ENTITY, 0
value :RANGE, 1
value :CHOICE, 2
end
+ add_enum "gerard.FilterDomain" do
+ value :ORDERS, 0
+ value :PRODUCTS, 1
+ value :BILLS, 2
+ end
end
end
@@ -40,7 +49,9 @@ module Rpc
EntityValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.EntityValue").msgclass
RangeValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.RangeValue").msgclass
ChoiceValue = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.ChoiceValue").msgclass
- FilterState = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.FilterState").msgclass
- FiltersState = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.FiltersState").msgclass
+ FilterEntry = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.FilterEntry").msgclass
+ FilterQuery = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.FilterQuery").msgclass
+ FiltersUrlStore = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.FiltersUrlStore").msgclass
FilterType = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.FilterType").enummodule
+ FilterDomain = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("gerard.FilterDomain").enummodule
end
diff --git a/api/vendor/rpc-definitions/filtering_pb.rbi b/api/vendor/rpc-definitions/filtering_pb.rbi
index 6115c77..39275f0 100644
--- a/api/vendor/rpc-definitions/filtering_pb.rbi
+++ b/api/vendor/rpc-definitions/filtering_pb.rbi
@@ -218,24 +218,24 @@ class Rpc::ChoiceValue
end
end
-class Rpc::FilterState
+class Rpc::FilterEntry
include Google::Protobuf
include Google::Protobuf::MessageExts
extend Google::Protobuf::MessageExts::ClassMethods
- sig { params(str: String).returns(Rpc::FilterState) }
+ sig { params(str: String).returns(Rpc::FilterEntry) }
def self.decode(str)
end
- sig { params(msg: Rpc::FilterState).returns(String) }
+ sig { params(msg: Rpc::FilterEntry).returns(String) }
def self.encode(msg)
end
- sig { params(str: String, kw: T.untyped).returns(Rpc::FilterState) }
+ sig { params(str: String, kw: T.untyped).returns(Rpc::FilterEntry) }
def self.decode_json(str, **kw)
end
- sig { params(msg: Rpc::FilterState, kw: T.untyped).returns(String) }
+ sig { params(msg: Rpc::FilterEntry, kw: T.untyped).returns(String) }
def self.encode_json(msg, **kw)
end
@@ -338,24 +338,24 @@ class Rpc::FilterState
end
end
-class Rpc::FiltersState
+class Rpc::FilterQuery
include Google::Protobuf
include Google::Protobuf::MessageExts
extend Google::Protobuf::MessageExts::ClassMethods
- sig { params(str: String).returns(Rpc::FiltersState) }
+ sig { params(str: String).returns(Rpc::FilterQuery) }
def self.decode(str)
end
- sig { params(msg: Rpc::FiltersState).returns(String) }
+ sig { params(msg: Rpc::FilterQuery).returns(String) }
def self.encode(msg)
end
- sig { params(str: String, kw: T.untyped).returns(Rpc::FiltersState) }
+ sig { params(str: String, kw: T.untyped).returns(Rpc::FilterQuery) }
def self.decode_json(str, **kw)
end
- sig { params(msg: Rpc::FiltersState, kw: T.untyped).returns(String) }
+ sig { params(msg: Rpc::FilterQuery, kw: T.untyped).returns(String) }
def self.encode_json(msg, **kw)
end
@@ -365,24 +365,98 @@ class Rpc::FiltersState
sig do
params(
- values: T.nilable(T::Array[T.nilable(Rpc::FilterState)])
+ id: T.nilable(T.any(Symbol, String, Integer)),
+ entries: T.nilable(T::Array[T.nilable(Rpc::FilterEntry)])
).void
end
def initialize(
- values: []
+ id: :ORDERS,
+ entries: []
)
end
- sig { returns(T::Array[T.nilable(Rpc::FilterState)]) }
- def values
+ sig { returns(Symbol) }
+ def id
+ end
+
+ sig { params(value: T.any(Symbol, String, Integer)).void }
+ def id=(value)
+ end
+
+ sig { void }
+ def clear_id
+ end
+
+ sig { returns(T::Array[T.nilable(Rpc::FilterEntry)]) }
+ def entries
end
sig { params(value: Google::Protobuf::RepeatedField).void }
- def values=(value)
+ def entries=(value)
end
sig { void }
- def clear_values
+ def clear_entries
+ end
+
+ sig { params(field: String).returns(T.untyped) }
+ def [](field)
+ end
+
+ sig { params(field: String, value: T.untyped).void }
+ def []=(field, value)
+ end
+
+ sig { returns(T::Hash[Symbol, T.untyped]) }
+ def to_h
+ end
+end
+
+class Rpc::FiltersUrlStore
+ include Google::Protobuf
+ include Google::Protobuf::MessageExts
+ extend Google::Protobuf::MessageExts::ClassMethods
+
+ sig { params(str: String).returns(Rpc::FiltersUrlStore) }
+ def self.decode(str)
+ end
+
+ sig { params(msg: Rpc::FiltersUrlStore).returns(String) }
+ def self.encode(msg)
+ end
+
+ sig { params(str: String, kw: T.untyped).returns(Rpc::FiltersUrlStore) }
+ def self.decode_json(str, **kw)
+ end
+
+ sig { params(msg: Rpc::FiltersUrlStore, kw: T.untyped).returns(String) }
+ def self.encode_json(msg, **kw)
+ end
+
+ sig { returns(Google::Protobuf::Descriptor) }
+ def self.descriptor
+ end
+
+ sig do
+ params(
+ queries: T.nilable(T::Array[T.nilable(Rpc::FilterQuery)])
+ ).void
+ end
+ def initialize(
+ queries: []
+ )
+ end
+
+ sig { returns(T::Array[T.nilable(Rpc::FilterQuery)]) }
+ def queries
+ end
+
+ sig { params(value: Google::Protobuf::RepeatedField).void }
+ def queries=(value)
+ end
+
+ sig { void }
+ def clear_queries
end
sig { params(field: String).returns(T.untyped) }
@@ -415,3 +489,21 @@ module Rpc::FilterType
def self.descriptor
end
end
+
+module Rpc::FilterDomain
+ self::ORDERS = T.let(0, Integer)
+ self::PRODUCTS = T.let(1, Integer)
+ self::BILLS = T.let(2, Integer)
+
+ sig { params(value: Integer).returns(T.nilable(Symbol)) }
+ def self.lookup(value)
+ end
+
+ sig { params(value: Symbol).returns(T.nilable(Integer)) }
+ def self.resolve(value)
+ end
+
+ sig { returns(::Google::Protobuf::EnumDescriptor) }
+ def self.descriptor
+ end
+end
diff --git a/api/vendor/rpc-definitions/orders_pb.rb b/api/vendor/rpc-definitions/orders_pb.rb
index 0dfc232..acfd9c7 100644
--- a/api/vendor/rpc-definitions/orders_pb.rb
+++ b/api/vendor/rpc-definitions/orders_pb.rb
@@ -61,11 +61,11 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
end
add_enum "gerard.Order.State" do
value :NEW, 0
- value :PACKED, 1
- value :BILLED, 2
- value :CANCELLED, 3
- value :DELIVERED, 4
- value :SUBMITTED, 5
+ value :SUBMITTED, 1
+ value :PACKED, 2
+ value :DELIVERED, 3
+ value :BILLED, 4
+ value :CANCELLED, 5
end
add_enum "gerard.Order.Event" do
value :PACK, 0
@@ -104,7 +104,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :name, :string, 2
end
add_message "gerard.ListOrdersRequest" do
- proto3_optional :filters, :message, 1, "gerard.FiltersState"
+ proto3_optional :filters, :message, 1, "gerard.FilterQuery"
proto3_optional :page_token, :string, 2
proto3_optional :page_size, :int32, 3
end
diff --git a/api/vendor/rpc-definitions/orders_pb.rbi b/api/vendor/rpc-definitions/orders_pb.rbi
index 9b14b0d..887ec48 100644
--- a/api/vendor/rpc-definitions/orders_pb.rbi
+++ b/api/vendor/rpc-definitions/orders_pb.rbi
@@ -631,7 +631,7 @@ class Rpc::ListOrdersRequest
sig do
params(
- filters: T.nilable(Rpc::FiltersState),
+ filters: T.nilable(Rpc::FilterQuery),
page_token: T.nilable(String),
page_size: T.nilable(Integer)
).void
@@ -643,11 +643,11 @@ class Rpc::ListOrdersRequest
)
end
- sig { returns(T.nilable(Rpc::FiltersState)) }
+ sig { returns(T.nilable(Rpc::FilterQuery)) }
def filters
end
- sig { params(value: T.nilable(Rpc::FiltersState)).void }
+ sig { params(value: T.nilable(Rpc::FilterQuery)).void }
def filters=(value)
end
@@ -2649,11 +2649,11 @@ end
module Rpc::Order::State
self::NEW = T.let(0, Integer)
- self::PACKED = T.let(1, Integer)
- self::BILLED = T.let(2, Integer)
- self::CANCELLED = T.let(3, Integer)
- self::DELIVERED = T.let(4, Integer)
- self::SUBMITTED = T.let(5, Integer)
+ self::SUBMITTED = T.let(1, Integer)
+ self::PACKED = T.let(2, Integer)
+ self::DELIVERED = T.let(3, Integer)
+ self::BILLED = T.let(4, Integer)
+ self::CANCELLED = T.let(5, Integer)
sig { params(value: Integer).returns(T.nilable(Symbol)) }
def self.lookup(value)
diff --git a/api/vendor/rpc-definitions/products_pb.rb b/api/vendor/rpc-definitions/products_pb.rb
index 4e2f69d..5b740a8 100644
--- a/api/vendor/rpc-definitions/products_pb.rb
+++ b/api/vendor/rpc-definitions/products_pb.rb
@@ -24,7 +24,7 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
optional :product, :message, 1, "gerard.Product"
end
add_message "gerard.ListProductsRequest" do
- proto3_optional :filters, :message, 1, "gerard.FiltersState"
+ proto3_optional :filters, :message, 1, "gerard.FilterQuery"
end
add_message "gerard.ListProductsResponse" do
repeated :products, :message, 1, "gerard.Product"
diff --git a/api/vendor/rpc-definitions/products_pb.rbi b/api/vendor/rpc-definitions/products_pb.rbi
index 735f441..06ada64 100644
--- a/api/vendor/rpc-definitions/products_pb.rbi
+++ b/api/vendor/rpc-definitions/products_pb.rbi
@@ -297,7 +297,7 @@ class Rpc::ListProductsRequest
sig do
params(
- filters: T.nilable(Rpc::FiltersState)
+ filters: T.nilable(Rpc::FilterQuery)
).void
end
def initialize(
@@ -305,11 +305,11 @@ class Rpc::ListProductsRequest
)
end
- sig { returns(T.nilable(Rpc::FiltersState)) }
+ sig { returns(T.nilable(Rpc::FilterQuery)) }
def filters
end
- sig { params(value: T.nilable(Rpc::FiltersState)).void }
+ sig { params(value: T.nilable(Rpc::FilterQuery)).void }
def filters=(value)
end
diff --git a/proto/src/bills.proto b/proto/src/bills.proto
index 1b69635..057a762 100644
--- a/proto/src/bills.proto
+++ b/proto/src/bills.proto
@@ -116,7 +116,7 @@ message CreateBillRequest {
message CreateBillResponse { Bill bill = 1; }
message ListBillsRequest {
- optional FiltersState filters = 1;
+ optional FilterQuery filters = 1;
optional string page_token = 2;
optional int32 page_size = 3;
}
diff --git a/proto/src/filtering.proto b/proto/src/filtering.proto
index f199b2b..ab67c64 100644
--- a/proto/src/filtering.proto
+++ b/proto/src/filtering.proto
@@ -10,10 +10,16 @@ enum FilterType {
CHOICE = 2;
}
+enum FilterDomain {
+ ORDERS = 0;
+ PRODUCTS = 1;
+ BILLS = 2;
+}
+
message EntityValue {
string id = 1;
- // this is purely presentational and really out to not be there.
- // refactoring not worth it for now. Should fetch the name opportunistically
+ // this is purely presentational and really ought to not be there.
+ // refactoring not worth it for now. Should fetch the name oportunistically
// using id
string name = 2;
}
@@ -23,7 +29,7 @@ message RangeValue {
}
message ChoiceValue { string value = 1; }
-message FilterState {
+message FilterEntry {
string name = 1;
FilterType type = 2;
oneof value {
@@ -33,4 +39,9 @@ message FilterState {
}
}
-message FiltersState { repeated FilterState values = 1; }
+message FilterQuery {
+ FilterDomain id = 1;
+ repeated FilterEntry entries = 2;
+}
+
+message FiltersUrlStore { repeated FilterQuery queries = 1; }
diff --git a/proto/src/orders.proto b/proto/src/orders.proto
index 8e1520f..ab3835f 100644
--- a/proto/src/orders.proto
+++ b/proto/src/orders.proto
@@ -33,11 +33,11 @@ message OrderTotal {
message Order {
enum State {
NEW = 0;
- PACKED = 1;
- BILLED = 2;
- CANCELLED = 3;
- DELIVERED = 4;
- SUBMITTED = 5;
+ SUBMITTED = 1;
+ PACKED = 2;
+ DELIVERED = 3;
+ BILLED = 4;
+ CANCELLED = 5;
}
enum Event {
PACK = 0;
@@ -114,7 +114,7 @@ message ListOrder {
}
message ListOrdersRequest {
- optional FiltersState filters = 1;
+ optional FilterQuery filters = 1;
optional string page_token = 2;
optional int32 page_size = 3;
}
diff --git a/proto/src/products.proto b/proto/src/products.proto
index d2d3325..675d6f4 100644
--- a/proto/src/products.proto
+++ b/proto/src/products.proto
@@ -36,7 +36,7 @@ message Product {
message GetProductRequest { string id = 1; }
message GetProductResponse { Product product = 1; }
-message ListProductsRequest { optional FiltersState filters = 1; }
+message ListProductsRequest { optional FilterQuery filters = 1; }
message ListProductsResponse { repeated Product products = 1; }
message UpdateProductRequest {
diff --git a/web/src/lib/net/grpc/FetchTransport.ts b/web/src/lib/net/grpc/FetchTransport.ts
index fc78526..ccf6e8c 100644
--- a/web/src/lib/net/grpc/FetchTransport.ts
+++ b/web/src/lib/net/grpc/FetchTransport.ts
@@ -38,7 +38,7 @@ function fetchRequest(
init: FetchTransportInit | null,
fetchImpl: typeof fetch | null
): grpc.Transport {
- options.debug = true
+ options.debug = false
options.debug && debug('fetchRequest', options)
return new Fetch(options, init, fetchImpl)
}
diff --git a/web/src/lib/net/grpc/definitions/bills.ts b/web/src/lib/net/grpc/definitions/bills.ts
index f2e1183..9341058 100644
--- a/web/src/lib/net/grpc/definitions/bills.ts
+++ b/web/src/lib/net/grpc/definitions/bills.ts
@@ -6,7 +6,7 @@ import { Timestamp } from "./google/protobuf/timestamp";
import { Money } from "./shared/money";
import { Address } from "./shared/address";
import { Quantity } from "./shared/quantity";
-import { FiltersState } from "./filtering";
+import { FilterQuery } from "./filtering";
export const protobufPackage = "gerard";
@@ -118,7 +118,7 @@ export interface CreateBillResponse {
}
export interface ListBillsRequest {
- filters?: FiltersState | undefined;
+ filters?: FilterQuery | undefined;
pageToken?: string | undefined;
pageSize?: number | undefined;
}
@@ -1159,7 +1159,7 @@ export const ListBillsRequest = {
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
if (message.filters !== undefined) {
- FiltersState.encode(message.filters, writer.uint32(10).fork()).ldelim();
+ FilterQuery.encode(message.filters, writer.uint32(10).fork()).ldelim();
}
if (message.pageToken !== undefined) {
writer.uint32(18).string(message.pageToken);
@@ -1178,7 +1178,7 @@ export const ListBillsRequest = {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
- message.filters = FiltersState.decode(reader, reader.uint32());
+ message.filters = FilterQuery.decode(reader, reader.uint32());
break;
case 2:
message.pageToken = reader.string();
@@ -1198,7 +1198,7 @@ export const ListBillsRequest = {
const message = createBaseListBillsRequest();
message.filters =
object.filters !== undefined && object.filters !== null
- ? FiltersState.fromPartial(object.filters)
+ ? FilterQuery.fromPartial(object.filters)
: undefined;
message.pageToken = object.pageToken ?? undefined;
message.pageSize = object.pageSize ?? undefined;
diff --git a/web/src/lib/net/grpc/definitions/filtering.ts b/web/src/lib/net/grpc/definitions/filtering.ts
index 0681954..4d607f7 100644
--- a/web/src/lib/net/grpc/definitions/filtering.ts
+++ b/web/src/lib/net/grpc/definitions/filtering.ts
@@ -10,11 +10,17 @@ export enum FilterType {
CHOICE = 2,
}
+export enum FilterDomain {
+ ORDERS = 0,
+ PRODUCTS = 1,
+ BILLS = 2,
+}
+
export interface EntityValue {
id: string;
/**
- * this is purely presentational and really out to not be there.
- * refactoring not worth it for now. Should fetch the name opportunistically
+ * this is purely presentational and really ought to not be there.
+ * refactoring not worth it for now. Should fetch the name oportunistically
* using id
*/
name: string;
@@ -29,7 +35,7 @@ export interface ChoiceValue {
value: string;
}
-export interface FilterState {
+export interface FilterEntry {
name: string;
type: FilterType;
value?:
@@ -38,8 +44,13 @@ export interface FilterState {
| { $case: "choiceValue"; choiceValue: ChoiceValue };
}
-export interface FiltersState {
- values: FilterState[];
+export interface FilterQuery {
+ id: FilterDomain;
+ entries: FilterEntry[];
+}
+
+export interface FiltersUrlStore {
+ queries: FilterQuery[];
}
function createBaseEntityValue(): EntityValue {
@@ -176,13 +187,13 @@ export const ChoiceValue = {
},
};
-function createBaseFilterState(): FilterState {
+function createBaseFilterEntry(): FilterEntry {
return { name: "", type: 0, value: undefined };
}
-export const FilterState = {
+export const FilterEntry = {
encode(
- message: FilterState,
+ message: FilterEntry,
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
if (message.name !== "") {
@@ -212,10 +223,10 @@ export const FilterState = {
return writer;
},
- decode(input: _m0.Reader | Uint8Array, length?: number): FilterState {
+ decode(input: _m0.Reader | Uint8Array, length?: number): FilterEntry {
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseFilterState();
+ const message = createBaseFilterEntry();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
@@ -251,8 +262,8 @@ export const FilterState = {
return message;
},
- fromPartial(object: DeepPartial<FilterState>): FilterState {
- const message = createBaseFilterState();
+ fromPartial(object: DeepPartial<FilterEntry>): FilterEntry {
+ const message = createBaseFilterEntry();
message.name = object.name ?? "";
message.type = object.type ?? 0;
if (
@@ -289,30 +300,78 @@ export const FilterState = {
},
};
-function createBaseFiltersState(): FiltersState {
- return { values: [] };
+function createBaseFilterQuery(): FilterQuery {
+ return { id: 0, entries: [] };
+}
+
+export const FilterQuery = {
+ encode(
+ message: FilterQuery,
+ writer: _m0.Writer = _m0.Writer.create()
+ ): _m0.Writer {
+ if (message.id !== 0) {
+ writer.uint32(8).int32(message.id);
+ }
+ for (const v of message.entries) {
+ FilterEntry.encode(v!, writer.uint32(18).fork()).ldelim();
+ }
+ return writer;
+ },
+
+ decode(input: _m0.Reader | Uint8Array, length?: number): FilterQuery {
+ const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
+ let end = length === undefined ? reader.len : reader.pos + length;
+ const message = createBaseFilterQuery();
+ while (reader.pos < end) {
+ const tag = reader.uint32();
+ switch (tag >>> 3) {
+ case 1:
+ message.id = reader.int32() as any;
+ break;
+ case 2:
+ message.entries.push(FilterEntry.decode(reader, reader.uint32()));
+ break;
+ default:
+ reader.skipType(tag & 7);
+ break;
+ }
+ }
+ return message;
+ },
+
+ fromPartial(object: DeepPartial<FilterQuery>): FilterQuery {
+ const message = createBaseFilterQuery();
+ message.id = object.id ?? 0;
+ message.entries =
+ object.entries?.map((e) => FilterEntry.fromPartial(e)) || [];
+ return message;
+ },
+};
+
+function createBaseFiltersUrlStore(): FiltersUrlStore {
+ return { queries: [] };
}
-export const FiltersState = {
+export const FiltersUrlStore = {
encode(
- message: FiltersState,
+ message: FiltersUrlStore,
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
- for (const v of message.values) {
- FilterState.encode(v!, writer.uint32(10).fork()).ldelim();
+ for (const v of message.queries) {
+ FilterQuery.encode(v!, writer.uint32(10).fork()).ldelim();
}
return writer;
},
- decode(input: _m0.Reader | Uint8Array, length?: number): FiltersState {
+ decode(input: _m0.Reader | Uint8Array, length?: number): FiltersUrlStore {
const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input);
let end = length === undefined ? reader.len : reader.pos + length;
- const message = createBaseFiltersState();
+ const message = createBaseFiltersUrlStore();
while (reader.pos < end) {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
- message.values.push(FilterState.decode(reader, reader.uint32()));
+ message.queries.push(FilterQuery.decode(reader, reader.uint32()));
break;
default:
reader.skipType(tag & 7);
@@ -322,10 +381,10 @@ export const FiltersState = {
return message;
},
- fromPartial(object: DeepPartial<FiltersState>): FiltersState {
- const message = createBaseFiltersState();
- message.values =
- object.values?.map((e) => FilterState.fromPartial(e)) || [];
+ fromPartial(object: DeepPartial<FiltersUrlStore>): FiltersUrlStore {
+ const message = createBaseFiltersUrlStore();
+ message.queries =
+ object.queries?.map((e) => FilterQuery.fromPartial(e)) || [];
return message;
},
};
diff --git a/web/src/lib/net/grpc/definitions/orders.ts b/web/src/lib/net/grpc/definitions/orders.ts
index 21ad007..8bc6fa5 100644
--- a/web/src/lib/net/grpc/definitions/orders.ts
+++ b/web/src/lib/net/grpc/definitions/orders.ts
@@ -6,7 +6,7 @@ import { Timestamp } from "./google/protobuf/timestamp";
import { Money } from "./shared/money";
import { Address } from "./shared/address";
import { Quantity } from "./shared/quantity";
-import { FiltersState } from "./filtering";
+import { FilterQuery } from "./filtering";
export const protobufPackage = "gerard";
@@ -39,11 +39,11 @@ export interface Order {
export enum Order_State {
NEW = 0,
- PACKED = 1,
- BILLED = 2,
- CANCELLED = 3,
- DELIVERED = 4,
- SUBMITTED = 5,
+ SUBMITTED = 1,
+ PACKED = 2,
+ DELIVERED = 3,
+ BILLED = 4,
+ CANCELLED = 5,
}
export enum Order_Event {
@@ -115,7 +115,7 @@ export interface ListOrder_Client {
}
export interface ListOrdersRequest {
- filters?: FiltersState | undefined;
+ filters?: FilterQuery | undefined;
pageToken?: string | undefined;
pageSize?: number | undefined;
}
@@ -1023,7 +1023,7 @@ export const ListOrdersRequest = {
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
if (message.filters !== undefined) {
- FiltersState.encode(message.filters, writer.uint32(10).fork()).ldelim();
+ FilterQuery.encode(message.filters, writer.uint32(10).fork()).ldelim();
}
if (message.pageToken !== undefined) {
writer.uint32(18).string(message.pageToken);
@@ -1042,7 +1042,7 @@ export const ListOrdersRequest = {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
- message.filters = FiltersState.decode(reader, reader.uint32());
+ message.filters = FilterQuery.decode(reader, reader.uint32());
break;
case 2:
message.pageToken = reader.string();
@@ -1062,7 +1062,7 @@ export const ListOrdersRequest = {
const message = createBaseListOrdersRequest();
message.filters =
object.filters !== undefined && object.filters !== null
- ? FiltersState.fromPartial(object.filters)
+ ? FilterQuery.fromPartial(object.filters)
: undefined;
message.pageToken = object.pageToken ?? undefined;
message.pageSize = object.pageSize ?? undefined;
diff --git a/web/src/lib/net/grpc/definitions/products.ts b/web/src/lib/net/grpc/definitions/products.ts
index 2fb0c22..630020c 100644
--- a/web/src/lib/net/grpc/definitions/products.ts
+++ b/web/src/lib/net/grpc/definitions/products.ts
@@ -3,7 +3,7 @@ import Long from "long";
import _m0 from "protobufjs/minimal.js";
import type { CallContext, CallOptions } from "nice-grpc-common";
import { Money } from "./shared/money";
-import { FiltersState } from "./filtering";
+import { FilterQuery } from "./filtering";
export const protobufPackage = "gerard";
@@ -36,7 +36,7 @@ export interface GetProductResponse {
}
export interface ListProductsRequest {
- filters?: FiltersState | undefined;
+ filters?: FilterQuery | undefined;
}
export interface ListProductsResponse {
@@ -270,7 +270,7 @@ export const ListProductsRequest = {
writer: _m0.Writer = _m0.Writer.create()
): _m0.Writer {
if (message.filters !== undefined) {
- FiltersState.encode(message.filters, writer.uint32(10).fork()).ldelim();
+ FilterQuery.encode(message.filters, writer.uint32(10).fork()).ldelim();
}
return writer;
},
@@ -283,7 +283,7 @@ export const ListProductsRequest = {
const tag = reader.uint32();
switch (tag >>> 3) {
case 1:
- message.filters = FiltersState.decode(reader, reader.uint32());
+ message.filters = FilterQuery.decode(reader, reader.uint32());
break;
default:
reader.skipType(tag & 7);
@@ -297,7 +297,7 @@ export const ListProductsRequest = {
const message = createBaseListProductsRequest();
message.filters =
object.filters !== undefined && object.filters !== null
- ? FiltersState.fromPartial(object.filters)
+ ? FilterQuery.fromPartial(object.filters)
: undefined;
return message;
},
diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts
index 9ca949e..d41566d 100644
--- a/web/src/lib/types.ts
+++ b/web/src/lib/types.ts
@@ -119,6 +119,7 @@ export enum Color {
GREEN = 'green',
RED = 'red',
YELLOW = 'yellow',
+ GRAY = 'gray',
}
// sync these with role.rb
diff --git a/web/src/lib/ui-app/orders/order_state_pill.svelte b/web/src/lib/ui-app/orders/order_state_pill.svelte
index 4e02c0b..d5eb39d 100644
--- a/web/src/lib/ui-app/orders/order_state_pill.svelte
+++ b/web/src/lib/ui-app/orders/order_state_pill.svelte
@@ -1,5 +1,16 @@
<svelte:options immutable={true} />
+<script lang="ts" context="module">
+ export const colors = {
+ [OrderState.NEW]: Color.GRAY,
+ [OrderState.SUBMITTED]: Color.BLUE,
+ [OrderState.PACKED]: Color.BLUE,
+ [OrderState.DELIVERED]: Color.GREEN,
+ [OrderState.BILLED]: Color.GREEN,
+ [OrderState.CANCELLED]: Color.RED,
+ }
+</script>
+
<script lang="ts">
import { t } from '#lib/translations'
import { Pill } from '#lib/ui'
@@ -10,18 +21,13 @@
export let state: OrderState
export let size: 'sm' | 'md' = 'md'
- const colors = {
- [OrderState.NEW]: Color.YELLOW,
- [OrderState.SUBMITTED]: Color.BLUE,
- [OrderState.PACKED]: Color.VIOLET,
- [OrderState.DELIVERED]: Color.GREEN,
- [OrderState.BILLED]: Color.GREEN,
- [OrderState.CANCELLED]: Color.RED,
- }
-
$: color = colors[state]
</script>
<Pill {color} {size} class={classes}>
- {$t(`common.order.state.${state}`)}
+ {#if $$slots.default}
+ <slot />
+ {:else}
+ {$t(`common.order.state.${state}`)}
+ {/if}
</Pill>
diff --git a/web/src/lib/ui/filtering/filter_controls.svelte b/web/src/lib/ui/filtering/filter_controls.svelte
index 20c9418..854d59a 100644
--- a/web/src/lib/ui/filtering/filter_controls.svelte
+++ b/web/src/lib/ui/filtering/filter_controls.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
- import { add, loadFilters } from './lib'
+ import { getFilterQuery, loadFilters } from './lib'
import Button from '../button.svelte'
- import type { FiltersConfig, FiltersState, FilterState } from './types'
+ import type { FiltersConfig, FilterEntry, FilterDomain } from './types'
import FilterTagList from './filter_tag_list.svelte'
import { Plus } from '../icons'
@@ -12,32 +12,42 @@
export { filterInits as filters }
$: filters = loadFilters(filterInits)
- export let state: FiltersState
- export let onAdd: (entry: FilterState) => void = undefined
+ export let id: FilterDomain
+ export let onAdd: (entries: FilterEntry[]) => void = undefined
+
+ $: query = getFilterQuery(id)
let editComponent: SvelteComponent
- let currentlyAdding: typeof filters[0]
+ let currentlyAdding: (typeof filters)[0]
const onSubmit = () => {
- const value = editComponent.getValue()
- if (value) {
- const entry = editComponent.getState()
- addEntry(entry)
+ const entries = [editComponent.getEntries()].flat().filter(Boolean)
+ if (entries.length) {
+ entries.forEach(query.add)
currentlyAdding = null
- onAdd && onAdd(entry)
+ onAdd && onAdd(entries)
}
}
- const addEntry = (entry: FilterState) => (state = add(state, entry))
+ const addEntry = (entry: FilterEntry) => query.add(entry)
+ // const getExistingValue = ({ type, name }) => {
+ // const entry = $query.entries.find(
+ // (v) => v.type === type && v.name === name
+ // )?.value
+ // if (entry) {
+ // return entry[entry.$case]
+ // }
+ // return null
+ // }
</script>
<div class="light flex flex-col px-5 py-4 gap-1 {classes}">
- {#if state.values.length}
+ {#if $query.entries.length}
<div
class="text-sm text-gray-800 font-medium whitespace-nowrap self-start mb-1"
>
Filtres en cours
</div>
- <FilterTagList filters={filterInits} bind:state />
+ <FilterTagList {id} filters={filterInits} />
<div class="self-center w-full h-px bg-gray-200 mt-4 mb-3 rounded-full" />
{/if}
<div class="flex flex-col">
diff --git a/web/src/lib/ui/filtering/filter_tag.svelte b/web/src/lib/ui/filtering/filter_tag.svelte
index f771232..50c4328 100644
--- a/web/src/lib/ui/filtering/filter_tag.svelte
+++ b/web/src/lib/ui/filtering/filter_tag.svelte
@@ -1,47 +1,53 @@
<script lang="ts">
+ import { Color } from '#lib/types'
import Icon from '../icon.svelte'
import { XMark } from '../icons'
- import type { Filter } from './types'
+ import Pill from '../pill.svelte'
+ import type { Filter, FilterEntry } from './types'
export let value: string
export let filter: Filter<unknown>
-
+ export let entry: FilterEntry
export let onRemove: () => void
+
+ $: color = filter.color
+ ? filter.color(entry.value[entry.value.$case])
+ : Color.BLUE
+
let classes = ''
export { classes as class }
$: ({ icon, label } = filter)
</script>
-<div
+<Pill
+ size="sm"
+ {color}
class="
flex flex-row items-center
- text-xs
- text-blue-600
- rounded-full
- bg-blue-200
- border border-blue-300
- pl-3
+ px-3
shadow-sm
{classes}
"
+ noDot
+ let:textColor
>
- <div class="flex flex-row items-center text-blue-600 py-1.5">
+ <div class="flex flex-row items-center {textColor} ">
{#if icon}
- <Icon src={icon} class="h-4 text-blue-500" />
+ <Icon src={icon} class="h-4 {textColor}" />
{:else}
{label}
{/if}
</div>
- <div class="w-px h-[1rem] bg-blue-300 mx-2" />
- <div class="font-medium py-1.5 whitespace-nowrap">
+ <div class="w-px h-[1rem] bg-gray-400 opacity-50 mx-2" />
+ <div class="font-medium whitespace-nowrap">
{value}
</div>
<button
title="Supprimer"
on:click={onRemove}
- class="flex flex-row items-center pl-2 pr-3 py-1.5 hover:text-black"
+ class="flex flex-row items-center pl-2 hover:text-black"
>
<Icon class="h-3" src={XMark} />
</button>
-</div>
+</Pill>
diff --git a/web/src/lib/ui/filtering/filter_tag_list.svelte b/web/src/lib/ui/filtering/filter_tag_list.svelte
index b318afc..ea728c2 100644
--- a/web/src/lib/ui/filtering/filter_tag_list.svelte
+++ b/web/src/lib/ui/filtering/filter_tag_list.svelte
@@ -1,11 +1,11 @@
<script lang="ts">
import { fade } from 'svelte/transition'
- import { loadFilters, remove } from './lib'
+ import { getFilterQuery, loadFilters } from './lib'
import type {
Filter,
FiltersConfig,
- FiltersState,
- FilterState,
+ FilterEntry,
+ FilterDomain,
} from './types'
let classes = ''
@@ -15,26 +15,30 @@
export { filterInits as filters }
$: filters = loadFilters(filterInits)
- export let state: FiltersState
+ export let id: FilterDomain
- let stateAndFilter: [FilterState, Filter<unknown>][]
- $: stateAndFilter = state.values.map((s) => {
+ $: query = getFilterQuery(id)
+
+ let stateAndFilter: [FilterEntry, Filter<unknown>][]
+ $: stateAndFilter = $query.entries.map((s) => {
const filter = filters.find((f) => f.type === s.type && f.name === s.name)
return [s, filter]
})
- const onRemove = (entry: FilterState) => {
- state = remove(state, entry)
+ const onRemove = (entry: FilterEntry) => {
+ query.remove(entry)
}
</script>
<div class="flex flex-row flex-wrap gap-2 {classes}">
{#each stateAndFilter as [entry, filter]}
- <div in:fade>
+ <div class="contents" in:fade>
<svelte:component
this={filter.definition.tagComponent}
value={entry.value[entry.value.$case]}
{filter}
+ {entry}
+ {id}
onRemove={() => onRemove(entry)}
/>
</div>
diff --git a/web/src/lib/ui/filtering/filters/choice/edit.svelte b/web/src/lib/ui/filtering/filters/choice/edit.svelte
index fbbb76b..1d559a7 100644
--- a/web/src/lib/ui/filtering/filters/choice/edit.svelte
+++ b/web/src/lib/ui/filtering/filters/choice/edit.svelte
@@ -1,41 +1,58 @@
<script lang="ts">
- import type { FilterState } from '../../types'
import { onMount } from 'svelte'
import { fade } from 'svelte/transition'
- import type { Value } from '.'
import type ChoiceFilter from '.'
- import { Select } from '#lib/ui'
+ import { Checkbox, Form, Select } from '#lib/ui'
+ import Pill from '#lib/ui/pill.svelte'
+ import { Color } from '#lib/types'
+ import type { FilterEntry } from '../../types'
export let filter: ChoiceFilter
- export let value: Value = null
+ let values: string[]
- export function getValue() {
- return value
- }
-
- export function getState(): FilterState {
- return {
+ export function getEntries(): FilterEntry[] {
+ return values.map((v) => ({
type: filter.type,
name: filter.name,
value: {
$case: 'choiceValue',
- choiceValue: value,
+ choiceValue: { value: v },
},
- }
+ }))
}
- const onChange = (v) => {
- value = { value: v }
- }
+ const onSingleChange = (v) => (values = [v])
+ const onMultipleChange = (v) => (values = v.value)
onMount(() => self.focus())
</script>
<div in:fade>
- <Select
- icon={filter.icon}
- label={filter.label}
- options={filter.options}
- {onChange}
- />
+ {#if filter.multiple}
+ <div class="gr-form-label mb-2">
+ {filter.label}
+ </div>
+ <Form onChange={onMultipleChange} class="flex flex-col gap-2 items-start">
+ {#each filter.options as { label, value }}
+ <Pill
+ noDot
+ color={filter.color ? filter.color({ value }) : Color.BLUE}
+ class="cursor-pointer flex flex-row gap-2.5"
+ on:click={(e) => {
+ e.target.querySelector('input')?.click()
+ }}
+ >
+ <Checkbox name="multiple" {value} />
+ {label}
+ </Pill>
+ {/each}
+ </Form>
+ {:else}
+ <Select
+ icon={filter.icon}
+ label={filter.label}
+ options={filter.options}
+ onChange={onSingleChange}
+ />
+ {/if}
</div>
diff --git a/web/src/lib/ui/filtering/filters/choice/index.ts b/web/src/lib/ui/filtering/filters/choice/index.ts
index 23f55d8..ff0eac8 100644
--- a/web/src/lib/ui/filtering/filters/choice/index.ts
+++ b/web/src/lib/ui/filtering/filters/choice/index.ts
@@ -8,6 +8,7 @@ export { Value }
type ChoiceFilterInit = FilterInit<typeof Filter<Value>> & {
options: { value: string; label: string }[]
+ multiple?: boolean
}
@declareFilter
@@ -17,9 +18,11 @@ export default class ChoiceFilter extends Filter<Value> {
static tagComponent = Tag
options: ChoiceFilterInit['options']
+ multiple: ChoiceFilterInit['multiple']
- constructor({ options, ...rest }: ChoiceFilterInit) {
+ constructor({ options, multiple, ...rest }: ChoiceFilterInit) {
super(rest)
this.options = options
+ this.multiple = multiple
}
}
diff --git a/web/src/lib/ui/filtering/filters/choice/tag.svelte b/web/src/lib/ui/filtering/filters/choice/tag.svelte
index 4a68ec0..ef6b1cf 100644
--- a/web/src/lib/ui/filtering/filters/choice/tag.svelte
+++ b/web/src/lib/ui/filtering/filters/choice/tag.svelte
@@ -5,10 +5,12 @@
export let value: Value
export let filter: ChoiceFilter
-
- $: label = filter.options.find(
- (o) => o.value.toString() === value.value.toString()
- ).label
</script>
-<FilterTag value={label} {filter} {...$$restProps} />
+<FilterTag
+ value={filter.options.find(
+ (o) => o.value.toString() === value.value.toString()
+ ).label}
+ {filter}
+ {...$$restProps}
+/>
diff --git a/web/src/lib/ui/filtering/filters/entity/edit.svelte b/web/src/lib/ui/filtering/filters/entity/edit.svelte
index 7d053ef..9ebf19c 100644
--- a/web/src/lib/ui/filtering/filters/entity/edit.svelte
+++ b/web/src/lib/ui/filtering/filters/entity/edit.svelte
@@ -1,20 +1,18 @@
<script lang="ts">
- import type { FilterState } from '../../types'
import { pick } from '#lib/util'
import { onMount } from 'svelte'
import { fade } from 'svelte/transition'
import type { Value } from '.'
import type EntityFilter from '.'
import Suggestions from '../../../suggestions.svelte'
+ import type { FilterEntry } from '../../types'
export let filter: EntityFilter
- export let value: Value = null
- export function getValue() {
- return value
- }
+ let value: Value
- export function getState(): FilterState {
+ export function getEntries(): FilterEntry {
+ if (!value) return null
return {
type: filter.type,
name: filter.name,
diff --git a/web/src/lib/ui/filtering/index.ts b/web/src/lib/ui/filtering/index.ts
index 7a3e1c5..1b2792d 100644
--- a/web/src/lib/ui/filtering/index.ts
+++ b/web/src/lib/ui/filtering/index.ts
@@ -2,3 +2,6 @@ import './filters/entity'
import './filters/choice'
export { default as FilterControls } from './filter_controls.svelte'
+
+export * from './lib'
+export * from './types'
diff --git a/web/src/lib/ui/filtering/lib.ts b/web/src/lib/ui/filtering/lib.ts
index 6cc8b65..1a3330b 100644
--- a/web/src/lib/ui/filtering/lib.ts
+++ b/web/src/lib/ui/filtering/lib.ts
@@ -1,12 +1,17 @@
+import { FiltersUrlStore } from '#lib/net/grpc/definitions/filtering'
import { isEqualLite } from '#lib/util'
import { goto } from '$app/navigation'
-import {
+import type { Page } from '@sveltejs/kit'
+import { getContext, setContext } from 'svelte'
+import { derived, type Readable } from 'svelte/store'
+import type {
Filter,
- type FiltersConfig,
- FiltersState,
- FilterState,
+ FiltersConfig,
+ FilterDomain,
+ FilterQuery,
+ FilterEntry,
FilterType,
- type FilterValue,
+ FilterValue,
} from './types'
export function loadFilters(filters: FiltersConfig): Filter<FilterValue>[] {
@@ -34,30 +39,13 @@ export function filterTypeToClass(type: FilterType): typeof Filter {
return _filterTypeToClass[type]
}
-export function add(state: FiltersState, entry: FilterState): FiltersState {
- const newState = { ...state, values: [...state.values, entry] }
- pushStateToUrl(newState)
- return newState
-}
-
-export function remove(state: FiltersState, entry: FilterState): FiltersState {
- const newState = {
- ...state,
- values: state.values.filter(
- (e) => !isEqualLite(e, entry)
- // (e) => !(e.type === entry.type && e.name === entry.name)
- ),
- }
- pushStateToUrl(newState)
- return newState
-}
-
const PARAM = 'q'
-function pushStateToUrl(state: FiltersState) {
+function pushToUrl(state: State) {
const url = new URL(window.location.href)
- if (state.values.length) {
- const bytes = FiltersState.encode(state).finish()
+ const queries = Object.values(state.queries)
+ if (queries.length && queries.find((q) => q.entries.length)) {
+ const bytes = FiltersUrlStore.encode({ queries }).finish()
const string = new TextDecoder().decode(bytes)
url.searchParams.set(PARAM, btoa(string))
} else {
@@ -70,21 +58,134 @@ function pushStateToUrl(state: FiltersState) {
})
}
-export function newFiltersState(): FiltersState {
- return { values: [] }
+export function newFilterQuery(id: FilterDomain): FilterQuery {
+ return { id, entries: [] }
}
-export function loadStateFromUrl(url: URL): FiltersState {
+export function newState(): State {
+ return { queries: {} }
+}
+
+const urlStoreToState = (store: FiltersUrlStore) =>
+ store.queries.reduce((state, query) => {
+ state.queries[query.id] = query
+ return state
+ }, newState())
+
+function loadFromUrl(url: URL): State {
const param = url.searchParams.get(PARAM)
- if (param) {
- try {
- const value = atob(param)
- const bytes = new TextEncoder().encode(value)
- const state = FiltersState.decode(bytes)
- return state
- } catch (e) {
- console.warn('failed to parse filters', e)
+ if (!param) return newState()
+ try {
+ const value = atob(param)
+ const bytes = new TextEncoder().encode(value)
+ const store = FiltersUrlStore.decode(bytes)
+ return urlStoreToState(store)
+ } catch (e) {
+ console.warn(e)
+ }
+ return newState()
+}
+
+export function loadFilterQueryFromUrl(
+ url: URL,
+ id: FilterDomain
+): FilterQuery {
+ const state = loadFromUrl(url)
+ return state.queries[id]
+}
+
+type State = { queries: { [id in FilterDomain]?: FilterQuery } }
+
+function stateStore(page: Readable<Page>) {
+ let state: State
+
+ const { subscribe } = derived([page], ([$page]) => {
+ state = loadFromUrl($page.url)
+ return state
+ })
+
+ function upsert(query: FilterQuery) {
+ state.queries[query.id] = query
+ pushToUrl(state)
+ }
+
+ return {
+ upsert,
+ subscribe,
+ }
+}
+
+function filterQueryStore(state: StateStore, id: FilterDomain) {
+ let query: FilterQuery
+
+ const { subscribe } = derived([state], ([$state]) => {
+ query = $state.queries[id] || newFilterQuery(id)
+ return query
+ })
+
+ function pushToState() {
+ state.upsert(query)
+ }
+
+ function add(entry: FilterEntry): FilterQuery {
+ query = { ...query, entries: [...query.entries, entry] }
+ pushToState()
+ return query
+ }
+
+ function upsert(entry: FilterEntry): FilterQuery {
+ console.log('upsert1', entry)
+ console.log('query.entries', query.entries)
+ let index = query.entries.findIndex(
+ (v) => v.type === entry.type && v.name === entry.name
+ )
+ console.log('index', index)
+ if (index === -1) index = query.entries.length
+ const entries = [...query.entries]
+ entries[index] = entry
+ query = { ...query, entries }
+ console.log('query out', query)
+ pushToState()
+ return query
+ }
+
+ function remove(entry: FilterEntry): FilterQuery {
+ query = {
+ ...query,
+ entries: query.entries.filter(
+ (e) => !isEqualLite(e, entry)
+ // (e) => !(e.type === entry.type && e.name === entry.name)
+ ),
}
+ pushToState()
+ return query
+ }
+
+ return {
+ remove,
+ add,
+ upsert,
+ subscribe,
+ }
+}
+
+export type FilterQueryStore = ReturnType<typeof filterQueryStore>
+export type StateStore = ReturnType<typeof stateStore>
+
+const key = {}
+export function getState(): StateStore {
+ let states = getContext(key) as StateStore
+ if (!states) {
+ // need to initFiltering first
+ throw new Error()
}
- return newFiltersState()
+ return states
+}
+export function getFilterQuery(id: FilterDomain) {
+ const state = getState()
+ return filterQueryStore(state, id)
+}
+export function initFiltering(page: Readable<Page>) {
+ const store = stateStore(page)
+ setContext(key, store)
}
diff --git a/web/src/lib/ui/filtering/types.ts b/web/src/lib/ui/filtering/types.ts
index 5d873bc..759da3c 100644
--- a/web/src/lib/ui/filtering/types.ts
+++ b/web/src/lib/ui/filtering/types.ts
@@ -1,8 +1,10 @@
import {
- FiltersState,
- FilterState,
+ FilterQuery,
+ FilterEntry,
FilterType,
+ FilterDomain,
} from '#lib/net/grpc/definitions/filtering'
+import type { Color } from '#lib/types'
import type { SvelteComponent } from 'svelte'
import type { IconSource } from '../icons/types'
import type ChoiceFilter from './filters/choice'
@@ -10,12 +12,17 @@ import type EntityFilter from './filters/entity'
import FilterTag from './filter_tag.svelte'
export { FilterType }
+export { FilterEntry }
+export { FilterQuery }
+export { FilterDomain }
+export type FilterValue = any
export type FilterInitBase<Value> = {
name: string
label: string
icon?: IconSource
value?: Value
+ color?: (value: Value) => Color
}
export class Filter<Value extends FilterValue> {
@@ -31,11 +38,13 @@ export class Filter<Value extends FilterValue> {
// return filter class
definition = this.constructor as typeof Filter<Value>
type = this.definition.type
+ color: FilterInitBase<unknown>['color']
- constructor({ name, label, icon }: FilterInitBase<Value>) {
+ constructor({ name, label, icon, color }: FilterInitBase<Value>) {
this.name = name
this.label = label
this.icon = icon
+ this.color = color
}
}
@@ -48,8 +57,3 @@ export type FiltersConfig = ((
// the signle object param passed to given filter's constructor
export type FilterInit<F extends typeof Filter<FilterValue>> =
ConstructorParameters<F>[0]
-
-// export type FilterState = { name: string; type: string; value: FilterValue }
-export { FilterState }
-export { FiltersState }
-export type FilterValue = any
diff --git a/web/src/lib/ui/forms/checkbox.svelte b/web/src/lib/ui/forms/checkbox.svelte
index 6752462..330f28a 100644
--- a/web/src/lib/ui/forms/checkbox.svelte
+++ b/web/src/lib/ui/forms/checkbox.svelte
@@ -1,5 +1,12 @@
<svelte:options immutable={true} />
+<script lang="ts" context="module">
+ export type OnCheckboxChange = (v: {
+ checked: boolean
+ value: string
+ }) => void
+</script>
+
<script lang="ts">
import BaseCheckOrRadio from './base_check_or_radio.svelte'
import { isBlank, randomDomId, sleep } from '#lib/util'
@@ -18,7 +25,7 @@
export let subtext = null
export let showBody = false
export let showBodyBar = false
- export let onChange = null
+ export let onChange: OnCheckboxChange = null
export let disabled = false
export let size: SIZE = 'md'
let classes = ''
diff --git a/web/src/lib/ui/forms/form.svelte b/web/src/lib/ui/forms/form.svelte
index ffe4bda..6a6f41c 100644
--- a/web/src/lib/ui/forms/form.svelte
+++ b/web/src/lib/ui/forms/form.svelte
@@ -22,8 +22,14 @@
'onSubmit is a required property in <Form /> when using the fallback context'
)
}
+ export let onChange: (event: {
+ field: string
+ value: any
+ context: FormState
+ }) => void
export let context = createForm({
initialValues,
+ onChange: ({ field, value }) => onChange({ field, value, context }),
onSubmit: (data) => onSubmit(data, context),
validate: schema ? (data) => formValidate(data, schema) : validate,
validationSchema,
diff --git a/web/src/lib/ui/layout.svelte b/web/src/lib/ui/layout.svelte
index 2dbde68..6ecdbd3 100644
--- a/web/src/lib/ui/layout.svelte
+++ b/web/src/lib/ui/layout.svelte
@@ -77,7 +77,7 @@
capitalize
"
>
- <Icon src={GlobeAlt} theme="outline" class="h-7" />
+ <Icon src={GlobeAlt} class="h-7" />
{$page.data.org.name}
</span>
<div class="flex-1" />
diff --git a/web/src/lib/ui/layout/page.svelte b/web/src/lib/ui/layout/page.svelte
index 4c468cf..6613421 100644
--- a/web/src/lib/ui/layout/page.svelte
+++ b/web/src/lib/ui/layout/page.svelte
@@ -17,9 +17,11 @@
{#if title || $$slots.title || $$slots.header}
<div
class="z-10 sticky top-0 w-full
- bg-slate-50 dork:bg-slate-800
+ bg-white dork:bg-slate-800
drop-shadow-sm dork:drop-shadow-md
border-b border-solid dork:border-gray-700
+ bg-opacity-70
+ backdrop-blur-sm
"
>
<div
diff --git a/web/src/lib/ui/layout/page_header.svelte b/web/src/lib/ui/layout/page_header.svelte
deleted file mode 100644
index 327f799..0000000
--- a/web/src/lib/ui/layout/page_header.svelte
+++ /dev/null
@@ -1,40 +0,0 @@
-<svelte:options immutable={true} />
-
-<script lang="ts">
- import Icon from '../icon.svelte'
- import type { IconSource } from '../icons/types'
-
- let classes = ''
- export { classes as class }
- export let title: string = undefined
- export let icon: IconSource = undefined
-</script>
-
-<div
- class="z-10 sticky top-0 w-full
- bg-slate-50 dork:bg-slate-800
- drop-shadow-sm dork:drop-shadow-md
- border-b border-solid dork:border-gray-700
- px-1.5 md:px-6 py-4
- mb-3 sm:mb-6"
->
- <div class="w-full mx-auto flex flex-row items-center gap-3 {classes}">
- {#if title || $$slots.title}
- <div class="flex flex-row gap-2 items-center">
- {#if icon}
- <Icon src={icon} class="h-6 text-gray-500 dork:text-gray-300" />
- {/if}
- <h1
- class="text-gray-800 dork:text-gray-300 text-lg font-medium capitalize"
- >
- {#if $$slots.title}
- <slot name="title" />
- {:else}
- {title}
- {/if}
- </h1>
- </div>
- {/if}
- <slot />
- </div>
-</div>
diff --git a/web/src/lib/ui/lazy_load.svelte b/web/src/lib/ui/lazy_load.svelte
index 7593f79..da607dc 100644
--- a/web/src/lib/ui/lazy_load.svelte
+++ b/web/src/lib/ui/lazy_load.svelte
@@ -24,6 +24,7 @@
{#if browser}
<InfiniteScroll
+ threshold={100}
elementScroll={document.querySelector('main')?.parentElement}
on:loadMore={onLoadMore}
{hasMore}
diff --git a/web/src/lib/ui/pill.svelte b/web/src/lib/ui/pill.svelte
index b886f6d..73e300b 100644
--- a/web/src/lib/ui/pill.svelte
+++ b/web/src/lib/ui/pill.svelte
@@ -6,44 +6,66 @@
let classes = ''
export { classes as class }
export let size: 'sm' | 'md' = 'md'
- export let color: Color.BLUE | Color.VIOLET | Color.GREEN | Color.RED
+ export let color: Color
+ export let noDot = false
- const colorStyles = {
- [Color.BLUE]:
- 'text-blue-500 dork:text-gray-100 bg-blue-100 dork:bg-blue-600 border-blue-200',
- [Color.VIOLET]:
- 'text-violet-500 dork:text-gray-100 bg-violet-100 dork:bg-violet-600 border-violet-200',
- [Color.GREEN]:
- 'text-green-600 dork:text-gray-100 bg-green-100 dork:bg-green-600 border-green-200',
- [Color.RED]:
- 'text-red-500 dork:text-gray-100 bg-red-100 dork:bg-red-600 border-red-200',
- [Color.YELLOW]:
- 'text-yellow-500 dork:text-gray-100 bg-yellow-100 dork:bg-yellow-600 border-yellow-200',
- }
+ $: textColor = {
+ [Color.BLUE]: 'text-blue-500 dork:text-gray-100',
+ [Color.VIOLET]: 'text-violet-500 dork:text-gray-100',
+ [Color.GREEN]: 'text-green-600 dork:text-gray-100',
+ [Color.RED]: 'text-red-500 dork:text-gray-100',
+ [Color.YELLOW]: 'text-yellow-500 dork:text-gray-100',
+ [Color.GRAY]: 'text-gray-500 dork:text-gray-100',
+ }[color]
- const sizeStyles = {
- sm: 'text-[0.58rem] px-3 py-1',
+ $: borderColor = {
+ [Color.BLUE]: 'border-blue-200',
+ [Color.VIOLET]: 'border-violet-200',
+ [Color.GREEN]: 'border-green-200',
+ [Color.RED]: 'border-red-200',
+ [Color.YELLOW]: 'border-yellow-200',
+ [Color.GRAY]: 'border-gray-200',
+ }[color]
+
+ $: bgColor = {
+ [Color.BLUE]: 'bg-blue-100 dork:bg-blue-600',
+ [Color.VIOLET]: 'bg-violet-100 dork:bg-violet-600',
+ [Color.GREEN]: 'bg-green-100 dork:bg-green-600',
+ [Color.RED]: 'bg-red-100 dork:bg-red-600',
+ [Color.YELLOW]: 'bg-yellow-100 dork:bg-yellow-600',
+ [Color.GRAY]: 'bg-gray-100 dork:bg-gray-600',
+ }[color]
+
+ $: sizing = {
+ xs: 'text-[0.58rem] px-3 py-1',
+ sm: 'text-xs px-4 py-1.5',
md: 'text-sm px-4 py-1.5',
- }
+ }[size]
</script>
<div
+ on:click
+ on:keyup
class="
border dork:border-none
order-state-pill
inline-flex items-center rounded-full
font-medium whitespace-nowrap
- {sizeStyles[size]}
- {colorStyles[color]}
+ {sizing}
+ {borderColor}
+ {textColor}
+ {bgColor}
{classes}
"
>
- <svg
- class="-ml-1 mr-1.5 h-2 w-2 opacity-70"
- fill="currentColor"
- viewBox="0 0 8 8"
- >
- <circle cx="4" cy="4" r="3" />
- </svg>
- <slot />
+ {#if !noDot}
+ <svg
+ class="-ml-1 mr-1.5 h-2 w-2 opacity-70"
+ fill="currentColor"
+ viewBox="0 0 8 8"
+ >
+ <circle cx="4" cy="4" r="3" />
+ </svg>
+ {/if}
+ <slot {borderColor} {textColor} {bgColor} />
</div>
diff --git a/web/src/lib/util/createForm/index.ts b/web/src/lib/util/createForm/index.ts
index 376344f..2785120 100644
--- a/web/src/lib/util/createForm/index.ts
+++ b/web/src/lib/util/createForm/index.ts
@@ -28,6 +28,7 @@ export const createForm = (config) => {
const validationSchema = config.validationSchema
const validateFunction = config.validate
const onSubmit = config.onSubmit
+ const onChange = config.onChange
const getInitial = {
values: () => util.cloneDeep(initialValues),
@@ -99,11 +100,13 @@ export const createForm = (config) => {
return Promise.resolve()
}
- function updateValidateField(field, value) {
+ async function updateValidateField(field, value) {
// if (get(storeGet(form), field) === value) return
updateField(field, value)
- return validateFieldValue(field, value)
+ const ret = await validateFieldValue(field, value)
+ onChange && onChange({ field, value })
+ return ret
}
function handleChange(event) {
diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte
index ad580fd..1323726 100644
--- a/web/src/routes/+layout.svelte
+++ b/web/src/routes/+layout.svelte
@@ -2,8 +2,9 @@
import { setSearchClient } from '#lib/search/web'
import { initSentry } from '#lib/sentry/browser'
import { Layout, SvelteToast, toast, useLayout } from '#lib/ui'
- import { initUnits } from '#lib/ui/units'
+ import { initFiltering } from '#lib/ui/filtering'
import { browser } from '$app/environment'
+ import { page } from '$app/stores'
import type { LayoutData } from './$types'
@@ -25,6 +26,7 @@
toast.danger('Une erreur est survenue')
})
}
+ initFiltering(page)
</script>
{#if $useLayout}
diff --git a/web/src/routes/bills/+page.svelte b/web/src/routes/bills/+page.svelte
index 61a7eae..8179795 100644
--- a/web/src/routes/bills/+page.svelte
+++ b/web/src/routes/bills/+page.svelte
@@ -1,12 +1,15 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { Button, LazyLoad, Page, Spinner } from '#lib/ui'
- import { navigating, page } from '$app/stores'
+ import { navigating } from '$app/stores'
import { grpc } from '#lib/net/grpc/web'
import Dropdown from '#lib/ui/dropdown.svelte'
- import { FilterControls } from '#lib/ui/filtering'
- import { FilterType, type FiltersConfig } from '#lib/ui/filtering/types'
- import { loadStateFromUrl } from '#lib/ui/filtering/lib'
+ import { FilterControls, getFilterQuery } from '#lib/ui/filtering'
+ import {
+ FilterDomain,
+ FilterType,
+ type FiltersConfig,
+ } from '#lib/ui/filtering/types'
import FilterTagList from '#lib/ui/filtering/filter_tag_list.svelte'
import ListBill from '#lib/ui-app/bills/bill_list_item.svelte'
import {
@@ -33,7 +36,7 @@
const { bills: newBills, nextPageToken: newToken } = await grpc(
BillsDefinition
).listBills({
- filters: filterState,
+ filters: $query,
pageToken: nextPageToken,
})
data.nextPageToken = newToken
@@ -53,7 +56,7 @@
collectionAttribute: 'name',
},
]
- let filterState = loadStateFromUrl($page.url)
+ const query = getFilterQuery(FilterDomain.BILLS)
</script>
<Page title="Factures" icon={CurrencyEuro}>
@@ -72,15 +75,15 @@
>
<FilterControls
{filters}
- bind:state={filterState}
+ id={FilterDomain.BILLS}
class="min-w-[14rem]"
onAdd={close}
/>
</Dropdown>
<Button onClick={create} type="success" icon={Plus}>Créer</Button>
</div>
- {#if filterState.values.length}
- <FilterTagList {filters} bind:state={filterState} class="mb-4" />
+ {#if $query.entries.length}
+ <FilterTagList {filters} id={FilterDomain.BILLS} class="mb-4" />
{/if}
<ul class="flex flex-col gap-3 sm:gap-4">
{#each bills as bill (bill.id)}
diff --git a/web/src/routes/bills/+page.ts b/web/src/routes/bills/+page.ts
index 2eeecf6..d61ba7c 100644
--- a/web/src/routes/bills/+page.ts
+++ b/web/src/routes/bills/+page.ts
@@ -1,12 +1,13 @@
import { grpc } from '#lib/net/grpc/web'
import type { PageLoad } from './$types'
-import { loadStateFromUrl } from '#lib/ui/filtering/lib'
+import { loadFilterQueryFromUrl } from '#lib/ui/filtering/lib'
import { BillsDefinition } from '#lib/net/grpc/definitions/bills'
+import { FilterDomain } from '#lib/ui/filtering'
export const load: PageLoad = async ({ url, fetch, parent }) => {
await parent()
- const filters = loadStateFromUrl(url)
+ const filters = loadFilterQueryFromUrl(url, FilterDomain.BILLS)
const { bills, nextPageToken } = await grpc(BillsDefinition, {
fetch,
}).listBills({
diff --git a/web/src/routes/bills/new/client_selection/+page.ts b/web/src/routes/bills/new/client_selection/+page.ts
index 8ddda68..bd0f702 100644
--- a/web/src/routes/bills/new/client_selection/+page.ts
+++ b/web/src/routes/bills/new/client_selection/+page.ts
@@ -11,7 +11,7 @@ export const load: PageLoad = async ({ fetch }) => {
grpc(ClientsDefinition, { fetch }).listClients({}),
grpc(OrdersDefinition, { fetch }).listOrders({
filters: {
- values: [
+ entries: [
{
type: FilterType.CHOICE,
name: 'state',
diff --git a/web/src/routes/orders/+page.svelte b/web/src/routes/orders/+page.svelte
index 9e7e5c7..15e5e0e 100644
--- a/web/src/routes/orders/+page.svelte
+++ b/web/src/routes/orders/+page.svelte
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { Button, LazyLoad, Page } from '#lib/ui'
- import { navigating, page } from '$app/stores'
+ import { navigating } from '$app/stores'
import { grpc } from '#lib/net/grpc/web'
import {
Order,
@@ -9,9 +9,13 @@
Order_State as OrderState,
} from '#lib/net/grpc/definitions/orders'
import Dropdown from '#lib/ui/dropdown.svelte'
- import { FilterControls } from '#lib/ui/filtering'
- import { FilterType, type FiltersConfig } from '#lib/ui/filtering/types'
- import { loadStateFromUrl } from '#lib/ui/filtering/lib'
+ import {
+ FilterControls,
+ FilterDomain,
+ FilterType,
+ getFilterQuery,
+ type FiltersConfig,
+ } from '#lib/ui/filtering'
import FilterTagList from '#lib/ui/filtering/filter_tag_list.svelte'
import { t } from '#lib/translations'
import { enumValues } from '#lib/util'
@@ -27,6 +31,7 @@
Plus,
Users,
} from '#lib/ui/icons'
+ import { colors } from '#lib/ui-app/orders/order_state_pill.svelte'
export let data: PageData
@@ -45,7 +50,7 @@
const { orders: newOrders, nextPageToken: newToken } = await grpc(
OrdersDefinition
).listOrders({
- filters: filterState,
+ filters: $query,
pageToken: nextPageToken,
})
data.nextPageToken = newToken
@@ -69,13 +74,15 @@
name: 'state',
label: 'Statut',
icon: { src: ArrowPathRoundedSquare, theme: 'mini' },
+ multiple: true,
+ color: (v) => colors[v.value],
options: Array.from(enumValues(OrderState)).map((state) => ({
label: $t(`common.order.state.${state}`),
value: state.toString(),
})),
},
]
- let filterState = loadStateFromUrl($page.url)
+ const query = getFilterQuery(FilterDomain.ORDERS)
</script>
<Page title="Commandes" icon={ClipboardDocumentList}>
@@ -86,8 +93,8 @@
<span class="flex-1" />
<Dropdown
type="action"
- label="Filtrer{filterState.values.length
- ? ` (${filterState.values.length})`
+ label="Filtrer{$query.entries.length
+ ? ` (${$query.entries.length})`
: ''}"
chevron="hidden"
icon={{ src: Funnel, theme: 'solid' }}
@@ -96,15 +103,15 @@
>
<FilterControls
{filters}
- bind:state={filterState}
+ id={FilterDomain.ORDERS}
class="min-w-[14rem]"
onAdd={close}
/>
</Dropdown>
<Button onClick={create} type="success" icon={Plus}>Créer</Button>
</div>
- {#if filterState.values.length}
- <FilterTagList {filters} bind:state={filterState} class="mb-4" />
+ {#if $query.entries.length}
+ <FilterTagList {filters} id={FilterDomain.ORDERS} class="mb-4" />
{/if}
<ul class="flex flex-col gap-3 sm:gap-4">
{#each orders as order (order.id)}
diff --git a/web/src/routes/orders/+page.ts b/web/src/routes/orders/+page.ts
index 831b738..793fffc 100644
--- a/web/src/routes/orders/+page.ts
+++ b/web/src/routes/orders/+page.ts
@@ -1,15 +1,17 @@
import { OrdersDefinition } from '#lib/net/grpc/definitions/orders'
import { grpc } from '#lib/net/grpc/web'
-import { loadStateFromUrl } from '#lib/ui/filtering/lib'
+import { FilterDomain } from '#lib/ui/filtering'
+import { loadFilterQueryFromUrl } from '#lib/ui/filtering/lib'
import type { PageLoad } from './$types'
export const load: PageLoad = async ({ url, fetch, parent }) => {
await parent()
- const filters = loadStateFromUrl(url)
+ const filters = loadFilterQueryFromUrl(url, FilterDomain.ORDERS)
const { orders, nextPageToken } = await grpc(OrdersDefinition, {
fetch,
}).listOrders({
+ pageSize: 15,
filters,
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment