-
-
Save ximus/4cd643dcffa202eb95e16d96e1373bfb to your computer and use it in GitHub Desktop.
vite hang
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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