Compare commits
No commits in common. "5eb73ddb3719085c6e51eb311cc63524987600e7" and "5ddf7479a47922f3c3c57c7ed202ef0170c321d4" have entirely different histories.
5eb73ddb37
...
5ddf7479a4
@ -762,43 +762,6 @@ def upgrade() -> None:
|
|||||||
['code'],
|
['code'],
|
||||||
schema='v3',
|
schema='v3',
|
||||||
)
|
)
|
||||||
create_table_if_not_exists(
|
|
||||||
'form3_phase',
|
|
||||||
sa.Column('rf_project_report_id', sa.Integer(),primary_key=True),
|
|
||||||
sa.Column('phase_code', sa.String(), primary_key=True),
|
|
||||||
sa.Column('role', sa.String(), nullable=False),
|
|
||||||
sa.Column('column_keys', postgresql.ARRAY(sa.Text()), nullable=False),
|
|
||||||
sa.Column('opens_at', sa.DateTime(timezone=True), nullable=False),
|
|
||||||
sa.Column('closes_at', sa.DateTime(timezone=True), nullable=False),
|
|
||||||
schema='v3',
|
|
||||||
)
|
|
||||||
|
|
||||||
create_check_constraint_if_not_exists(
|
|
||||||
'chk_v3_form3_phase_window', 'form3_phase',
|
|
||||||
sa.text('closes_at > opens_at'),
|
|
||||||
schema='v3',
|
|
||||||
)
|
|
||||||
create_check_constraint_if_not_exists(
|
|
||||||
'chk_v3_form3_phase_columns', 'form3_phase',
|
|
||||||
sa.text('cardinality(column_keys) > 0'),
|
|
||||||
schema='v3',
|
|
||||||
)
|
|
||||||
create_check_constraint_if_not_exists(
|
|
||||||
'chk_v3_form3_phase_no_admin', 'form3_phase',
|
|
||||||
sa.text("role != 'ADMIN'"),
|
|
||||||
schema='v3',
|
|
||||||
)
|
|
||||||
create_index_if_not_exists(
|
|
||||||
'ix_v3_form3_phase_active', 'form3_phase',
|
|
||||||
['rf_project_report_id', 'role', 'opens_at', 'closes_at'],
|
|
||||||
schema='v3',
|
|
||||||
)
|
|
||||||
create_index_if_not_exists(
|
|
||||||
'ix_v3_form3_phase_columns_gin', 'form3_phase',
|
|
||||||
['column_keys'],
|
|
||||||
schema='v3',
|
|
||||||
postgresql_using='gin',
|
|
||||||
)
|
|
||||||
create_table_if_not_exists(
|
create_table_if_not_exists(
|
||||||
'security_detail',
|
'security_detail',
|
||||||
sa.Column('id', sa.Integer(),
|
sa.Column('id', sa.Integer(),
|
||||||
|
|||||||
@ -1,63 +0,0 @@
|
|||||||
from alembic import op
|
|
||||||
|
|
||||||
|
|
||||||
revision = "0004"
|
|
||||||
down_revision = "0003"
|
|
||||||
branch_labels = None
|
|
||||||
depends_on = None
|
|
||||||
|
|
||||||
|
|
||||||
def upgrade() -> None:
|
|
||||||
op.execute(
|
|
||||||
"""
|
|
||||||
CREATE OR REPLACE FUNCTION v3.del_vsp(p_vsp_id integer, p_deleted_by integer)
|
|
||||||
RETURNS integer
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
AS $function$
|
|
||||||
DECLARE
|
|
||||||
v_old RECORD;
|
|
||||||
v_usr RECORD;
|
|
||||||
BEGIN
|
|
||||||
SELECT vsp.id, vsp.branch_id
|
|
||||||
INTO v_old
|
|
||||||
FROM v3.vsp vsp WHERE vsp.id = p_vsp_id;
|
|
||||||
IF NOT FOUND THEN
|
|
||||||
RAISE EXCEPTION 'vsp #% не существует', p_vsp_id;
|
|
||||||
END IF;
|
|
||||||
SELECT usr.id
|
|
||||||
INTO v_usr
|
|
||||||
FROM v3.app_user usr WHERE usr.id = p_deleted_by;
|
|
||||||
IF NOT FOUND THEN
|
|
||||||
RAISE EXCEPTION 'user #% не существует', p_deleted_by;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
UPDATE v3.vsp
|
|
||||||
SET is_deleted = true,
|
|
||||||
is_active = false,
|
|
||||||
updated_by = p_deleted_by
|
|
||||||
WHERE id = p_vsp_id;
|
|
||||||
UPDATE v3.vsp
|
|
||||||
SET closed_at = CURRENT_DATE
|
|
||||||
WHERE id = p_vsp_id
|
|
||||||
AND closed_at IS NULL;
|
|
||||||
|
|
||||||
PERFORM v3.log_event(
|
|
||||||
'DELETE_VSP', 'VSP',
|
|
||||||
jsonb_build_object(
|
|
||||||
'vsp_id', p_vsp_id,
|
|
||||||
'entity_id', p_vsp_id,
|
|
||||||
'branch_id', v_old.branch_id,
|
|
||||||
'updated_by', p_deleted_by
|
|
||||||
),
|
|
||||||
null, null, v_old.branch_id
|
|
||||||
);
|
|
||||||
|
|
||||||
RETURN p_vsp_id;
|
|
||||||
END;
|
|
||||||
$function$;
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def downgrade() -> None:
|
|
||||||
pass
|
|
||||||
@ -4078,12 +4078,26 @@ $function$
|
|||||||
|
|
||||||
-- DROP FUNCTION v3.v_form1_smeta(int4, int4);
|
-- DROP FUNCTION v3.v_form1_smeta(int4, int4);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.v_form1_smeta(p_year integer, p_org_unit_id integer)
|
|
||||||
RETURNS TABLE(row_type character varying, depth integer, section_code character varying, name character varying, supp_plan_q1 numeric, supp_plan_q2 numeric, supp_plan_q3 numeric, supp_plan_q4 numeric, supp_plan_year numeric, dev_plan_q1 numeric, dev_plan_q2 numeric, dev_plan_q3 numeric, dev_plan_q4 numeric, dev_plan_year numeric, total_plan_year numeric, supp_appr_q1 numeric, supp_appr_q2 numeric, supp_appr_q3 numeric, supp_appr_q4 numeric, supp_appr_year numeric, dev_appr_q1 numeric, dev_appr_q2 numeric, dev_appr_q3 numeric, dev_appr_q4 numeric, dev_appr_year numeric, total_appr_year numeric, supp_act_q1 numeric, supp_act_q2 numeric, supp_act_q3 numeric, supp_act_q4 numeric, supp_act_year numeric, dev_act_q1 numeric, dev_act_q2 numeric, dev_act_q3 numeric, dev_act_q4 numeric, dev_act_year numeric, total_act_year numeric, supp_corr_q2 numeric, supp_corr_q3 numeric, supp_corr_q4 numeric, dev_corr_q2 numeric, dev_corr_q3 numeric, dev_corr_q4 numeric)
|
CREATE FUNCTION v3.v_form1_smeta(p_year INT, p_org_unit_id INT)
|
||||||
LANGUAGE plpgsql
|
RETURNS TABLE (
|
||||||
STABLE
|
row_type VARCHAR,
|
||||||
SET search_path TO 'v3', 'pg_catalog'
|
depth INT,
|
||||||
AS $function$
|
section_code VARCHAR,
|
||||||
|
name VARCHAR,
|
||||||
|
supp_plan_q1 NUMERIC, supp_plan_q2 NUMERIC, supp_plan_q3 NUMERIC, supp_plan_q4 NUMERIC, supp_plan_year NUMERIC,
|
||||||
|
dev_plan_q1 NUMERIC, dev_plan_q2 NUMERIC, dev_plan_q3 NUMERIC, dev_plan_q4 NUMERIC, dev_plan_year NUMERIC,
|
||||||
|
total_plan_year NUMERIC,
|
||||||
|
supp_appr_q1 NUMERIC, supp_appr_q2 NUMERIC, supp_appr_q3 NUMERIC, supp_appr_q4 NUMERIC, supp_appr_year NUMERIC,
|
||||||
|
dev_appr_q1 NUMERIC, dev_appr_q2 NUMERIC, dev_appr_q3 NUMERIC, dev_appr_q4 NUMERIC, dev_appr_year NUMERIC,
|
||||||
|
total_appr_year NUMERIC,
|
||||||
|
supp_act_q1 NUMERIC, supp_act_q2 NUMERIC, supp_act_q3 NUMERIC, supp_act_q4 NUMERIC, supp_act_year NUMERIC,
|
||||||
|
dev_act_q1 NUMERIC, dev_act_q2 NUMERIC, dev_act_q3 NUMERIC, dev_act_q4 NUMERIC, dev_act_year NUMERIC,
|
||||||
|
total_act_year NUMERIC,
|
||||||
|
supp_corr_q2 NUMERIC, supp_corr_q3 NUMERIC, supp_corr_q4 NUMERIC,
|
||||||
|
dev_corr_q2 NUMERIC, dev_corr_q3 NUMERIC, dev_corr_q4 NUMERIC
|
||||||
|
)
|
||||||
|
AS $$
|
||||||
#variable_conflict use_column
|
#variable_conflict use_column
|
||||||
DECLARE
|
DECLARE
|
||||||
v_ahr INT; v_cap INT; v_oper INT;
|
v_ahr INT; v_cap INT; v_oper INT;
|
||||||
@ -4213,8 +4227,8 @@ BEGIN
|
|||||||
FROM merged m
|
FROM merged m
|
||||||
ORDER BY m.sc;
|
ORDER BY m.sc;
|
||||||
END;
|
END;
|
||||||
$function$
|
$$ LANGUAGE plpgsql STABLE SET search_path = v3, pg_catalog;
|
||||||
;
|
|
||||||
-- DROP FUNCTION v3.v_form2_sheet_sections(int4, varchar, _text);
|
-- DROP FUNCTION v3.v_form2_sheet_sections(int4, varchar, _text);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.v_form2_sheet_sections(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[])
|
CREATE OR REPLACE FUNCTION v3.v_form2_sheet_sections(p_form_id integer, p_sheet character varying, p_sections text[] DEFAULT NULL::text[])
|
||||||
@ -7772,407 +7786,4 @@ BEGIN
|
|||||||
|
|
||||||
END;
|
END;
|
||||||
$function$
|
$function$
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.copy_template_to_report(p_report_id INT)
|
|
||||||
RETURNS INTEGER
|
|
||||||
AS $function$
|
|
||||||
DECLARE
|
|
||||||
v_report_type VARCHAR;
|
|
||||||
v_inserted INTEGER;
|
|
||||||
BEGIN
|
|
||||||
SELECT report_type INTO v_report_type
|
|
||||||
FROM v3.rf_project_report
|
|
||||||
WHERE id = p_report_id;
|
|
||||||
|
|
||||||
IF NOT FOUND THEN
|
|
||||||
RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
INSERT INTO v3.form3_phase
|
|
||||||
(rf_project_report_id, phase_code, role, column_keys, opens_at, closes_at)
|
|
||||||
SELECT p_report_id, pt.phase_code, pt.role,
|
|
||||||
pt.column_keys, pt.opens_at, pt.closes_at
|
|
||||||
FROM v3.phase_template pt
|
|
||||||
WHERE pt.form_type = 'FORM_3'
|
|
||||||
AND pt.sheet = v_report_type
|
|
||||||
ON CONFLICT (rf_project_report_id, phase_code) DO NOTHING;
|
|
||||||
|
|
||||||
GET DIAGNOSTICS v_inserted = ROW_COUNT;
|
|
||||||
RETURN v_inserted;
|
|
||||||
END;
|
|
||||||
$function$
|
|
||||||
LANGUAGE plpgsql VOLATILE;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.user_in_report_org(p_user_id INT, p_report_id INT)
|
|
||||||
RETURNS BOOLEAN
|
|
||||||
AS $function$
|
|
||||||
DECLARE
|
|
||||||
v_org INT;
|
|
||||||
BEGIN
|
|
||||||
SELECT p.org_unit_id
|
|
||||||
INTO v_org
|
|
||||||
FROM v3.rf_project_report r
|
|
||||||
JOIN v3.project p ON p.id = r.project_id
|
|
||||||
WHERE r.id = p_report_id;
|
|
||||||
|
|
||||||
IF v_org IS NULL THEN
|
|
||||||
RETURN TRUE; -- проект без org_unit — пропускаем фильтр
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM v3.user_org
|
|
||||||
WHERE user_id = p_user_id
|
|
||||||
AND org_unit_id = v_org
|
|
||||||
);
|
|
||||||
END;
|
|
||||||
$function$
|
|
||||||
LANGUAGE plpgsql STABLE;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.editable_columns_for3(
|
|
||||||
p_report_id INT,
|
|
||||||
p_user_id INT
|
|
||||||
)
|
|
||||||
RETURNS TABLE (
|
|
||||||
column_key TEXT,
|
|
||||||
closes_at TIMESTAMPTZ
|
|
||||||
)
|
|
||||||
AS $function$
|
|
||||||
DECLARE
|
|
||||||
v_role VARCHAR;
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM v3.rf_project_report WHERE id = p_report_id) THEN
|
|
||||||
RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
v_role := v3.user_role_code(p_user_id);
|
|
||||||
IF v_role IS NULL THEN
|
|
||||||
RETURN; -- неизвестный/неактивный юзер → пустая маска
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- ADMIN: UNION всех column_keys всех фаз отчёта.
|
|
||||||
IF v_role = 'ADMIN' THEN
|
|
||||||
RETURN QUERY
|
|
||||||
SELECT ck AS column_key,
|
|
||||||
MAX(fp.closes_at) AS closes_at
|
|
||||||
FROM v3.form3_phase fp,
|
|
||||||
LATERAL unnest(fp.column_keys) AS ck
|
|
||||||
WHERE fp.rf_project_report_id = p_report_id
|
|
||||||
GROUP BY ck;
|
|
||||||
RETURN;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- EXECUTOR_RF без назначения → пустая маска (default deny).
|
|
||||||
IF v_role = 'EXECUTOR_RF' AND NOT v3.user_in_report_org(p_user_id, p_report_id) THEN
|
|
||||||
RETURN;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
RETURN QUERY
|
|
||||||
SELECT ck AS column_key,
|
|
||||||
MAX(fp.closes_at) AS closes_at
|
|
||||||
FROM v3.form3_phase fp,
|
|
||||||
LATERAL unnest(fp.column_keys) AS ck
|
|
||||||
WHERE fp.rf_project_report_id = p_report_id
|
|
||||||
AND fp.role = v_role
|
|
||||||
AND now() BETWEEN fp.opens_at AND fp.closes_at
|
|
||||||
GROUP BY ck;
|
|
||||||
END;
|
|
||||||
$function$
|
|
||||||
LANGUAGE plpgsql STABLE;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.can_edit3(
|
|
||||||
p_report_id INT,
|
|
||||||
p_column_key TEXT,
|
|
||||||
p_user_id INT
|
|
||||||
)
|
|
||||||
RETURNS JSONB
|
|
||||||
AS $function$
|
|
||||||
DECLARE
|
|
||||||
v_role VARCHAR;
|
|
||||||
v_active_close TIMESTAMPTZ;
|
|
||||||
v_role_has_col BOOLEAN;
|
|
||||||
v_next_open TIMESTAMPTZ;
|
|
||||||
v_last_close TIMESTAMPTZ;
|
|
||||||
BEGIN
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM v3.rf_project_report WHERE id = p_report_id) THEN
|
|
||||||
RAISE EXCEPTION 'rf_project_report id=% не существует', p_report_id;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
v_role := v3.user_role_code(p_user_id);
|
|
||||||
IF v_role IS NULL THEN
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'ok', false,
|
|
||||||
'code', 'role_not_allowed',
|
|
||||||
'detail', 'unknown or inactive user'
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- ADMIN — bypass всех проверок.
|
|
||||||
IF v_role = 'ADMIN' THEN
|
|
||||||
RETURN jsonb_build_object('ok', true, 'admin', true);
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- EXECUTOR_RF: фильтр по user_org проекта отчёта.
|
|
||||||
IF v_role = 'EXECUTOR_RF' AND NOT v3.user_in_report_org(p_user_id, p_report_id) THEN
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'ok', false,
|
|
||||||
'code', 'org_not_assigned',
|
|
||||||
'detail', p_column_key
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- 1. Активная сейчас фаза, в которой эта колонка перечислена для этой роли?
|
|
||||||
SELECT MAX(fp.closes_at)
|
|
||||||
INTO v_active_close
|
|
||||||
FROM v3.form3_phase fp
|
|
||||||
WHERE fp.rf_project_report_id = p_report_id
|
|
||||||
AND fp.role = v_role
|
|
||||||
AND p_column_key = ANY(fp.column_keys)
|
|
||||||
AND now() BETWEEN fp.opens_at AND fp.closes_at;
|
|
||||||
|
|
||||||
IF v_active_close IS NOT NULL THEN
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'ok', true,
|
|
||||||
'closes_at', v_active_close
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- 2. Колонка В ПРИНЦИПЕ есть хоть в одной фазе этой роли на этом отчёте?
|
|
||||||
SELECT EXISTS (
|
|
||||||
SELECT 1
|
|
||||||
FROM v3.form3_phase fp
|
|
||||||
WHERE fp.rf_project_report_id = p_report_id
|
|
||||||
AND fp.role = v_role
|
|
||||||
AND p_column_key = ANY(fp.column_keys)
|
|
||||||
) INTO v_role_has_col;
|
|
||||||
|
|
||||||
IF NOT v_role_has_col THEN
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'ok', false,
|
|
||||||
'code', 'role_not_allowed',
|
|
||||||
'detail', p_column_key
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- 3. Колонка доступна, но окно не активно. Подсказать ближайшее.
|
|
||||||
SELECT MIN(fp.opens_at)
|
|
||||||
INTO v_next_open
|
|
||||||
FROM v3.form3_phase fp
|
|
||||||
WHERE fp.rf_project_report_id = p_report_id
|
|
||||||
AND fp.role = v_role
|
|
||||||
AND p_column_key = ANY(fp.column_keys)
|
|
||||||
AND fp.opens_at > now();
|
|
||||||
|
|
||||||
IF v_next_open IS NOT NULL THEN
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'ok', false,
|
|
||||||
'code', 'window_closed',
|
|
||||||
'detail', p_column_key,
|
|
||||||
'opens_at', v_next_open
|
|
||||||
);
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
SELECT MAX(fp.closes_at)
|
|
||||||
INTO v_last_close
|
|
||||||
FROM v3.form3_phase fp
|
|
||||||
WHERE fp.rf_project_report_id = p_report_id
|
|
||||||
AND fp.role = v_role
|
|
||||||
AND p_column_key = ANY(fp.column_keys)
|
|
||||||
AND fp.closes_at <= now();
|
|
||||||
|
|
||||||
RETURN jsonb_build_object(
|
|
||||||
'ok', false,
|
|
||||||
'code', 'window_closed',
|
|
||||||
'detail', p_column_key,
|
|
||||||
'closes_at', v_last_close
|
|
||||||
);
|
|
||||||
END;
|
|
||||||
$function$
|
|
||||||
LANGUAGE plpgsql STABLE;
|
|
||||||
|
|
||||||
-- ─── 3. Authz-обёртки upd_form3_cell/cells (доп. сигнатуры с p_user_id) ─────
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.upd_form3_cells(
|
|
||||||
p_report_id INT,
|
|
||||||
p_changes JSONB,
|
|
||||||
p_user_id INT
|
|
||||||
)
|
|
||||||
RETURNS TABLE (
|
|
||||||
row_type VARCHAR,
|
|
||||||
depth INT,
|
|
||||||
sort_order BIGINT,
|
|
||||||
data JSONB
|
|
||||||
)
|
|
||||||
AS $function$
|
|
||||||
DECLARE
|
|
||||||
v_change JSONB;
|
|
||||||
v_column TEXT;
|
|
||||||
v_decision JSONB;
|
|
||||||
v_msg TEXT;
|
|
||||||
BEGIN
|
|
||||||
IF p_user_id IS NULL THEN
|
|
||||||
RAISE EXCEPTION 'role_not_allowed: <missing user_id>';
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM v3.app_user WHERE id = p_user_id AND is_active) THEN
|
|
||||||
RAISE EXCEPTION 'role_not_allowed: unknown or inactive user %', p_user_id;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF p_changes IS NULL OR jsonb_typeof(p_changes) <> 'array' THEN
|
|
||||||
RAISE EXCEPTION 'p_changes must be a JSONB array';
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
FOR v_change IN SELECT * FROM jsonb_array_elements(p_changes)
|
|
||||||
LOOP
|
|
||||||
v_column := v_change->>'column';
|
|
||||||
IF v_column IS NULL THEN
|
|
||||||
RAISE EXCEPTION 'change must have column: %', v_change;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
v_decision := v3.can_edit3(p_report_id, v_column, p_user_id);
|
|
||||||
|
|
||||||
IF NOT (v_decision->>'ok')::boolean THEN
|
|
||||||
IF v_decision->>'code' = 'role_not_allowed' THEN
|
|
||||||
RAISE EXCEPTION 'role_not_allowed: %', v_column;
|
|
||||||
|
|
||||||
ELSIF v_decision->>'code' = 'org_not_assigned' THEN
|
|
||||||
RAISE EXCEPTION 'org_not_assigned: %', v_column;
|
|
||||||
|
|
||||||
ELSIF v_decision->>'code' = 'window_closed' THEN
|
|
||||||
IF v_decision ? 'opens_at' THEN
|
|
||||||
v_msg := format('window_closed: %s (opens %s)',
|
|
||||||
v_column, v_decision->>'opens_at');
|
|
||||||
ELSIF v_decision->>'closes_at' IS NOT NULL THEN
|
|
||||||
v_msg := format('window_closed: %s (closed %s)',
|
|
||||||
v_column, v_decision->>'closes_at');
|
|
||||||
ELSE
|
|
||||||
v_msg := format('window_closed: %s (no phase)', v_column);
|
|
||||||
END IF;
|
|
||||||
RAISE EXCEPTION '%', v_msg;
|
|
||||||
|
|
||||||
ELSE
|
|
||||||
RAISE EXCEPTION '%: %', v_decision->>'code', v_column;
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
END LOOP;
|
|
||||||
|
|
||||||
RETURN QUERY
|
|
||||||
SELECT * FROM v3.upd_form3_cells(p_report_id, p_changes);
|
|
||||||
END;
|
|
||||||
$function$
|
|
||||||
LANGUAGE plpgsql VOLATILE;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.upd_form3_cell(
|
|
||||||
p_report_id INT,
|
|
||||||
p_line_id INT,
|
|
||||||
p_column TEXT,
|
|
||||||
p_value JSONB,
|
|
||||||
p_user_id INT
|
|
||||||
)
|
|
||||||
RETURNS TABLE (
|
|
||||||
row_type VARCHAR,
|
|
||||||
depth INT,
|
|
||||||
sort_order BIGINT,
|
|
||||||
data JSONB
|
|
||||||
)
|
|
||||||
AS $function$
|
|
||||||
SELECT * FROM v3.upd_form3_cells(
|
|
||||||
p_report_id,
|
|
||||||
jsonb_build_array(jsonb_build_object(
|
|
||||||
'line_id', p_line_id,
|
|
||||||
'column', p_column,
|
|
||||||
'value', p_value
|
|
||||||
)),
|
|
||||||
p_user_id
|
|
||||||
);
|
|
||||||
$function$
|
|
||||||
LANGUAGE sql VOLATILE;
|
|
||||||
|
|
||||||
-- ─── 4. add_project — раскладка snapshot обоих отчётов ──────────────────────
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION v3.add_project(
|
|
||||||
p_name VARCHAR,
|
|
||||||
p_year INT,
|
|
||||||
p_org_unit_id INT,
|
|
||||||
p_level VARCHAR DEFAULT 'project',
|
|
||||||
p_parent_id INT DEFAULT NULL,
|
|
||||||
p_project_type VARCHAR DEFAULT NULL,
|
|
||||||
p_vsp_format VARCHAR DEFAULT NULL,
|
|
||||||
p_placement_type VARCHAR DEFAULT NULL,
|
|
||||||
p_object_address VARCHAR DEFAULT NULL,
|
|
||||||
p_staff_count INT DEFAULT NULL,
|
|
||||||
p_total_area NUMERIC DEFAULT NULL
|
|
||||||
)
|
|
||||||
RETURNS TABLE (
|
|
||||||
project_id INT,
|
|
||||||
limit_report_id INT,
|
|
||||||
current_expenses_report_id INT
|
|
||||||
)
|
|
||||||
AS $function$
|
|
||||||
DECLARE
|
|
||||||
v_pid INT;
|
|
||||||
v_lim INT;
|
|
||||||
v_cur INT;
|
|
||||||
BEGIN
|
|
||||||
IF p_name IS NULL OR length(trim(p_name)) = 0 THEN
|
|
||||||
RAISE EXCEPTION 'p_name обязателен';
|
|
||||||
END IF;
|
|
||||||
IF p_year IS NULL THEN
|
|
||||||
RAISE EXCEPTION 'p_year обязателен';
|
|
||||||
END IF;
|
|
||||||
IF p_org_unit_id IS NULL THEN
|
|
||||||
RAISE EXCEPTION 'p_org_unit_id обязателен';
|
|
||||||
END IF;
|
|
||||||
IF NOT EXISTS (SELECT 1 FROM v3.org_unit WHERE id = p_org_unit_id) THEN
|
|
||||||
RAISE EXCEPTION 'org_unit id=% не существует', p_org_unit_id;
|
|
||||||
END IF;
|
|
||||||
IF p_parent_id IS NOT NULL AND
|
|
||||||
NOT EXISTS (SELECT 1 FROM v3.project WHERE id = p_parent_id AND level = 'program') THEN
|
|
||||||
RAISE EXCEPTION 'parent project id=% не существует или не имеет level=program', p_parent_id;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
INSERT INTO v3.project (
|
|
||||||
name, level, parent_id, org_unit_id,
|
|
||||||
project_type, vsp_format, placement_type, object_address, staff_count, total_area
|
|
||||||
) VALUES (
|
|
||||||
p_name, p_level, p_parent_id, p_org_unit_id,
|
|
||||||
p_project_type, p_vsp_format, p_placement_type, p_object_address, p_staff_count, p_total_area
|
|
||||||
) RETURNING id INTO v_pid;
|
|
||||||
|
|
||||||
INSERT INTO v3.rf_project_report (project_id, year, report_type)
|
|
||||||
VALUES (v_pid, p_year, 'LIMIT') RETURNING id INTO v_lim;
|
|
||||||
INSERT INTO v3.rf_project_report (project_id, year, report_type)
|
|
||||||
VALUES (v_pid, p_year, 'CURRENT_EXPENSES') RETURNING id INTO v_cur;
|
|
||||||
|
|
||||||
-- Snapshot RBAC-фаз (иначе default deny — отчёт read-only).
|
|
||||||
PERFORM v3.copy_template_to_report(v_lim);
|
|
||||||
PERFORM v3.copy_template_to_report(v_cur);
|
|
||||||
|
|
||||||
PERFORM v3.log_event(
|
|
||||||
'PROJECT_CREATE', 'PROJECT_CREATE',
|
|
||||||
jsonb_strip_nulls(jsonb_build_object(
|
|
||||||
'project_id', v_pid,
|
|
||||||
'name', p_name,
|
|
||||||
'year', p_year,
|
|
||||||
'level', p_level,
|
|
||||||
'parent_id', p_parent_id,
|
|
||||||
'org_unit_id', p_org_unit_id,
|
|
||||||
'project_type', p_project_type,
|
|
||||||
'vsp_format', p_vsp_format,
|
|
||||||
'placement_type', p_placement_type,
|
|
||||||
'object_address', p_object_address,
|
|
||||||
'staff_count', p_staff_count,
|
|
||||||
'total_area', p_total_area,
|
|
||||||
'limit_report_id', v_lim,
|
|
||||||
'current_expenses_report_id', v_cur
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
RETURN QUERY SELECT v_pid, v_lim, v_cur;
|
|
||||||
END;
|
|
||||||
$function$
|
|
||||||
LANGUAGE plpgsql VOLATILE;
|
|
||||||
|
|
||||||
@ -4250,29 +4250,4 @@ INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES
|
|||||||
INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES
|
INSERT INTO v3.expense_item_form_type (expense_item_id,form_type_code) VALUES
|
||||||
(798,'FORM_3');
|
(798,'FORM_3');
|
||||||
|
|
||||||
REFRESH MATERIALIZED VIEW CONCURRENTLY v3.mv_expense_item_tree;
|
REFRESH MATERIALIZED VIEW CONCURRENTLY v3.mv_expense_item_tree;
|
||||||
|
|
||||||
|
|
||||||
-- ─── 5. Seed фаз FORM_3 (идемпотентно, только FORM_3) ──────────────────────
|
|
||||||
-- DELETE скоупится на FORM_3 → фазы FORM_1/2/4 не затрагиваются.
|
|
||||||
DELETE FROM v3.phase_template WHERE form_type = 'FORM_3';
|
|
||||||
|
|
||||||
INSERT INTO v3.phase_template
|
|
||||||
(form_type, sheet, phase_code, role, column_keys, opens_at, closes_at)
|
|
||||||
SELECT 'FORM_3', sh, 'RF_FILL_2026', 'EXECUTOR_RF',
|
|
||||||
ARRAY[
|
|
||||||
'q1.adj_by_items','q1.adj_increase','q1.m1','q1.m2','q1.m3',
|
|
||||||
'q2.adj_by_items','q2.adj_increase','q2.m1','q2.m2','q2.m3',
|
|
||||||
'q3.adj_by_items','q3.adj_increase','q3.m1','q3.m2','q3.m3',
|
|
||||||
'q4.adj_by_items','q4.adj_increase','q4.m1','q4.m2','q4.m3','q4.spod'
|
|
||||||
],
|
|
||||||
'2026-01-01 00:00:00+00'::timestamptz,
|
|
||||||
'2026-12-31 23:59:59+00'::timestamptz
|
|
||||||
FROM (VALUES ('LIMIT'), ('CURRENT_EXPENSES')) AS s(sh);
|
|
||||||
|
|
||||||
-- ─── 6. Backfill v3.form3_phase для существующих отчётов ────────────────────
|
|
||||||
-- ON CONFLICT DO NOTHING внутри copy_template_to_report → повторный прогон
|
|
||||||
-- ничего не дублирует.
|
|
||||||
SELECT id, v3.copy_template_to_report(id) AS phases_inserted
|
|
||||||
FROM v3.rf_project_report
|
|
||||||
ORDER BY id;
|
|
||||||
@ -6,7 +6,7 @@ from src.core.errors import AccessDeniedException
|
|||||||
from src.api.v1.deps import get_current_active_user, require_admin
|
from src.api.v1.deps import get_current_active_user, require_admin
|
||||||
from src.db.models.app_user import AppUser
|
from src.db.models.app_user import AppUser
|
||||||
from src.db.session import get_db
|
from src.db.session import get_db
|
||||||
from src.domain.schemas import BaseListResponse, BaseSingleResponse, OrgUnitCreateSchema, OrgUnitListSchema, OrgUnitSchema, OrgUnitUpdateSchema, ResponseBase
|
from src.domain.schemas import BaseListResponse, BaseSingleResponse, OrgUnitBaseSchema, OrgUnitListSchema, OrgUnitSchema, OrgUnitUpdateSchema, ResponseBase
|
||||||
from src.services.org_unit_service import OrgUnitService
|
from src.services.org_unit_service import OrgUnitService
|
||||||
|
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ async def org_unit_id(
|
|||||||
|
|
||||||
@router.post("/")
|
@router.post("/")
|
||||||
async def create_ssp(
|
async def create_ssp(
|
||||||
org_data: OrgUnitCreateSchema,
|
org_data: OrgUnitBaseSchema,
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
current_user: AppUser = Depends(require_admin),
|
current_user: AppUser = Depends(require_admin),
|
||||||
) -> BaseSingleResponse[OrgUnitSchema]:
|
) -> BaseSingleResponse[OrgUnitSchema]:
|
||||||
|
|||||||
@ -14,8 +14,6 @@ from sqlalchemy.exc import IntegrityError
|
|||||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status
|
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, status
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from src.services.rf_project_report_service import RfProjectReportService
|
|
||||||
from src.services.rf_project_report_line_service import RfProjectReportLineService
|
|
||||||
from src.repository.user_repository import UserRepository
|
from src.repository.user_repository import UserRepository
|
||||||
from src.services.budget_line_service import BudgetLineService
|
from src.services.budget_line_service import BudgetLineService
|
||||||
from src.api.v1.deps import get_user_by_token
|
from src.api.v1.deps import get_user_by_token
|
||||||
@ -492,8 +490,7 @@ class FormEventProcess:
|
|||||||
class ProjectEventProcess:
|
class ProjectEventProcess:
|
||||||
def __init__(self, db: AsyncSession):
|
def __init__(self, db: AsyncSession):
|
||||||
self.project_service: ProjectService = ProjectService(db)
|
self.project_service: ProjectService = ProjectService(db)
|
||||||
self.prl_service: RfProjectReportLineService = RfProjectReportLineService(db)
|
self.bf_service: BudgetFormService = BudgetFormService(db)
|
||||||
self.pr_service: RfProjectReportService = RfProjectReportService(db)
|
|
||||||
self.user_service: UserService = UserService(db)
|
self.user_service: UserService = UserService(db)
|
||||||
|
|
||||||
async def process(
|
async def process(
|
||||||
@ -576,16 +573,11 @@ class ProjectEventProcess:
|
|||||||
expense_item_id: Optional[int] = None
|
expense_item_id: Optional[int] = None
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
project_report = await self.pr_service.get(
|
project = await self.project_service.get(project_id=project_id, user=user)
|
||||||
year=year,
|
if not project:
|
||||||
report_type=report_type,
|
|
||||||
project_id=project_id,
|
|
||||||
)
|
|
||||||
if not project_report:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
ids = await self.prl_service.get_ids(project_report_id=project_report.id)
|
return await self.project_service.add_form3_line(
|
||||||
result = await self.project_service.add_form3_line(
|
|
||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
year=year,
|
year=year,
|
||||||
report_type=report_type,
|
report_type=report_type,
|
||||||
@ -593,16 +585,6 @@ class ProjectEventProcess:
|
|||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
final_result = {
|
|
||||||
"data": result,
|
|
||||||
"new_line_id": None,
|
|
||||||
}
|
|
||||||
for el in result:
|
|
||||||
if el[3]["line_id"] and el[3]["line_id"] not in ids:
|
|
||||||
final_result["new_line_id"] = el[3]["line_id"]
|
|
||||||
return final_result
|
|
||||||
return final_result
|
|
||||||
|
|
||||||
async def __del_row(
|
async def __del_row(
|
||||||
self,
|
self,
|
||||||
event_data: dict,
|
event_data: dict,
|
||||||
@ -613,7 +595,7 @@ class ProjectEventProcess:
|
|||||||
) -> list[tuple]:
|
) -> list[tuple]:
|
||||||
"""
|
"""
|
||||||
data = {
|
data = {
|
||||||
"row_id": int,
|
"line_id": int,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
project = await self.project_service.get(project_id=project_id, user=user)
|
project = await self.project_service.get(project_id=project_id, user=user)
|
||||||
@ -624,7 +606,7 @@ class ProjectEventProcess:
|
|||||||
project_id=project_id,
|
project_id=project_id,
|
||||||
year=year,
|
year=year,
|
||||||
report_type=report_type,
|
report_type=report_type,
|
||||||
line_id=event_data["row_id"],
|
line_id=event_data["line_id"],
|
||||||
user=user,
|
user=user,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -794,7 +776,6 @@ async def process_websocket(
|
|||||||
async with get_db_session(user_id=user_id) as db:
|
async with get_db_session(user_id=user_id) as db:
|
||||||
processor = processor_cls(db)
|
processor = processor_cls(db)
|
||||||
t0 = time.perf_counter()
|
t0 = time.perf_counter()
|
||||||
data_old = data.copy()
|
|
||||||
data["result"] = await processor.process(
|
data["result"] = await processor.process(
|
||||||
event_data=data,
|
event_data=data,
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
@ -808,7 +789,7 @@ async def process_websocket(
|
|||||||
"time_ms": f"{db_ms:.2f}",
|
"time_ms": f"{db_ms:.2f}",
|
||||||
"event": data.get("event"),
|
"event": data.get("event"),
|
||||||
}
|
}
|
||||||
extra.update(data_old)
|
extra.update(data)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"db_time: {extra}",
|
f"db_time: {extra}",
|
||||||
extra=extra,
|
extra=extra,
|
||||||
|
|||||||
@ -10,7 +10,6 @@ from src.db.models.collegial_approval import CollegialApproval
|
|||||||
from src.db.models.contract_detail import ContractDetail
|
from src.db.models.contract_detail import ContractDetail
|
||||||
from src.db.models.contract_summary import ContractSummary
|
from src.db.models.contract_summary import ContractSummary
|
||||||
from src.db.models.expense_item import ExpenseItem
|
from src.db.models.expense_item import ExpenseItem
|
||||||
from src.db.models.form3_phase import Form3Phase
|
|
||||||
from src.db.models.form_limit import FormLimit
|
from src.db.models.form_limit import FormLimit
|
||||||
from src.db.models.form_phase import FormPhase
|
from src.db.models.form_phase import FormPhase
|
||||||
from src.db.models.form_type import FormType
|
from src.db.models.form_type import FormType
|
||||||
|
|||||||
@ -1,27 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from sqlalchemy import ARRAY, CheckConstraint, DateTime, ForeignKey, Index, Integer, String, Text, text
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
|
||||||
|
|
||||||
from src.db.base import Base
|
|
||||||
|
|
||||||
|
|
||||||
class Form3Phase(Base):
|
|
||||||
__tablename__ = "form3_phase"
|
|
||||||
__table_args__ = (
|
|
||||||
CheckConstraint(text("cardinality(column_keys) > 0"), name="chk_v3_form3_phase_columns"),
|
|
||||||
CheckConstraint(text("role != 'ADMIN'"), name="chk_v3_form3_phase_no_admin"),
|
|
||||||
CheckConstraint(text("closes_at > opens_at"), name="chk_v3_form3_phase_window"),
|
|
||||||
Index("ix_v3_form3_phase_active", "rf_project_report_id", "role", "opens_at", "closes_at"),
|
|
||||||
Index("ix_v3_form3_phase_columns_gin", "column_keys", postgresql_using="gin"),
|
|
||||||
{"schema": "v3"},
|
|
||||||
)
|
|
||||||
|
|
||||||
rf_project_report_id: Mapped[int] = mapped_column(
|
|
||||||
Integer, ForeignKey("v3.rf_project_report.id", ondelete="CASCADE"), primary_key=True
|
|
||||||
)
|
|
||||||
phase_code: Mapped[str] = mapped_column(String, primary_key=True)
|
|
||||||
role: Mapped[str] = mapped_column(String, ForeignKey("v3.role.code"))
|
|
||||||
column_keys: Mapped[list[str]] = mapped_column(ARRAY(Text))
|
|
||||||
opens_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
|
||||||
closes_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
|
||||||
@ -233,10 +233,6 @@ class OrgUnitBaseSchema(BaseModel):
|
|||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|
||||||
class OrgUnitCreateSchema(OrgUnitBaseSchema):
|
|
||||||
title: str = Field(..., min_length=1)
|
|
||||||
|
|
||||||
|
|
||||||
class OrgUnitUpdateSchema(OrgUnitBaseSchema):
|
class OrgUnitUpdateSchema(OrgUnitBaseSchema):
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
is_ssp: Optional[bool] = None
|
is_ssp: Optional[bool] = None
|
||||||
@ -362,7 +358,7 @@ class AddProjectBody(BaseModel):
|
|||||||
max_length=30,
|
max_length=30,
|
||||||
pattern=r"^[^+\-\/\\=&*\s]{1,30}$",
|
pattern=r"^[^+\-\/\\=&*\s]{1,30}$",
|
||||||
)
|
)
|
||||||
year: int = Field(default_factory=lambda: datetime.now().year)
|
year: int
|
||||||
branch_id: int
|
branch_id: int
|
||||||
level: Literal["project", "program"] = "project"
|
level: Literal["project", "program"] = "project"
|
||||||
parent_id: Optional[int] = None
|
parent_id: Optional[int] = None
|
||||||
@ -374,10 +370,6 @@ class AddProjectBody(BaseModel):
|
|||||||
staff_count: Optional[int] = None
|
staff_count: Optional[int] = None
|
||||||
total_area: Optional[float] = None
|
total_area: Optional[float] = None
|
||||||
|
|
||||||
@field_validator("year", mode="before")
|
|
||||||
@classmethod
|
|
||||||
def optional_year(cls, v: Any) -> Any:
|
|
||||||
return v or datetime.now().year
|
|
||||||
|
|
||||||
class AddLineSchema(BaseModel):
|
class AddLineSchema(BaseModel):
|
||||||
expense_item_id: Optional[int] = None
|
expense_item_id: Optional[int] = None
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class BudgetLineRepository:
|
|||||||
query = select(BudgetLine).where(BudgetLine.id == budget_line_id).limit(1)
|
query = select(BudgetLine).where(BudgetLine.id == budget_line_id).limit(1)
|
||||||
return (await self.db.execute(query)).scalar_one_or_none()
|
return (await self.db.execute(query)).scalar_one_or_none()
|
||||||
|
|
||||||
async def get_list(self, budget_line_ids: list[int]) -> list[BudgetLine]:
|
async def get_list(self, budget_line_ids: list[int]) -> BudgetLine | None:
|
||||||
query = select(BudgetLine).where(BudgetLine.id.in_(budget_line_ids))
|
query = select(BudgetLine).where(BudgetLine.id.in_(budget_line_ids))
|
||||||
return (await self.db.execute(query)).scalars().all()
|
return (await self.db.execute(query)).scalars().all()
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class ProjectRepository:
|
|||||||
self.db = db
|
self.db = db
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _serialize_project(project: Project, org_unit_name: str | None, report_count: int, years: list[InterruptedError] | None = None) -> dict:
|
def _serialize_project(project: Project, org_unit_name: str | None, report_count: int) -> dict:
|
||||||
return {
|
return {
|
||||||
"id": project.id,
|
"id": project.id,
|
||||||
"name": project.name,
|
"name": project.name,
|
||||||
@ -29,7 +29,6 @@ class ProjectRepository:
|
|||||||
"org_unit_id": project.org_unit_id,
|
"org_unit_id": project.org_unit_id,
|
||||||
"org_unit_name": org_unit_name,
|
"org_unit_name": org_unit_name,
|
||||||
"report_count": int(report_count),
|
"report_count": int(report_count),
|
||||||
"years": years,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -67,19 +66,11 @@ class ProjectRepository:
|
|||||||
.scalar_subquery()
|
.scalar_subquery()
|
||||||
)
|
)
|
||||||
|
|
||||||
report_years_subq = (
|
|
||||||
select(RfProjectReport.year)
|
|
||||||
.where(RfProjectReport.project_id == Project.id)
|
|
||||||
.distinct()
|
|
||||||
.scalar_subquery()
|
|
||||||
)
|
|
||||||
|
|
||||||
query = (
|
query = (
|
||||||
select(
|
select(
|
||||||
Project,
|
Project,
|
||||||
OrgUnit.title.label("org_unit_name"),
|
OrgUnit.title.label("org_unit_name"),
|
||||||
report_count_subq.label("report_count"),
|
report_count_subq.label("report_count"),
|
||||||
func.array(report_years_subq).label("years"),
|
|
||||||
)
|
)
|
||||||
.outerjoin(OrgUnit, OrgUnit.id == Project.org_unit_id)
|
.outerjoin(OrgUnit, OrgUnit.id == Project.org_unit_id)
|
||||||
.order_by(Project.id)
|
.order_by(Project.id)
|
||||||
@ -91,7 +82,7 @@ class ProjectRepository:
|
|||||||
query = query.limit(limit)
|
query = query.limit(limit)
|
||||||
|
|
||||||
rows = (await self.db.execute(query)).all()
|
rows = (await self.db.execute(query)).all()
|
||||||
payload = [self._serialize_project(row[0], row[1], row[2], row[3]) for row in rows]
|
payload = [self._serialize_project(row[0], row[1], row[2]) for row in rows]
|
||||||
if not with_count:
|
if not with_count:
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
@ -110,18 +101,11 @@ class ProjectRepository:
|
|||||||
.where(RfProjectReport.project_id == Project.id)
|
.where(RfProjectReport.project_id == Project.id)
|
||||||
.scalar_subquery()
|
.scalar_subquery()
|
||||||
)
|
)
|
||||||
report_years_subq = (
|
|
||||||
select(RfProjectReport.year)
|
|
||||||
.where(RfProjectReport.project_id == Project.id)
|
|
||||||
.distinct()
|
|
||||||
.scalar_subquery()
|
|
||||||
)
|
|
||||||
query = (
|
query = (
|
||||||
select(
|
select(
|
||||||
Project,
|
Project,
|
||||||
OrgUnit.title.label("org_unit_name"),
|
OrgUnit.title.label("org_unit_name"),
|
||||||
report_count_subq.label("report_count"),
|
report_count_subq.label("report_count"),
|
||||||
func.array(report_years_subq).label("years"),
|
|
||||||
)
|
)
|
||||||
.outerjoin(OrgUnit, OrgUnit.id == Project.org_unit_id)
|
.outerjoin(OrgUnit, OrgUnit.id == Project.org_unit_id)
|
||||||
.where(Project.id == project_id)
|
.where(Project.id == project_id)
|
||||||
@ -133,7 +117,7 @@ class ProjectRepository:
|
|||||||
row = (await self.db.execute(query)).first()
|
row = (await self.db.execute(query)).first()
|
||||||
if not row:
|
if not row:
|
||||||
return None
|
return None
|
||||||
return self._serialize_project(row[0], row[1], row[2], row[3])
|
return self._serialize_project(row[0], row[1], row[2])
|
||||||
|
|
||||||
async def get_reports(self, project_id: int) -> list[dict]:
|
async def get_reports(self, project_id: int) -> list[dict]:
|
||||||
line_count_subq = (
|
line_count_subq = (
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from src.db.models.rf_project_report_line import RfProjectReportLine
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RfProjectReportLineRepository:
|
|
||||||
def __init__(self, db: AsyncSession):
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
async def get(self, line_id: int) -> RfProjectReportLine | None:
|
|
||||||
query = select(
|
|
||||||
RfProjectReportLine
|
|
||||||
).where(
|
|
||||||
RfProjectReportLine.id == line_id
|
|
||||||
).limit(1)
|
|
||||||
return (await self.db.execute(query)).scalar_one_or_none()
|
|
||||||
|
|
||||||
async def get_list(self, line_ids: list[int]) -> list[RfProjectReportLine]:
|
|
||||||
query = select(
|
|
||||||
RfProjectReportLine
|
|
||||||
).where(
|
|
||||||
RfProjectReportLine.id.in_(line_ids)
|
|
||||||
)
|
|
||||||
return (await self.db.execute(query)).scalars().all()
|
|
||||||
|
|
||||||
async def get_ids(
|
|
||||||
self,
|
|
||||||
project_report_id: int,
|
|
||||||
) -> list[int]:
|
|
||||||
|
|
||||||
query = select(
|
|
||||||
RfProjectReportLine.id
|
|
||||||
).where(
|
|
||||||
RfProjectReportLine.rf_project_report_id == project_report_id
|
|
||||||
)
|
|
||||||
return (await self.db.execute(query)).scalars().all()
|
|
||||||
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from src.db.models.rf_project_report import RfProjectReport
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RfProjectReportRepository:
|
|
||||||
def __init__(self, db: AsyncSession):
|
|
||||||
self.db = db
|
|
||||||
|
|
||||||
async def get(
|
|
||||||
self,
|
|
||||||
report_id: int | None = None,
|
|
||||||
year: int | None = None,
|
|
||||||
report_type: str | None = None,
|
|
||||||
project_id: int | None = None,
|
|
||||||
) -> RfProjectReport | None:
|
|
||||||
assert report_id is not None or all((el is not None for el in (year, report_type, project_id)))
|
|
||||||
query = select(
|
|
||||||
RfProjectReport
|
|
||||||
)
|
|
||||||
if report_id is not None:
|
|
||||||
query = query.where(RfProjectReport.id == report_id)
|
|
||||||
else:
|
|
||||||
query = query.where(
|
|
||||||
RfProjectReport.year == year,
|
|
||||||
RfProjectReport.report_type == report_type,
|
|
||||||
RfProjectReport.project_id == project_id
|
|
||||||
)
|
|
||||||
|
|
||||||
query = query.limit(1)
|
|
||||||
return (await self.db.execute(query)).scalar_one_or_none()
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from src.db.models.rf_project_report_line import RfProjectReportLine
|
|
||||||
from src.repository.rf_project_report_line_repository import RfProjectReportLineRepository
|
|
||||||
from src.db.models.app_user import AppUser
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RfProjectReportLineService:
|
|
||||||
def __init__(self, db: AsyncSession):
|
|
||||||
self.db = db
|
|
||||||
self.prl_repo = RfProjectReportLineRepository(db)
|
|
||||||
|
|
||||||
async def get(
|
|
||||||
self,
|
|
||||||
user: AppUser,
|
|
||||||
line_id: int,
|
|
||||||
) -> RfProjectReportLine | None:
|
|
||||||
return await self.prl_repo.get(line_id=line_id)
|
|
||||||
|
|
||||||
async def get_list(
|
|
||||||
self,
|
|
||||||
user: AppUser,
|
|
||||||
line_ids: list[int],
|
|
||||||
) -> list[RfProjectReportLine]:
|
|
||||||
return await self.prl_repo.get_list(line_ids=line_ids)
|
|
||||||
|
|
||||||
async def get_ids(
|
|
||||||
self,
|
|
||||||
project_report_id: int,
|
|
||||||
) -> list[int]:
|
|
||||||
return await self.prl_repo.get_ids(project_report_id=project_report_id)
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
|
||||||
|
|
||||||
from src.db.models.rf_project_report import RfProjectReport
|
|
||||||
from src.repository.rf_project_report_repository import RfProjectReportRepository
|
|
||||||
from src.db.models.rf_project_report_line import RfProjectReportLine
|
|
||||||
from src.repository.rf_project_report_line_repository import RfProjectReportLineRepository
|
|
||||||
from src.db.models.app_user import AppUser
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RfProjectReportService:
|
|
||||||
def __init__(self, db: AsyncSession):
|
|
||||||
self.db = db
|
|
||||||
self.pr_repo = RfProjectReportRepository(db)
|
|
||||||
|
|
||||||
async def get(
|
|
||||||
self,
|
|
||||||
user: AppUser| None = None,
|
|
||||||
report_id: int | None = None,
|
|
||||||
year: int | None = None,
|
|
||||||
report_type: str | None = None,
|
|
||||||
project_id: int | None = None,
|
|
||||||
) -> RfProjectReport | None:
|
|
||||||
return await self.pr_repo.get(
|
|
||||||
report_id=report_id,
|
|
||||||
year=year,
|
|
||||||
report_type=report_type,
|
|
||||||
project_id=project_id,
|
|
||||||
)
|
|
||||||
@ -11,7 +11,6 @@ from src.repository.budget_form_repository import BudgetFormRepository
|
|||||||
from src.core.errors import AccessDeniedException, ValidationException
|
from src.core.errors import AccessDeniedException, ValidationException
|
||||||
from src.db.models import AppUser, UserRoleEnum, Vsp
|
from src.db.models import AppUser, UserRoleEnum, Vsp
|
||||||
from src.domain.schemas import VSPCreate, VSPUpdate
|
from src.domain.schemas import VSPCreate, VSPUpdate
|
||||||
from src.repository.org_unit_repository import OrgUnitRepository
|
|
||||||
from src.repository.user_repository import UserRepository
|
from src.repository.user_repository import UserRepository
|
||||||
from src.repository.vsp_repository import VSPRepository
|
from src.repository.vsp_repository import VSPRepository
|
||||||
|
|
||||||
@ -32,7 +31,6 @@ class VSPService:
|
|||||||
self.db = db
|
self.db = db
|
||||||
self.vsp_repo = VSPRepository(db)
|
self.vsp_repo = VSPRepository(db)
|
||||||
self.form_repo = BudgetFormRepository(db)
|
self.form_repo = BudgetFormRepository(db)
|
||||||
self.org_unit_repo = OrgUnitRepository(db)
|
|
||||||
self.user_repo = UserRepository(db)
|
self.user_repo = UserRepository(db)
|
||||||
|
|
||||||
async def get(self, vsp_id: int, user: AppUser, load_org_unit: bool = False) -> Optional[Vsp]:
|
async def get(self, vsp_id: int, user: AppUser, load_org_unit: bool = False) -> Optional[Vsp]:
|
||||||
@ -130,7 +128,6 @@ class VSPService:
|
|||||||
async def create(self, payload: VSPCreate, user: AppUser, load_org_unit: bool = False) -> Vsp:
|
async def create(self, payload: VSPCreate, user: AppUser, load_org_unit: bool = False) -> Vsp:
|
||||||
if not self._can_modify(user):
|
if not self._can_modify(user):
|
||||||
raise AccessDeniedException()
|
raise AccessDeniedException()
|
||||||
await self._ensure_active_branch(payload.branch_id)
|
|
||||||
try:
|
try:
|
||||||
created = await self.vsp_repo.create(payload=payload, created_by=user.id, load_org_unit=load_org_unit)
|
created = await self.vsp_repo.create(payload=payload, created_by=user.id, load_org_unit=load_org_unit)
|
||||||
if created.closed_at is not None and created.closed_at <= date.today():
|
if created.closed_at is not None and created.closed_at <= date.today():
|
||||||
@ -248,9 +245,6 @@ class VSPService:
|
|||||||
if not data:
|
if not data:
|
||||||
return vsp
|
return vsp
|
||||||
|
|
||||||
if "branch_id" in data:
|
|
||||||
await self._ensure_active_branch(data["branch_id"])
|
|
||||||
|
|
||||||
if user.role_id != UserRoleEnum.ADMIN:
|
if user.role_id != UserRoleEnum.ADMIN:
|
||||||
ssp_ids = await self._get_scoped_ssp_ids(user=user)
|
ssp_ids = await self._get_scoped_ssp_ids(user=user)
|
||||||
if not ssp_ids or vsp.branch_id not in set(ssp_ids):
|
if not ssp_ids or vsp.branch_id not in set(ssp_ids):
|
||||||
@ -297,10 +291,3 @@ class VSPService:
|
|||||||
return
|
return
|
||||||
await self.vsp_repo.deactivate_by_close_date(as_of=today)
|
await self.vsp_repo.deactivate_by_close_date(as_of=today)
|
||||||
_last_info_close_sync_run = today
|
_last_info_close_sync_run = today
|
||||||
|
|
||||||
async def _ensure_active_branch(self, branch_id: int | None) -> None:
|
|
||||||
if branch_id is None:
|
|
||||||
return
|
|
||||||
org_unit = await self.org_unit_repo.get(org_unit_id=branch_id)
|
|
||||||
if org_unit is None or not org_unit.is_active:
|
|
||||||
raise ValidationException(description="Указан неактивный или отсутствующий ССП/РФ")
|
|
||||||
|
|||||||
@ -48,31 +48,6 @@ def _add_line(client, admin_tokens, auth_headers, project_id: int) -> int:
|
|||||||
pytest.skip("Не удалось подобрать expense_item_id для add_form3_line в текущей БД")
|
pytest.skip("Не удалось подобрать expense_item_id для add_form3_line в текущей БД")
|
||||||
|
|
||||||
|
|
||||||
def test_create_project_smoke(client, admin_tokens, auth_headers):
|
|
||||||
name = f"PT_{uuid.uuid4().hex[:10]}"
|
|
||||||
response = client.post(
|
|
||||||
"/api/v1/projects",
|
|
||||||
json={"name": name, "year": None, "branch_id": 1},
|
|
||||||
headers=auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
payload = response.json()
|
|
||||||
assert "project_id" in payload
|
|
||||||
assert (project_id := payload["project_id"]) is not None
|
|
||||||
response = client.get(
|
|
||||||
f"/api/v1/projects/{project_id}",
|
|
||||||
headers=auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
payload = response.json()
|
|
||||||
assert "result" in payload
|
|
||||||
assert "id" in payload["result"]
|
|
||||||
assert payload["result"]["id"] == project_id
|
|
||||||
assert "years" in payload["result"]
|
|
||||||
assert isinstance(payload["result"]["years"], list)
|
|
||||||
assert len(payload["result"]["years"]) == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_projects_write_smoke(client, admin_tokens, auth_headers):
|
def test_projects_write_smoke(client, admin_tokens, auth_headers):
|
||||||
project_id, name = _create_project(client, admin_tokens, auth_headers)
|
project_id, name = _create_project(client, admin_tokens, auth_headers)
|
||||||
|
|
||||||
|
|||||||
@ -150,36 +150,6 @@ def test_vsp_create_forbidden_for_executor(client, isp_tokens, auth_headers):
|
|||||||
assert "message" in payload
|
assert "message" in payload
|
||||||
|
|
||||||
|
|
||||||
def test_vsp_create_with_inactive_ssp_fails(client, admin_tokens, auth_headers):
|
|
||||||
created_org = client.post(
|
|
||||||
"/api/v1/org-unit/",
|
|
||||||
json={"title": f"Inactive SSP {uuid.uuid4().hex[:6]}", "is_ssp": True},
|
|
||||||
headers=auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert created_org.status_code == 200
|
|
||||||
org_unit_id = created_org.json()["result"]["id"]
|
|
||||||
|
|
||||||
deactivate = client.patch(
|
|
||||||
f"/api/v1/org-unit/{org_unit_id}",
|
|
||||||
json={"is_active": False},
|
|
||||||
headers=auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert deactivate.status_code == 200
|
|
||||||
|
|
||||||
response = client.post(
|
|
||||||
"/api/v1/dict/info",
|
|
||||||
json={
|
|
||||||
"registration_number": _unique_reg_number(),
|
|
||||||
"address": "Should Fail",
|
|
||||||
"ssp_id": org_unit_id,
|
|
||||||
},
|
|
||||||
headers=auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert response.status_code == 400
|
|
||||||
payload = response.json()
|
|
||||||
assert "message" in payload
|
|
||||||
|
|
||||||
|
|
||||||
def test_vsp_update_smoke(client, admin_tokens, auth_headers):
|
def test_vsp_update_smoke(client, admin_tokens, auth_headers):
|
||||||
reg_number = _unique_reg_number()
|
reg_number = _unique_reg_number()
|
||||||
created = client.post(
|
created = client.post(
|
||||||
@ -225,33 +195,7 @@ def test_vsp_delete_smoke(client, admin_tokens, auth_headers):
|
|||||||
json={
|
json={
|
||||||
"registration_number": reg_number,
|
"registration_number": reg_number,
|
||||||
"address": "To Be Deleted",
|
"address": "To Be Deleted",
|
||||||
"ssp_id": 2,
|
"ssp_id": 1,
|
||||||
},
|
|
||||||
headers=auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert created.status_code == 201
|
|
||||||
vsp_id = created.json()["result"]["id"]
|
|
||||||
|
|
||||||
response = client.delete(
|
|
||||||
f"/api/v1/dict/info/{vsp_id}",
|
|
||||||
headers=auth_headers(admin_tokens),
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
payload = response.json()
|
|
||||||
assert payload["success"] is True
|
|
||||||
assert payload["message"] == "Запись INFO удалена"
|
|
||||||
|
|
||||||
|
|
||||||
def test_vsp_delete_with_close_date_smoke(client, admin_tokens, auth_headers):
|
|
||||||
reg_number = _unique_reg_number()
|
|
||||||
created = client.post(
|
|
||||||
"/api/v1/dict/info",
|
|
||||||
json={
|
|
||||||
"registration_number": reg_number,
|
|
||||||
"address": "To Be Deleted with close_date",
|
|
||||||
"ssp_id": 2,
|
|
||||||
"open_date": "2026-01-01",
|
|
||||||
"close_date": "2030-01-01",
|
|
||||||
},
|
},
|
||||||
headers=auth_headers(admin_tokens),
|
headers=auth_headers(admin_tokens),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -26,8 +26,7 @@ SELECT setval('v3.budget_line_id_seq', 1);
|
|||||||
INSERT INTO v3.form_phase (budget_form_id,sheet,phase_code,"role",column_keys,opens_at,closes_at) VALUES
|
INSERT INTO v3.form_phase (budget_form_id,sheet,phase_code,"role",column_keys,opens_at,closes_at) VALUES
|
||||||
(1,'AHR','test','DFIP','{{plan.q1}}','2026-05-05 03:00:00+03','2026-06-06 03:00:00+03');
|
(1,'AHR','test','DFIP','{{plan.q1}}','2026-05-05 03:00:00+03','2026-06-06 03:00:00+03');
|
||||||
|
|
||||||
INSERT INTO v3.vsp (id,branch_id,reg_number,address,format,opened_at,placement_type,staff_count,total_area,closed_at,is_active,updated_at,is_deleted,created_at,created_by,system_code,updated_by,vsp_type,notes,rent_contract_num,rent_end_date) VALUES
|
INSERT INTO v3.vsp (id,branch_id,reg_number,address,format,opened_at,placement_type,staff_count,total_area,closed_at,is_active,updated_at,is_deleted,created_at,created_by,system_code,updated_by,vsp_type,notes,location_form,numbers,rent_contract_num,rent_end_date) VALUES
|
||||||
(1,2,'Тестовый всп 1','Тестовая 12','укукк','2026-05-07','ывс',2026,230,'2026-06-16',false,'2026-06-18 15:46:38.611903',false,'2026-06-04 14:36:59.737727',1,'1233',91,'','','',NULL),
|
(1,2,'Тестовый всп 1','Тестовая 12','укукк','2026-05-07','ывс',2026,230,'2026-06-16',false,'2026-06-18 15:46:38.611903',false,'2026-06-04 14:36:59.737727',1,'1233',91,'','','встроенное помещение',NULL,'',NULL),
|
||||||
(2,2,'Test12 -----','Тестовая 125','формат 15 ','2026-06-08',NULL,NULL,58,NULL,true,'2026-06-16 18:53:17.986651',false,'2026-06-09 09:04:33.29858',91,'123-1234',91,'субаренда','Работает ','12-45','2026-10-02'),
|
(2,2,'Test12 -----','Тестовая 125','формат 15 ','2026-06-08',NULL,NULL,58,NULL,true,'2026-06-16 18:53:17.986651',false,'2026-06-09 09:04:33.29858',91,'123-1234',91,'субаренда','Работает ','аренда',155,'12-45','2026-10-02');
|
||||||
(3,2,'3434','победы 12','формат','2026-06-01','субаренда', NULL,34,NULL,true,'2026-07-01 13:32:35.327097',false,'2026-06-29 15:53:50.236062',91,'3434',NULL,'2','примечание','567','2026-07-01');
|
SELECT setval('v3.vsp_id_seq', 2);
|
||||||
SELECT setval('v3.vsp_id_seq', 3);
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export const DictVspApi = {
|
|||||||
responseType: "blob",
|
responseType: "blob",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getDropdownVsp(params) {
|
getDropdownVsp(params){
|
||||||
return api.get(`/dict/info/dropdown`, { params }).then((r) => r.data);
|
return api.get(`/dict/info/dropdown`, params).then((r) => r.data);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,34 +0,0 @@
|
|||||||
import api from "./client";
|
|
||||||
|
|
||||||
export const ProjectsApi = {
|
|
||||||
list: (params) => {
|
|
||||||
return api.get(`/projects`, { params }).then((r) => r.data);
|
|
||||||
},
|
|
||||||
|
|
||||||
get: (id) => {
|
|
||||||
return api.get(`/projects/${id}`).then((r) => r.data);
|
|
||||||
},
|
|
||||||
|
|
||||||
create: (data) => {
|
|
||||||
return api.post("/projects", data).then((r) => r.data);
|
|
||||||
},
|
|
||||||
|
|
||||||
update: (id, data) => {
|
|
||||||
return api.patch(`/projects/${id}`, data).then((r) => r.data);
|
|
||||||
},
|
|
||||||
|
|
||||||
delete: (id) => {
|
|
||||||
return api.delete(`/projects/${id}`).then((r) => r.data);
|
|
||||||
},
|
|
||||||
|
|
||||||
getTableData: (id, sheetName, year) => {
|
|
||||||
return api
|
|
||||||
.get(`/projects/${id}/report/${year}/${sheetName}`)
|
|
||||||
.then((r) => r.data);
|
|
||||||
},
|
|
||||||
export: (id, year, sheetName) => {
|
|
||||||
return api.get(`/export/project/${id}/report/${year}/${sheetName}`, {
|
|
||||||
responseType: "blob",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -17,7 +17,6 @@ import { CircularProgress } from '@mui/material';
|
|||||||
import NewTablePage from '../pages/NewTablePage.jsx';
|
import NewTablePage from '../pages/NewTablePage.jsx';
|
||||||
import TablesTest from '../pages/TablesTest.jsx';
|
import TablesTest from '../pages/TablesTest.jsx';
|
||||||
import NewFormTablePage from '../pages/NewFormTablePage.jsx'
|
import NewFormTablePage from '../pages/NewFormTablePage.jsx'
|
||||||
import ProjectsPage from '../pages/ProjectsPage/ProjectsPage.jsx';
|
|
||||||
|
|
||||||
export const PrivateRoute = () => {
|
export const PrivateRoute = () => {
|
||||||
const { isAuthenticated, loading } = useAuth();
|
const { isAuthenticated, loading } = useAuth();
|
||||||
@ -38,10 +37,8 @@ export const AppRoutes = () => {
|
|||||||
<Route element={<PrivateRoute />}>
|
<Route element={<PrivateRoute />}>
|
||||||
<Route path="/" element={<TasksPage />} />
|
<Route path="/" element={<TasksPage />} />
|
||||||
<Route path="/tasks" element={<TasksPage />} />
|
<Route path="/tasks" element={<TasksPage />} />
|
||||||
<Route path="/projects" element={<ProjectsPage />}/>
|
|
||||||
<Route path="/forms" element={<FormsPage />} />
|
<Route path="/forms" element={<FormsPage />} />
|
||||||
<Route path="/task/:taskId" element={<TaskPage />} />
|
<Route path="/task/:taskId" element={<TaskPage />} />
|
||||||
<Route path="/project/:projectId" element={<TaskPage />} />
|
|
||||||
<Route path="/table/:tableId" element={<TablePage />} />
|
<Route path="/table/:tableId" element={<TablePage />} />
|
||||||
<Route path="/table-temp/:tableId" element={<TableTempPage />} />
|
<Route path="/table-temp/:tableId" element={<TableTempPage />} />
|
||||||
<Route path="/admin_panel/users" element={<UsersPage />} />
|
<Route path="/admin_panel/users" element={<UsersPage />} />
|
||||||
@ -52,7 +49,7 @@ export const AppRoutes = () => {
|
|||||||
<Route path="/new-table" element={<NewTablePage />} />
|
<Route path="/new-table" element={<NewTablePage />} />
|
||||||
<Route path="/tables-new" element={<TablesTest />} />
|
<Route path="/tables-new" element={<TablesTest />} />
|
||||||
<Route path="/tables/:form/:sheetName" element={<NewFormTablePage />} />
|
<Route path="/tables/:form/:sheetName" element={<NewFormTablePage />} />
|
||||||
<Route path="/table/form/:formId/form-type/:formType/:sheetName/:direction/:year" element={<NewFormTablePage />} />
|
<Route path="/table/form/:formId/form-type/:formType/:sheetName/:direction" element={<NewFormTablePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { NavLink } from 'react-router-dom';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { TasksSvg, AdminPanelSvg, DictSvg } from '../common/icons/icons';
|
import { TasksSvg, AdminPanelSvg, DictSvg } from '../common/icons/icons';
|
||||||
import { useAuth } from '../../app/context/AuthProvider';
|
import { useAuth } from '../../app/context/AuthProvider';
|
||||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
import { ROLES_NAME_ID } from '../../constants';
|
||||||
|
|
||||||
const Switch = styled.div`
|
const Switch = styled.div`
|
||||||
margin-left: 1rem;
|
margin-left: 1rem;
|
||||||
|
|||||||
@ -31,13 +31,12 @@ const LockBadge = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Функция для подсветки текста
|
// Функция для подсветки текста
|
||||||
const highlightText = (text, searchQueries) => {
|
const highlightText = (text, searchQuery) => {
|
||||||
if (!text) return text;
|
if (!searchQuery || !text) return text;
|
||||||
const cleanSearchQueries = searchQueries.filter(q => (q !== undefined && q !== ''));
|
|
||||||
if (cleanSearchQueries.length == 0) return text;
|
const escapedQuery = searchQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
const regex = new RegExp(`(${escapedQuery})`, 'gi');
|
||||||
|
|
||||||
const escapedQueries = cleanSearchQueries.map(query => query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
||||||
const regex = new RegExp(`(${ escapedQueries.join('|')})`, 'gi');
|
|
||||||
const parts = text.split(regex);
|
const parts = text.split(regex);
|
||||||
|
|
||||||
return parts.map((part, index) =>
|
return parts.map((part, index) =>
|
||||||
@ -90,7 +89,7 @@ const formatNumber = (value, asInteger = false) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const Cell = React.memo(({ cell, row, column, onClick, globalFilter, columnFilter, backgroundColor, color, isEditable }) => {
|
const Cell = React.memo(({ cell, row, column, onClick, globalFilter, backgroundColor, color, isEditable }) => {
|
||||||
const { lockedCells } = useRealtime();
|
const { lockedCells } = useRealtime();
|
||||||
const cellKey = `${row.id}_${column.id}`;
|
const cellKey = `${row.id}_${column.id}`;
|
||||||
|
|
||||||
@ -106,10 +105,9 @@ const Cell = React.memo(({ cell, row, column, onClick, globalFilter, columnFilte
|
|||||||
}
|
}
|
||||||
|
|
||||||
const highlightedContent = useMemo(() => {
|
const highlightedContent = useMemo(() => {
|
||||||
return highlightText(textValue, [globalFilter, columnFilter]);
|
return highlightText(textValue, globalFilter);
|
||||||
}, [textValue, globalFilter]);
|
}, [textValue, globalFilter]);
|
||||||
|
|
||||||
|
|
||||||
// Базовые стили для заблокированной ячейки
|
// Базовые стили для заблокированной ячейки
|
||||||
const lockedStyles = isLocked ? {
|
const lockedStyles = isLocked ? {
|
||||||
border: '2px solid #e0e0e0',
|
border: '2px solid #e0e0e0',
|
||||||
|
|||||||
@ -2,18 +2,15 @@
|
|||||||
import { useEffect, useRef, useLayoutEffect, useState } from 'react';
|
import { useEffect, useRef, useLayoutEffect, useState } from 'react';
|
||||||
import { isFormula, extractFormula, calculateFormula, formatNumber } from '../../utils/formulaUtils';
|
import { isFormula, extractFormula, calculateFormula, formatNumber } from '../../utils/formulaUtils';
|
||||||
import { saveToStorage, loadFromStorage } from '../../utils/localStorageUtils';
|
import { saveToStorage, loadFromStorage } from '../../utils/localStorageUtils';
|
||||||
import { useRealtime } from '../../contexts/RealtimeContext';
|
|
||||||
|
|
||||||
const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId, table }) => {
|
|
||||||
const { endEditing: contextEndEditing } = useRealtime();
|
|
||||||
|
|
||||||
|
const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId }) => {
|
||||||
const [curValue, setCurValue] = useState(value);
|
const [curValue, setCurValue] = useState(value);
|
||||||
const [displayValue, setDisplayValue] = useState(value);
|
const [displayValue, setDisplayValue] = useState(value);
|
||||||
const [isFormulaMode, setIsFormulaMode] = useState(false);
|
const [isFormulaMode, setIsFormulaMode] = useState(false);
|
||||||
|
|
||||||
// Ключ для хранения формул в localStorage
|
// Ключ для хранения формул в localStorage
|
||||||
const storageKey = `formula_${tableId}_${cellId}`;
|
const storageKey = `formula_${tableId}_${cellId}`;
|
||||||
|
|
||||||
const trFrag = refCell.current.offsetParent.offsetParent;
|
const trFrag = refCell.current.offsetParent.offsetParent;
|
||||||
const tdFrag = refCell.current.offsetParent;
|
const tdFrag = refCell.current.offsetParent;
|
||||||
const left = tdFrag.offsetLeft;
|
const left = tdFrag.offsetLeft;
|
||||||
@ -72,6 +69,7 @@ const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId, table }
|
|||||||
if (isFormula(curValue)) {
|
if (isFormula(curValue)) {
|
||||||
// Сохраняем формулу в localStorage
|
// Сохраняем формулу в localStorage
|
||||||
saveToStorage(storageKey, curValue);
|
saveToStorage(storageKey, curValue);
|
||||||
|
|
||||||
// Вычисляем и сохраняем результат
|
// Вычисляем и сохраняем результат
|
||||||
try {
|
try {
|
||||||
const formula = extractFormula(curValue);
|
const formula = extractFormula(curValue);
|
||||||
@ -110,32 +108,6 @@ const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId, table }
|
|||||||
return displayValue;
|
return displayValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
const finishEditing = () => {
|
|
||||||
if (!disabled) {
|
|
||||||
handleSave();
|
|
||||||
}
|
|
||||||
const editingCell = table.getState().editingCell || null;
|
|
||||||
if (editingCell) {
|
|
||||||
contextEndEditing?.(editingCell.row, editingCell.column);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handleClickOutside = (event) => {
|
|
||||||
// Проверяем, был ли клик внутри элемента редактирования
|
|
||||||
const editElement = ref.current;
|
|
||||||
if (editElement && !editElement.contains(event.target)) {
|
|
||||||
finishEditing();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousedown', handleClickOutside);
|
|
||||||
};
|
|
||||||
}, [ref, finishEditing]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
@ -178,10 +150,16 @@ const EditCell = ({ refCell, onChange, disabled, value, tableId, cellId, table }
|
|||||||
}}
|
}}
|
||||||
onChange={handleChangeValue}
|
onChange={handleChangeValue}
|
||||||
value={curValue}
|
value={curValue}
|
||||||
|
onBlur={handleSave}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === 'Enter' && !e.shiftKey) {
|
if (e.key === 'Enter' && !e.shiftKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
finishEditing();
|
handleSave();
|
||||||
|
// Закрываем редактирование
|
||||||
|
if (onChange) {
|
||||||
|
// Симулируем завершение редактирования
|
||||||
|
document.activeElement?.blur();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder={isFormulaMode ? 'Введите формулу (начинается с =)' : ''}
|
placeholder={isFormulaMode ? 'Введите формулу (начинается с =)' : ''}
|
||||||
|
|||||||
@ -26,11 +26,11 @@ import { useRealtime } from './contexts/RealtimeContext';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { CircularProgress } from '@mui/material';
|
import { CircularProgress } from '@mui/material';
|
||||||
import { additionVspRowTable } from './constants/addingRowConfig';
|
import { additionVspRowTable } from './constants/addingRowConfig';
|
||||||
import { SelectVspModal } from './Modals/SelectVspModal';
|
import { SelectVspModal } from './Modals/selectVspModal';
|
||||||
import { getRowId } from './utils/rowUtils';
|
import { getRowId } from './utils/rowUtils';
|
||||||
|
|
||||||
const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
const RealtimeTable = ({ formType, formId, sheetName, direction }) => {
|
||||||
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction, formType, year);
|
const { data, setData, isLoading: isTableLoading, editingCells: dataEditingCells } = useRealtimeData(formId, sheetName, direction);
|
||||||
const [globalFilter, setGlobalFilter] = useState('');
|
const [globalFilter, setGlobalFilter] = useState('');
|
||||||
const [showColumnFilters, setShowColumnFilters] = useState(false);
|
const [showColumnFilters, setShowColumnFilters] = useState(false);
|
||||||
const [selectedColumnId, setSelectedColumnId] = useState();
|
const [selectedColumnId, setSelectedColumnId] = useState();
|
||||||
@ -63,6 +63,7 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
addRow: contextAddRow,
|
addRow: contextAddRow,
|
||||||
deleteRow: contextDeleteRow,
|
deleteRow: contextDeleteRow,
|
||||||
startEditing: contextStartEditing,
|
startEditing: contextStartEditing,
|
||||||
|
endEditing: contextEndEditing,
|
||||||
} = useRealtime();
|
} = useRealtime();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -179,6 +180,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
if (cell) {
|
if (cell) {
|
||||||
if (editingCell) return;
|
if (editingCell) return;
|
||||||
contextStartEditing?.(cell.row, cell.column);
|
contextStartEditing?.(cell.row, cell.column);
|
||||||
|
} else {
|
||||||
|
contextEndEditing?.(editingCell.row, editingCell.column);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
columnVirtualizerOptions: ({ table }) => ({
|
columnVirtualizerOptions: ({ table }) => ({
|
||||||
@ -226,12 +229,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
minHeight: '1px',
|
minHeight: '1px',
|
||||||
maxHeight: '1px',
|
maxHeight: '1px',
|
||||||
visibility: 'hidden',
|
visibility: 'hidden',
|
||||||
'& svg': {
|
|
||||||
height: '1px',
|
|
||||||
minHeight: '1px',
|
|
||||||
maxHeight: '1px',
|
|
||||||
visibility: 'hidden',
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
muiTablePaperProps: getTablePaperStyles(),
|
muiTablePaperProps: getTablePaperStyles(),
|
||||||
@ -363,8 +360,6 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
formId={formId}
|
formId={formId}
|
||||||
sheetName={sheetName}
|
sheetName={sheetName}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
year={year}
|
|
||||||
isProject={formType == 'PROJECT'}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div style={tableWrapperStyle}>
|
<div style={tableWrapperStyle}>
|
||||||
@ -425,6 +420,8 @@ const RealtimeTable = ({ formType, formId, sheetName, direction, year }) => {
|
|||||||
onClose={() => setIsOpenModalSelectVsp(false)}
|
onClose={() => setIsOpenModalSelectVsp(false)}
|
||||||
formId={formId}
|
formId={formId}
|
||||||
onSelect={handleAddVspRow}
|
onSelect={handleAddVspRow}
|
||||||
|
// vspOptions={}
|
||||||
|
// selectedVSP={}
|
||||||
title="Выбор ВСП"
|
title="Выбор ВСП"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import CollapsibleTree from '../../common/CollapsibleTree';
|
|||||||
import ZoomSlider from './ZoomSlider';
|
import ZoomSlider from './ZoomSlider';
|
||||||
import SearchComponent from '../../common/SearchComponent';
|
import SearchComponent from '../../common/SearchComponent';
|
||||||
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
import { ExportDefaultButton } from '../../common/Buttons/ButtonsActions';
|
||||||
import { exportSheet, exportSheetProject } from '../../../utils/exportFile';
|
import { exportSheet } from '../../../utils/exportForm';
|
||||||
|
|
||||||
const SettingPanel = ({
|
const SettingPanel = ({
|
||||||
selectedColumnId,
|
selectedColumnId,
|
||||||
@ -39,8 +39,6 @@ const SettingPanel = ({
|
|||||||
formId,
|
formId,
|
||||||
sheetName,
|
sheetName,
|
||||||
direction,
|
direction,
|
||||||
year,
|
|
||||||
isProject,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [isPinned, setIsPinned] = useState(false);
|
const [isPinned, setIsPinned] = useState(false);
|
||||||
const [isExporting, setIsExporting] = useState(false);
|
const [isExporting, setIsExporting] = useState(false);
|
||||||
@ -78,16 +76,7 @@ const SettingPanel = ({
|
|||||||
const handleClickDeleteFormula = () => { };
|
const handleClickDeleteFormula = () => { };
|
||||||
|
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
if (isExporting) return;
|
if (!formId || !sheetName || isExporting) return;
|
||||||
if (isProject) {
|
|
||||||
if (!formId || !sheetName || !year) return;
|
|
||||||
setIsExporting(true);
|
|
||||||
await exportSheetProject(formId, sheetName, year);
|
|
||||||
setIsExporting(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formId || !sheetName) return;
|
|
||||||
setIsExporting(true);
|
setIsExporting(true);
|
||||||
await exportSheet(formId, sheetName, direction);
|
await exportSheet(formId, sheetName, direction);
|
||||||
setIsExporting(false);
|
setIsExporting(false);
|
||||||
@ -126,26 +115,40 @@ const SettingPanel = ({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</GroupByObject>
|
</GroupByObject>
|
||||||
|
|
||||||
{!isProject &&
|
<Divider orientation="vertical" flexItem />
|
||||||
<>
|
|
||||||
<Divider orientation="vertical" flexItem />
|
|
||||||
<GroupByObject title="Строки">
|
|
||||||
<Tooltip title="Добавить строку">
|
|
||||||
<IconButton onClick={onAddRow} variant="outlined">
|
|
||||||
<Plus />
|
|
||||||
<AddLine />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip title="Удалить строку">
|
<GroupByObject title="Строки">
|
||||||
<IconButton onClick={onDeleteRow} variant="outlined">
|
<Tooltip title="Добавить строку">
|
||||||
<Minus />
|
<IconButton onClick={onAddRow} variant="outlined">
|
||||||
<RemoveLine />
|
<Plus />
|
||||||
</IconButton>
|
<AddLine />
|
||||||
</Tooltip>
|
</IconButton>
|
||||||
</GroupByObject>
|
</Tooltip>
|
||||||
</>
|
|
||||||
}
|
<Tooltip title="Удалить строку">
|
||||||
|
<IconButton onClick={onDeleteRow} variant="outlined">
|
||||||
|
<Minus />
|
||||||
|
<RemoveLine />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</GroupByObject>
|
||||||
|
|
||||||
|
{/* <Divider orientation="vertical" flexItem />
|
||||||
|
|
||||||
|
<GroupByObject title="Формулы">
|
||||||
|
<Tooltip title="Добавить формулу">
|
||||||
|
<IconButton onClick={handleClickAddFormula} variant="outlined">
|
||||||
|
<FormulaSettingPanel />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip title="Удалить формулу">
|
||||||
|
<IconButton onClick={handleClickDeleteFormula} variant="outlined">
|
||||||
|
<Minus />
|
||||||
|
<FormulaSettingPanel />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</GroupByObject> */}
|
||||||
<Divider orientation="vertical" flexItem />
|
<Divider orientation="vertical" flexItem />
|
||||||
<GroupByObject title="Столбцы">
|
<GroupByObject title="Столбцы">
|
||||||
{columns ? (
|
{columns ? (
|
||||||
|
|||||||
@ -313,10 +313,10 @@ const FilterCell = ({ column, table }) => {
|
|||||||
onChange={(e) => handleFilterChange(e.target.value)}
|
onChange={(e) => handleFilterChange(e.target.value)}
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '0.25rem 0.375rem',
|
padding: '4px 6px',
|
||||||
fontSize: '0.6875rem',
|
fontSize: '11px',
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '4px',
|
||||||
backgroundColor: 'white',
|
backgroundColor: 'white',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -330,30 +330,23 @@ const FilterCell = ({ column, table }) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
case 'range': {
|
case 'range': {
|
||||||
|
// Для диапазона можно реализовать два поля
|
||||||
const [min, max] = column.getFilterValue() || ['', ''];
|
const [min, max] = column.getFilterValue() || ['', ''];
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', gap: '0.25rem' }}> {/* 4px */}
|
<div style={{ display: 'flex', gap: '4px' }}>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="От"
|
placeholder="От"
|
||||||
value={min}
|
value={min}
|
||||||
onChange={(e) => column.setFilterValue([e.target.value, max])}
|
onChange={(e) => column.setFilterValue([e.target.value, max])}
|
||||||
style={{
|
style={{ width: '50%', padding: '4px', fontSize: '11px' }}
|
||||||
width: '50%',
|
|
||||||
padding: '0.25rem',
|
|
||||||
fontSize: '0.6875rem'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
placeholder="До"
|
placeholder="До"
|
||||||
value={max}
|
value={max}
|
||||||
onChange={(e) => column.setFilterValue([min, e.target.value])}
|
onChange={(e) => column.setFilterValue([min, e.target.value])}
|
||||||
style={{
|
style={{ width: '50%', padding: '4px', fontSize: '11px' }}
|
||||||
width: '50%',
|
|
||||||
padding: '0.25rem',
|
|
||||||
fontSize: '0.6875rem'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -368,10 +361,10 @@ const FilterCell = ({ column, table }) => {
|
|||||||
placeholder="Поиск..."
|
placeholder="Поиск..."
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
padding: '0.25rem 0.375rem',
|
padding: '4px 6px',
|
||||||
fontSize: '0.6875rem',
|
fontSize: '11px',
|
||||||
border: '1px solid #ddd',
|
border: '1px solid #ddd',
|
||||||
borderRadius: '0.25rem',
|
borderRadius: '4px',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
555
web/src/components/RealtimeTable/constants/FORM_3/LP.js
Normal file
555
web/src/components/RealtimeTable/constants/FORM_3/LP.js
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
greenColumn,
|
||||||
|
whiteColumn,
|
||||||
|
orangeColumn,
|
||||||
|
yellowColumn,
|
||||||
|
redColumn,
|
||||||
|
blueColumn,
|
||||||
|
} from '../columnColors';
|
||||||
|
export const config = {
|
||||||
|
"config":{
|
||||||
|
"colors":{
|
||||||
|
"kod_razdela_kod_razdela":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"kod_razdela_kod_razdela"
|
||||||
|
},
|
||||||
|
"id_stati_id_stati":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"id_stati_id_stati"
|
||||||
|
},
|
||||||
|
"id_gruppy_nomenklatury_id_gruppy_nomenklatury":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury"
|
||||||
|
},
|
||||||
|
"naimenovanie_naimenovanie":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"naimenovanie_naimenovanie"
|
||||||
|
},
|
||||||
|
"korrektirovka_limita_po_statyam_smety":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"korrektirovka_limita_po_statyam_smety"
|
||||||
|
},
|
||||||
|
"field_uvelichenie_smety":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_uvelichenie_smety"
|
||||||
|
},
|
||||||
|
"field_za_yanvar":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_yanvar"
|
||||||
|
},
|
||||||
|
"field_za_fevral":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_fevral"
|
||||||
|
},
|
||||||
|
"field_itogo_i_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_i_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_aprel":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_aprel"
|
||||||
|
},
|
||||||
|
"field_za_may":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_may"
|
||||||
|
},
|
||||||
|
"field_itogo_ii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_ii_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_iyul":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_iyul"
|
||||||
|
},
|
||||||
|
"field_za_avgust":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_avgust"
|
||||||
|
},
|
||||||
|
"field_itogo_iii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_iii_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_oktyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_oktyabr"
|
||||||
|
},
|
||||||
|
"field_za_noyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_noyabr"
|
||||||
|
},
|
||||||
|
"field_spod":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_spod"
|
||||||
|
},
|
||||||
|
"field_itogo_iv_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_iv_kvartal"
|
||||||
|
},
|
||||||
|
"itogo_itogo":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"itogo_itogo"
|
||||||
|
},
|
||||||
|
"fakt_zamart":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zamart"
|
||||||
|
},
|
||||||
|
"fakt_zaiyun":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zaiyun"
|
||||||
|
},
|
||||||
|
"fakt_zasentyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zasentyabr"
|
||||||
|
},
|
||||||
|
"fakt_zadekabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zadekabr"
|
||||||
|
},
|
||||||
|
"ekonomiya_pereraskhod_ekonomiya_pereraskhod":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod"
|
||||||
|
},
|
||||||
|
"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal"
|
||||||
|
},
|
||||||
|
"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal"
|
||||||
|
},
|
||||||
|
"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Код раздела",
|
||||||
|
"accessorKey":"kod_razdela",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Код раздела",
|
||||||
|
"accessorKey":"kod_razdela_kod_razdela",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"ID статьи",
|
||||||
|
"accessorKey":"id_stati",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"ID статьи",
|
||||||
|
"accessorKey":"id_stati_id_stati",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"ID группы номенклатуры",
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"ID группы номенклатуры",
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Наименование",
|
||||||
|
"accessorKey":"naimenovanie",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Наименование",
|
||||||
|
"accessorKey":"naimenovanie_naimenovanie",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Корректировка лимита",
|
||||||
|
"accessorKey":"korrektirovka_limita",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"по статьям сметы *",
|
||||||
|
"accessorKey":"korrektirovka_limita_po_statyam_smety",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"",
|
||||||
|
"accessorKey":"field",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"увеличение сметы **",
|
||||||
|
"accessorKey":"field_uvelichenie_smety",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ЯНВАРЬ",
|
||||||
|
"accessorKey":"field_za_yanvar",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ФЕВРАЛЬ",
|
||||||
|
"accessorKey":"field_za_fevral",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого I квартал",
|
||||||
|
"accessorKey":"field_itogo_i_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за АПРЕЛЬ",
|
||||||
|
"accessorKey":"field_za_aprel",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за МАЙ",
|
||||||
|
"accessorKey":"field_za_may",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого II квартал",
|
||||||
|
"accessorKey":"field_itogo_ii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ИЮЛЬ",
|
||||||
|
"accessorKey":"field_za_iyul",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за АВГУСТ",
|
||||||
|
"accessorKey":"field_za_avgust",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого III квартал",
|
||||||
|
"accessorKey":"field_itogo_iii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ОКТЯБРЬ",
|
||||||
|
"accessorKey":"field_za_oktyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за НОЯБРЬ",
|
||||||
|
"accessorKey":"field_za_noyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"СПОД",
|
||||||
|
"accessorKey":"field_spod",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого IV квартал",
|
||||||
|
"accessorKey":"field_itogo_iv_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого",
|
||||||
|
"accessorKey":"itogo",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Итого",
|
||||||
|
"accessorKey":"itogo_itogo",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Факт",
|
||||||
|
"accessorKey":"fakt",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"заМАРТ",
|
||||||
|
"accessorKey":"fakt_zamart",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заИЮНЬ",
|
||||||
|
"accessorKey":"fakt_zaiyun",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заСЕНТЯБРЬ",
|
||||||
|
"accessorKey":"fakt_zasentyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заДЕКАБРЬ",
|
||||||
|
"accessorKey":"fakt_zadekabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"экономия (+) перерасход (-)",
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"экономия (+) перерасход (-)",
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
577
web/src/components/RealtimeTable/constants/FORM_3/PROJECT.js
Normal file
577
web/src/components/RealtimeTable/constants/FORM_3/PROJECT.js
Normal file
@ -0,0 +1,577 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
greenColumn,
|
||||||
|
whiteColumn,
|
||||||
|
orangeColumn,
|
||||||
|
yellowColumn,
|
||||||
|
redColumn,
|
||||||
|
blueColumn,
|
||||||
|
} from '../columnColors';
|
||||||
|
export const config = {
|
||||||
|
"config":{
|
||||||
|
"colors":{
|
||||||
|
"kod_razdela_kod_razdela":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"kod_razdela_kod_razdela"
|
||||||
|
},
|
||||||
|
"id_stati_id_stati":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"id_stati_id_stati"
|
||||||
|
},
|
||||||
|
"id_gruppy_nomenklatury_id_gruppy_nomenklatury":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury"
|
||||||
|
},
|
||||||
|
"naimenovanie_naimenovanie":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"naimenovanie_naimenovanie"
|
||||||
|
},
|
||||||
|
"korrektirovka_plana_po_statyam_smety":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"korrektirovka_plana_po_statyam_smety"
|
||||||
|
},
|
||||||
|
"field_uvelichenie_smety":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_uvelichenie_smety"
|
||||||
|
},
|
||||||
|
"field_za_yanvar":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_yanvar"
|
||||||
|
},
|
||||||
|
"field_za_fevral":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_fevral"
|
||||||
|
},
|
||||||
|
"field_itogo_i_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_i_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_aprel":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_aprel"
|
||||||
|
},
|
||||||
|
"field_za_may":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_may"
|
||||||
|
},
|
||||||
|
"field_itogo_ii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_ii_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_iyul":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_iyul"
|
||||||
|
},
|
||||||
|
"field_za_avgust":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_avgust"
|
||||||
|
},
|
||||||
|
"field_itogo_iii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_iii_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_oktyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_oktyabr"
|
||||||
|
},
|
||||||
|
"field_za_noyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_noyabr"
|
||||||
|
},
|
||||||
|
"field_spod":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_spod"
|
||||||
|
},
|
||||||
|
"field_itogo_iv_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_iv_kvartal"
|
||||||
|
},
|
||||||
|
"itogo_korrektirovka_itogo_korrektirovka":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"itogo_korrektirovka_itogo_korrektirovka"
|
||||||
|
},
|
||||||
|
"fakt_zamart":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zamart"
|
||||||
|
},
|
||||||
|
"fakt_zaiyun":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zaiyun"
|
||||||
|
},
|
||||||
|
"fakt_zasentyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zasentyabr"
|
||||||
|
},
|
||||||
|
"fakt_zadekabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zadekabr"
|
||||||
|
},
|
||||||
|
"ekonomiya_pereraskhod_ekonomiya_pereraskhod":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod"
|
||||||
|
},
|
||||||
|
"smeta_raskhodov_po_razvitiyu_na_ii_kvartal_smeta_raskhodov_po_razvitiyu_na_ii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_ii_kvartal_smeta_raskhodov_po_razvitiyu_na_ii_kvartal"
|
||||||
|
},
|
||||||
|
"itogo_skorrek_smeta_itogo_skorrek_smeta":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"itogo_skorrek_smeta_itogo_skorrek_smeta"
|
||||||
|
},
|
||||||
|
"smeta_raskhodov_po_razvitiyu_na_iii_kvartal_smeta_raskhodov_po_razvitiyu_na_iii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iii_kvartal_smeta_raskhodov_po_razvitiyu_na_iii_kvartal"
|
||||||
|
},
|
||||||
|
"smeta_raskhodov_po_razvitiyu_na_iv_kvartal_smeta_raskhodov_po_razvitiyu_na_iv_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iv_kvartal_smeta_raskhodov_po_razvitiyu_na_iv_kvartal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Код раздела",
|
||||||
|
"accessorKey":"kod_razdela",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Код раздела",
|
||||||
|
"accessorKey":"kod_razdela_kod_razdela",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"ID статьи",
|
||||||
|
"accessorKey":"id_stati",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"ID статьи",
|
||||||
|
"accessorKey":"id_stati_id_stati",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"ID группы номенклатуры",
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"ID группы номенклатуры",
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Наименование",
|
||||||
|
"accessorKey":"naimenovanie",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Наименование",
|
||||||
|
"accessorKey":"naimenovanie_naimenovanie",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Корректировка плана",
|
||||||
|
"accessorKey":"korrektirovka_plana",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"по статьям сметы *",
|
||||||
|
"accessorKey":"korrektirovka_plana_po_statyam_smety",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"",
|
||||||
|
"accessorKey":"field",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"увеличение сметы **",
|
||||||
|
"accessorKey":"field_uvelichenie_smety",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ЯНВАРЬ",
|
||||||
|
"accessorKey":"field_za_yanvar",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ФЕВРАЛЬ",
|
||||||
|
"accessorKey":"field_za_fevral",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого I квартал",
|
||||||
|
"accessorKey":"field_itogo_i_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за АПРЕЛЬ",
|
||||||
|
"accessorKey":"field_za_aprel",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за МАЙ",
|
||||||
|
"accessorKey":"field_za_may",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого II квартал",
|
||||||
|
"accessorKey":"field_itogo_ii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ИЮЛЬ",
|
||||||
|
"accessorKey":"field_za_iyul",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за АВГУСТ",
|
||||||
|
"accessorKey":"field_za_avgust",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого III квартал",
|
||||||
|
"accessorKey":"field_itogo_iii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ОКТЯБРЬ",
|
||||||
|
"accessorKey":"field_za_oktyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за НОЯБРЬ",
|
||||||
|
"accessorKey":"field_za_noyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"СПОД",
|
||||||
|
"accessorKey":"field_spod",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого IV квартал",
|
||||||
|
"accessorKey":"field_itogo_iv_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого корректировка",
|
||||||
|
"accessorKey":"itogo_korrektirovka",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Итого корректировка",
|
||||||
|
"accessorKey":"itogo_korrektirovka_itogo_korrektirovka",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Факт",
|
||||||
|
"accessorKey":"fakt",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"заМАРТ",
|
||||||
|
"accessorKey":"fakt_zamart",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заИЮНЬ",
|
||||||
|
"accessorKey":"fakt_zaiyun",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заСЕНТЯБРЬ",
|
||||||
|
"accessorKey":"fakt_zasentyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заДЕКАБРЬ",
|
||||||
|
"accessorKey":"fakt_zadekabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"экономия (+) перерасход (-)",
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"экономия (+) перерасход (-)",
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Смета расходов по развитию на II квартал",
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_ii_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Смета расходов по развитию на II квартал",
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_ii_kvartal_smeta_raskhodov_po_razvitiyu_na_ii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого скоррек. смета",
|
||||||
|
"accessorKey":"itogo_skorrek_smeta",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Итого скоррек. смета",
|
||||||
|
"accessorKey":"itogo_skorrek_smeta_itogo_skorrek_smeta",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Смета расходов по развитию на III квартал",
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iii_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Смета расходов по развитию на III квартал",
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iii_kvartal_smeta_raskhodov_po_razvitiyu_na_iii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Смета расходов по развитию на IV квартал",
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iv_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Смета расходов по развитию на IV квартал",
|
||||||
|
"accessorKey":"smeta_raskhodov_po_razvitiyu_na_iv_kvartal_smeta_raskhodov_po_razvitiyu_na_iv_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
555
web/src/components/RealtimeTable/constants/FORM_3/TR.js
Normal file
555
web/src/components/RealtimeTable/constants/FORM_3/TR.js
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
|
||||||
|
import {
|
||||||
|
greenColumn,
|
||||||
|
whiteColumn,
|
||||||
|
orangeColumn,
|
||||||
|
yellowColumn,
|
||||||
|
redColumn,
|
||||||
|
blueColumn,
|
||||||
|
} from '../columnColors';
|
||||||
|
export const config = {
|
||||||
|
"config":{
|
||||||
|
"colors":{
|
||||||
|
"kod_razdela_kod_razdela":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"kod_razdela_kod_razdela"
|
||||||
|
},
|
||||||
|
"id_stati_id_stati":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"id_stati_id_stati"
|
||||||
|
},
|
||||||
|
"id_gruppy_nomenklatury_id_gruppy_nomenklatury":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury"
|
||||||
|
},
|
||||||
|
"naimenovanie_naimenovanie":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"naimenovanie_naimenovanie"
|
||||||
|
},
|
||||||
|
"korrektirovka_limita_po_statyam_smety":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"korrektirovka_limita_po_statyam_smety"
|
||||||
|
},
|
||||||
|
"field_uvelichenie_smety":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_uvelichenie_smety"
|
||||||
|
},
|
||||||
|
"field_za_yanvar":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_yanvar"
|
||||||
|
},
|
||||||
|
"field_za_fevral":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_fevral"
|
||||||
|
},
|
||||||
|
"field_itogo_i_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_i_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_aprel":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_aprel"
|
||||||
|
},
|
||||||
|
"field_za_may":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_may"
|
||||||
|
},
|
||||||
|
"field_itogo_ii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_ii_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_iyul":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_iyul"
|
||||||
|
},
|
||||||
|
"field_za_avgust":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_avgust"
|
||||||
|
},
|
||||||
|
"field_itogo_iii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_iii_kvartal"
|
||||||
|
},
|
||||||
|
"field_za_oktyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_oktyabr"
|
||||||
|
},
|
||||||
|
"field_za_noyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_za_noyabr"
|
||||||
|
},
|
||||||
|
"field_spod":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_spod"
|
||||||
|
},
|
||||||
|
"field_itogo_iv_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"field_itogo_iv_kvartal"
|
||||||
|
},
|
||||||
|
"itogo_itogo":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"itogo_itogo"
|
||||||
|
},
|
||||||
|
"fakt_zamart":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zamart"
|
||||||
|
},
|
||||||
|
"fakt_zaiyun":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zaiyun"
|
||||||
|
},
|
||||||
|
"fakt_zasentyabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zasentyabr"
|
||||||
|
},
|
||||||
|
"fakt_zadekabr":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"fakt_zadekabr"
|
||||||
|
},
|
||||||
|
"ekonomiya_pereraskhod_ekonomiya_pereraskhod":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod"
|
||||||
|
},
|
||||||
|
"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal"
|
||||||
|
},
|
||||||
|
"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal"
|
||||||
|
},
|
||||||
|
"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal":{
|
||||||
|
"color_type":whiteColumn,
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Код раздела",
|
||||||
|
"accessorKey":"kod_razdela",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Код раздела",
|
||||||
|
"accessorKey":"kod_razdela_kod_razdela",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"ID статьи",
|
||||||
|
"accessorKey":"id_stati",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"ID статьи",
|
||||||
|
"accessorKey":"id_stati_id_stati",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"ID группы номенклатуры",
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"ID группы номенклатуры",
|
||||||
|
"accessorKey":"id_gruppy_nomenklatury_id_gruppy_nomenklatury",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Наименование",
|
||||||
|
"accessorKey":"naimenovanie",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Наименование",
|
||||||
|
"accessorKey":"naimenovanie_naimenovanie",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Корректировка лимита",
|
||||||
|
"accessorKey":"korrektirovka_limita",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"по статьям сметы *",
|
||||||
|
"accessorKey":"korrektirovka_limita_po_statyam_smety",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"",
|
||||||
|
"accessorKey":"field",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"увеличение сметы **",
|
||||||
|
"accessorKey":"field_uvelichenie_smety",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ЯНВАРЬ",
|
||||||
|
"accessorKey":"field_za_yanvar",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ФЕВРАЛЬ",
|
||||||
|
"accessorKey":"field_za_fevral",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого I квартал",
|
||||||
|
"accessorKey":"field_itogo_i_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за АПРЕЛЬ",
|
||||||
|
"accessorKey":"field_za_aprel",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за МАЙ",
|
||||||
|
"accessorKey":"field_za_may",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого II квартал",
|
||||||
|
"accessorKey":"field_itogo_ii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ИЮЛЬ",
|
||||||
|
"accessorKey":"field_za_iyul",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за АВГУСТ",
|
||||||
|
"accessorKey":"field_za_avgust",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого III квартал",
|
||||||
|
"accessorKey":"field_itogo_iii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за ОКТЯБРЬ",
|
||||||
|
"accessorKey":"field_za_oktyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"за НОЯБРЬ",
|
||||||
|
"accessorKey":"field_za_noyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"СПОД",
|
||||||
|
"accessorKey":"field_spod",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого IV квартал",
|
||||||
|
"accessorKey":"field_itogo_iv_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Итого",
|
||||||
|
"accessorKey":"itogo",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Итого",
|
||||||
|
"accessorKey":"itogo_itogo",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Факт",
|
||||||
|
"accessorKey":"fakt",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"заМАРТ",
|
||||||
|
"accessorKey":"fakt_zamart",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заИЮНЬ",
|
||||||
|
"accessorKey":"fakt_zaiyun",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заСЕНТЯБРЬ",
|
||||||
|
"accessorKey":"fakt_zasentyabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"заДЕКАБРЬ",
|
||||||
|
"accessorKey":"fakt_zadekabr",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains",
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"экономия (+) перерасход (-)",
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"экономия (+) перерасход (-)",
|
||||||
|
"accessorKey":"ekonomiya_pereraskhod_ekonomiya_pereraskhod",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на II квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_ii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на III квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iii_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||||
|
"columns":[
|
||||||
|
{
|
||||||
|
"header":"Лимит затрат / остаток лимита затрат на IV квартал",
|
||||||
|
"accessorKey":"limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal_limit_zatrat_ostatok_limita_zatrat_na_iv_kvartal",
|
||||||
|
"size":150,
|
||||||
|
"filterFn":"contains"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"muiTableHeadCellProps":{
|
||||||
|
"sx":{
|
||||||
|
"backgroundColor":"#C2D69B",
|
||||||
|
"color":"#000000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -25,29 +25,17 @@ export const RealtimeProvider = ({
|
|||||||
formId,
|
formId,
|
||||||
sheetName,
|
sheetName,
|
||||||
direction,
|
direction,
|
||||||
year,
|
|
||||||
userId,
|
userId,
|
||||||
isProject = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
// Формируем URL (кастомные секреты на фронте пока недоступны, делаем через REACT_APP_ROOT_PATH)
|
// Формируем URL (кастомные секреты на фронте пока недоступны, делаем через REACT_APP_ROOT_PATH)
|
||||||
const wsUrl =
|
const wsUrl = `${(process.env.REACT_APP_API_URL || process.env.REACT_APP_API_URL || process.env.REACT_APP_ROOT_PATH || "ws://localhost:8000").replace(/^http/, "ws")}/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`;
|
||||||
`${(
|
|
||||||
process.env.REACT_APP_API_URL ||
|
|
||||||
process.env.REACT_APP_API_URL ||
|
|
||||||
process.env.REACT_APP_ROOT_PATH ||
|
|
||||||
"ws://localhost:8000"
|
|
||||||
).replace(/^http/, "ws")}` +
|
|
||||||
(!isProject
|
|
||||||
? `/api/v1/ws/form/${formId}/sheet/${sheetName}${direction ? `?direction=${direction}` : ""}`
|
|
||||||
: `/api/v1/ws/projects/${formId}/report/${year}/${sheetName}`);
|
|
||||||
|
|
||||||
//для ячеек которые редактируются другими пользователями
|
//для ячеек которые редактируются другими пользователями
|
||||||
const [lockedCells, setLockedCells] = useState([]);
|
const [lockedCells, setLockedCells] = useState([]);
|
||||||
const handleMessage = useCallback((data) => {
|
const handleMessage = useCallback((data) => {
|
||||||
// Обработка ошибок
|
// Обработка ошибок
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
console.log(data.error.message);
|
console.log(data.error.message)
|
||||||
toast.error("Server error:" + data.error.message || "");
|
toast.error("Server error:" + data.error.message || '');
|
||||||
onErrorRef.current?.(data.error);
|
onErrorRef.current?.(data.error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -93,7 +81,7 @@ export const RealtimeProvider = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { isConnectedRef, isConnected, sendMessage, disconnect, lastError } = useWebSocket(
|
const { isConnected, sendMessage, disconnect, lastError } = useWebSocket(
|
||||||
wsUrl,
|
wsUrl,
|
||||||
handleMessage,
|
handleMessage,
|
||||||
);
|
);
|
||||||
@ -157,26 +145,13 @@ export const RealtimeProvider = ({
|
|||||||
}
|
}
|
||||||
}, [isConnected]);
|
}, [isConnected]);
|
||||||
|
|
||||||
const addCommonField = (message) => {
|
|
||||||
if (isProject) {
|
|
||||||
message.report_type = sheetName;
|
|
||||||
message.project_id = formId;
|
|
||||||
message.year = year;
|
|
||||||
} else {
|
|
||||||
message.sheet = sheetName;
|
|
||||||
message.form_id = formId;
|
|
||||||
message.direction = direction;
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция для начала редактирования ячейки
|
// Функция для начала редактирования ячейки
|
||||||
const startEditing = useCallback(
|
const startEditing = useCallback(
|
||||||
async (row, column) => {
|
async (row, column) => {
|
||||||
const columnId = column.id;
|
const columnId = column.id;
|
||||||
const rowId = row.id;
|
const rowId = row.id;
|
||||||
|
|
||||||
if (!isConnectedRef) {
|
if (!isConnected) {
|
||||||
onErrorRef.current?.("WebSocket не подключен");
|
onErrorRef.current?.("WebSocket не подключен");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -208,7 +183,7 @@ export const RealtimeProvider = ({
|
|||||||
const columnId = column.id;
|
const columnId = column.id;
|
||||||
const rowId = row.id;
|
const rowId = row.id;
|
||||||
|
|
||||||
if (!isConnectedRef) {
|
if (!isConnected) {
|
||||||
onErrorRef.current?.("WebSocket не подключен");
|
onErrorRef.current?.("WebSocket не подключен");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -238,7 +213,7 @@ export const RealtimeProvider = ({
|
|||||||
async (row, column, value) => {
|
async (row, column, value) => {
|
||||||
const columnId = column.id;
|
const columnId = column.id;
|
||||||
const rowId = row.id;
|
const rowId = row.id;
|
||||||
if (!isConnectedRef) {
|
if (!isConnected) {
|
||||||
onErrorRef.current?.("WebSocket не подключен");
|
onErrorRef.current?.("WebSocket не подключен");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -266,7 +241,7 @@ export const RealtimeProvider = ({
|
|||||||
|
|
||||||
const addRow = useCallback(
|
const addRow = useCallback(
|
||||||
async (rowData) => {
|
async (rowData) => {
|
||||||
if (!isConnectedRef) {
|
if (!isConnected) {
|
||||||
onErrorRef.current?.("Нет подключения или авторизации");
|
onErrorRef.current?.("Нет подключения или авторизации");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -274,9 +249,10 @@ export const RealtimeProvider = ({
|
|||||||
const message = {
|
const message = {
|
||||||
event: "row_added",
|
event: "row_added",
|
||||||
data: rowData,
|
data: rowData,
|
||||||
|
sheet: sheetName,
|
||||||
|
form_id: formId,
|
||||||
|
direction: direction,
|
||||||
};
|
};
|
||||||
addCommonField(message);
|
|
||||||
|
|
||||||
return sendMessage(message);
|
return sendMessage(message);
|
||||||
},
|
},
|
||||||
[isConnected, sendMessage, sheetName, formId, direction],
|
[isConnected, sendMessage, sheetName, formId, direction],
|
||||||
@ -284,7 +260,7 @@ export const RealtimeProvider = ({
|
|||||||
|
|
||||||
const deleteRow = useCallback(
|
const deleteRow = useCallback(
|
||||||
async (rowId) => {
|
async (rowId) => {
|
||||||
if (!isConnectedRef) {
|
if (!isConnected) {
|
||||||
onErrorRef.current?.("Нет подключения или авторизации");
|
onErrorRef.current?.("Нет подключения или авторизации");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -292,8 +268,10 @@ export const RealtimeProvider = ({
|
|||||||
const message = {
|
const message = {
|
||||||
event: "row_deleted",
|
event: "row_deleted",
|
||||||
data: { row_id: rowId },
|
data: { row_id: rowId },
|
||||||
|
sheet: sheetName,
|
||||||
|
form_id: formId,
|
||||||
|
direction: direction,
|
||||||
};
|
};
|
||||||
addCommonField(message);
|
|
||||||
|
|
||||||
return sendMessage(message);
|
return sendMessage(message);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,9 +4,8 @@ import { useRealtime } from "../contexts/RealtimeContext";
|
|||||||
import { deleteRow, insertCell, updateCells } from "../utils/cellUtils";
|
import { deleteRow, insertCell, updateCells } from "../utils/cellUtils";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { isVspNewRow, isVspRow } from "../utils/rowUtils";
|
import { isVspNewRow, isVspRow } from "../utils/rowUtils";
|
||||||
import { ProjectsApi } from "../../../api/projects";
|
|
||||||
|
|
||||||
const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
const useRealtimeData = (formId, sheetName, direction) => {
|
||||||
const [data, setData] = useState([]);
|
const [data, setData] = useState([]);
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
@ -78,10 +77,7 @@ const useRealtimeData = (formId, sheetName, direction, formType, year) => {
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
let res;
|
let res;
|
||||||
|
if (direction !== null) {
|
||||||
if(formType == 'PROJECT'){
|
|
||||||
res = await ProjectsApi.getTableData(formId, sheetName, year)
|
|
||||||
} else if (direction !== null) {
|
|
||||||
res = await FormsSheetApi.get(formId, sheetName, {
|
res = await FormsSheetApi.get(formId, sheetName, {
|
||||||
params: { direction: direction },
|
params: { direction: direction },
|
||||||
});
|
});
|
||||||
|
|||||||
@ -107,16 +107,30 @@ export const useWebSocket = (url, onMessage) => {
|
|||||||
return false;
|
return false;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const login = useCallback(
|
||||||
|
(token) => {
|
||||||
|
if (!token) {
|
||||||
|
console.warn("No token provided for login");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return sendMessage({
|
||||||
|
event: "user_login",
|
||||||
|
data: { token },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[sendMessage],
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
connect();
|
connect();
|
||||||
return () => disconnect();
|
return () => disconnect();
|
||||||
}, [connect, disconnect]);
|
}, [connect, disconnect]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isConnectedRef,
|
isConnected: isConnectedRef,
|
||||||
isConnected,
|
|
||||||
error,
|
error,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
|
login,
|
||||||
disconnect,
|
disconnect,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,6 @@ import { createPortal } from 'react-dom';
|
|||||||
import { columns as mockColumns } from '../../mocks/mockTable';
|
import { columns as mockColumns } from '../../mocks/mockTable';
|
||||||
import { config } from './constants/FORM_2/AHR';
|
import { config } from './constants/FORM_2/AHR';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { useRealtime } from './contexts/RealtimeContext';
|
|
||||||
|
|
||||||
function EditCellPortal({
|
function EditCellPortal({
|
||||||
cell,
|
cell,
|
||||||
@ -21,8 +20,6 @@ function EditCellPortal({
|
|||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const isDisabled = false;
|
const isDisabled = false;
|
||||||
|
|
||||||
const { endEditing: contextEndEditing } = useRealtime();
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const tbodyRef = ref.current.offsetParent?.offsetParent?.offsetParent;
|
const tbodyRef = ref.current.offsetParent?.offsetParent?.offsetParent;
|
||||||
@ -71,7 +68,6 @@ function EditCellPortal({
|
|||||||
disabled={!isEditable}
|
disabled={!isEditable}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
tableId={tableKey}
|
tableId={tableKey}
|
||||||
table={table}
|
|
||||||
cellId={cell.row.id + '_' + cell.column.id}
|
cellId={cell.row.id + '_' + cell.column.id}
|
||||||
/>,
|
/>,
|
||||||
refTbody,
|
refTbody,
|
||||||
@ -116,7 +112,6 @@ export const getTableColumns = ({
|
|||||||
globalFilter={
|
globalFilter={
|
||||||
table.getState().globalFilter
|
table.getState().globalFilter
|
||||||
}
|
}
|
||||||
columnFilter={table.getState().columnFilters?.find(f => f.id === column.id)?.value}
|
|
||||||
isEditable={row.original?.row_type === 'INPUT' || false}
|
isEditable={row.original?.row_type === 'INPUT' || false}
|
||||||
backgroundColor={columnColors[column.id]?.color_type?.[row.original?.row_type || row.row_type]}
|
backgroundColor={columnColors[column.id]?.color_type?.[row.original?.row_type || row.row_type]}
|
||||||
color={columnColors[column.id]?.color_type?.['COLOR']}
|
color={columnColors[column.id]?.color_type?.['COLOR']}
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import { StageStatusChip } from './StageStatusChip';
|
|||||||
import { formatDateForApi } from './utils';
|
import { formatDateForApi } from './utils';
|
||||||
import CollapsibleTree from '../common/CollapsibleTree';
|
import CollapsibleTree from '../common/CollapsibleTree';
|
||||||
import { collectLeafColumns } from '../RealtimeTable/utils/columnUtils';
|
import { collectLeafColumns } from '../RealtimeTable/utils/columnUtils';
|
||||||
import { STAGE_ROLE_RUSSIAN_NAME } from '../../constants/constants';
|
import { STAGE_ROLE_RUSSIAN_NAME } from '../../constants';
|
||||||
|
|
||||||
export const AddStagesModal = ({
|
export const AddStagesModal = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Box, Divider, Stack } from '@mui/material';
|
import { Box, Divider, Stack } from '@mui/material';
|
||||||
import { UnLockedSvg } from '../common/icons/icons';
|
import { UnLockedSvg } from '../common/icons/icons';
|
||||||
import { ROLES_ID_RUSSIAN_NAME, EDIT_ACCESS_RUSSIAN } from '../../constants/constants';
|
import { ROLES_ID_RUSSIAN_NAME, EDIT_ACCESS_RUSSIAN } from '../../constants';
|
||||||
|
|
||||||
export const StageInfoForTooltip = ({ stageData = [] }) => {
|
export const StageInfoForTooltip = ({ stageData = [] }) => {
|
||||||
const accessData = stageData[0] || [];
|
const accessData = stageData[0] || [];
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import {
|
|||||||
import { getStageStatus, getStatusColors } from './utils';
|
import { getStageStatus, getStatusColors } from './utils';
|
||||||
import { formatDate } from '../../../utils/formatDate';
|
import { formatDate } from '../../../utils/formatDate';
|
||||||
import { StageStatusChip } from '../StageStatusChip';
|
import { StageStatusChip } from '../StageStatusChip';
|
||||||
import { ROLES_NAME_ID, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../constants/constants';
|
import { ROLES_NAME_ID, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../constants';
|
||||||
import { useAuth } from '../../../app/context/AuthProvider';
|
import { useAuth } from '../../../app/context/AuthProvider';
|
||||||
|
|
||||||
export const StagesTable = ({ stages, onEdit, onDelete }) => {
|
export const StagesTable = ({ stages, onEdit, onDelete }) => {
|
||||||
|
|||||||
@ -1,33 +0,0 @@
|
|||||||
// components/NavigationToggle.jsx
|
|
||||||
import React from 'react';
|
|
||||||
import { Box, Typography } from '@mui/material';
|
|
||||||
import { PrimaryOutlinedButton } from './Buttons/Buttons'; // Ваш компонент кнопки
|
|
||||||
|
|
||||||
const NavigationToggle = ({ currentView, onViewChange }) => {
|
|
||||||
const handleNavigateToTasks = () => {
|
|
||||||
onViewChange('/tasks');
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNavigateToProjects = () => {
|
|
||||||
onViewChange('/projects');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
|
||||||
{currentView === 'tasks' ? (
|
|
||||||
<PrimaryOutlinedButton onClick={handleNavigateToProjects}>
|
|
||||||
← К проектам
|
|
||||||
</PrimaryOutlinedButton>
|
|
||||||
) : (
|
|
||||||
<PrimaryOutlinedButton onClick={handleNavigateToTasks}>
|
|
||||||
← К задачам
|
|
||||||
</PrimaryOutlinedButton>
|
|
||||||
)}
|
|
||||||
<Typography variant="h6" color="primary" sx={{ fontWeight: 200, fontSize: '1rem' }}>
|
|
||||||
{currentView === 'tasks' ? 'Управление задачами' : 'Управление проектами'}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NavigationToggle;
|
|
||||||
@ -1,257 +0,0 @@
|
|||||||
// src/components/common/PaginatedList/PaginatedList.jsx
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
Pagination,
|
|
||||||
PaginationItem,
|
|
||||||
Select,
|
|
||||||
MenuItem,
|
|
||||||
Typography,
|
|
||||||
Stack,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { CardsSkeleton } from './Skeleton';
|
|
||||||
import {
|
|
||||||
ExportExitButton,
|
|
||||||
ExportWithTextButton,
|
|
||||||
} from './Buttons/ButtonsActions';
|
|
||||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportFile';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Универсальный компонент для отображения списка с пагинацией и экспортом
|
|
||||||
*
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {Array} props.items - Массив элементов для отображения
|
|
||||||
* @param {boolean} props.loading - Состояние загрузки
|
|
||||||
* @param {number} props.totalCount - Общее количество элементов
|
|
||||||
* @param {number} props.page - Текущая страница
|
|
||||||
* @param {number} props.limit - Количество элементов на странице
|
|
||||||
* @param {Function} props.onPageChange - Обработчик изменения страницы
|
|
||||||
* @param {Function} props.onLimitChange - Обработчик изменения лимита
|
|
||||||
* @param {Function} props.renderItem - Функция рендеринга элемента
|
|
||||||
* @param {Function} props.getItemId - Функция получения ID элемента
|
|
||||||
* @param {Function} props.getItemTitle - Функция получения заголовка элемента
|
|
||||||
* @param {string} props.entityName - Название сущности (для текстов)
|
|
||||||
* @param {string} props.emptyMessage - Сообщение при пустом списке
|
|
||||||
* @param {number} props.skeletonCount - Количество силуэтов таблиц при загрузки
|
|
||||||
* @param {Object} props.extraActions - Дополнительные действия справа
|
|
||||||
* @param {boolean} props.enableExport - Включить экспорт
|
|
||||||
* @param {Array} props.limitOptions - Опции для выбора лимита
|
|
||||||
* @param {number} props.maxHeight - Максимальная высота контейнера со скроллом
|
|
||||||
* @param {boolean} props.enableScroll - Включить скролл для списка
|
|
||||||
* @param {boolean} props.filterActions - Элементы фильтрации
|
|
||||||
*/
|
|
||||||
export function PaginatedList({
|
|
||||||
items = [],
|
|
||||||
loading = false,
|
|
||||||
totalCount = 0,
|
|
||||||
page = 1,
|
|
||||||
limit = 20,
|
|
||||||
onPageChange,
|
|
||||||
onLimitChange,
|
|
||||||
renderItem,
|
|
||||||
getItemId,
|
|
||||||
getItemTitle = (item) => `Элемент ${getItemId(item)}`,
|
|
||||||
entityName = 'элементов',
|
|
||||||
emptyMessage = 'Элементы не найдены',
|
|
||||||
skeletonCount = 20,
|
|
||||||
extraActions = null,
|
|
||||||
enableExport = true,
|
|
||||||
limitOptions = [10, 20, 50, 100, 200],
|
|
||||||
maxHeight = 'calc(100vh - 300px)',
|
|
||||||
enableScroll = true,
|
|
||||||
filterActions = null,
|
|
||||||
}) {
|
|
||||||
const [exportMode, setExportMode] = useState(false);
|
|
||||||
const [exportList, setExportList] = useState([]);
|
|
||||||
|
|
||||||
// Сброс списка экспорта при переключении режима
|
|
||||||
useEffect(() => {
|
|
||||||
setExportList([]);
|
|
||||||
}, [exportMode]);
|
|
||||||
|
|
||||||
const handleAddToExport = (itemId, checked) => {
|
|
||||||
if (checked) {
|
|
||||||
setExportList((prev) => [...prev, itemId]);
|
|
||||||
} else {
|
|
||||||
setExportList((prev) => prev.filter((id) => id !== itemId));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBulkExport = async () => {
|
|
||||||
await exportBulkForms(exportList);
|
|
||||||
setExportMode(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSingleExport = (itemId) => {
|
|
||||||
const item = items.find((item) => getItemId(item) === itemId);
|
|
||||||
const title = item ? getItemTitle(item) : `Элемент ${itemId}`;
|
|
||||||
exportSingleForm(itemId, title);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Если включен экспорт, передаем его пропсы в дочерние элементы
|
|
||||||
const renderItemWithExport = (item) => {
|
|
||||||
if (enableExport) {
|
|
||||||
return renderItem(item, {
|
|
||||||
exportMode,
|
|
||||||
onAddForExport: (checked) => {
|
|
||||||
handleAddToExport(getItemId(item), checked);
|
|
||||||
},
|
|
||||||
onExportClick: () => {
|
|
||||||
handleSingleExport(getItemId(item));
|
|
||||||
},
|
|
||||||
isSelected: exportList.includes(getItemId(item)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return renderItem(item);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100%', width: '100%' }}>
|
|
||||||
{/* Верхняя панель с экспортом и дополнительными действиями */}
|
|
||||||
{enableExport && (
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
|
||||||
<div>
|
|
||||||
{filterActions}
|
|
||||||
</div>
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: '1.5rem' }}>
|
|
||||||
{exportMode && (
|
|
||||||
<Typography variant="body2" color="textSecondary">
|
|
||||||
Выделено {entityName}: {exportList.length}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
<Box sx={{ flex: 1 }} />
|
|
||||||
<Stack direction="row" spacing="0.5rem">
|
|
||||||
{!exportMode ? (
|
|
||||||
<>
|
|
||||||
<ExportWithTextButton
|
|
||||||
text="Пакетный экспорт"
|
|
||||||
onClick={() => setExportMode(true)}
|
|
||||||
height='2.5rem'
|
|
||||||
/>
|
|
||||||
{extraActions}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ExportWithTextButton
|
|
||||||
text="Экспортировать"
|
|
||||||
onClick={handleBulkExport}
|
|
||||||
disabled={exportList.length === 0}
|
|
||||||
height='2.5rem'
|
|
||||||
/>
|
|
||||||
<ExportExitButton onClick={() => setExportMode(false)} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Контейнер со скроллом для списка */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
flex: 1,
|
|
||||||
overflow: enableScroll ? 'auto' : 'visible',
|
|
||||||
maxHeight: enableScroll ? maxHeight : 'none',
|
|
||||||
'&::-webkit-scrollbar': {
|
|
||||||
width: '8px',
|
|
||||||
},
|
|
||||||
'&::-webkit-scrollbar-track': {
|
|
||||||
background: '#f1f1f1',
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
'&::-webkit-scrollbar-thumb': {
|
|
||||||
background: '#888',
|
|
||||||
borderRadius: '4px',
|
|
||||||
},
|
|
||||||
'&::-webkit-scrollbar-thumb:hover': {
|
|
||||||
background: '#555',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Список элементов */}
|
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
|
|
||||||
{loading ? (
|
|
||||||
<CardsSkeleton count={skeletonCount} />
|
|
||||||
) : items.length === 0 ? (
|
|
||||||
<Box sx={{ textAlign: 'center', py: 4, width: '100%' }}>
|
|
||||||
<Typography variant="body1" color="textSecondary">
|
|
||||||
{emptyMessage}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
) : (
|
|
||||||
items.map((item) => renderItemWithExport(item))
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Пагинация */}
|
|
||||||
{!loading && items.length > 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
pt: 2,
|
|
||||||
pb: 2,
|
|
||||||
borderTop: '1px solid',
|
|
||||||
borderColor: 'divider',
|
|
||||||
flexShrink: 0,
|
|
||||||
mt: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
||||||
<Typography variant="body2" color="textSecondary">
|
|
||||||
Показать на странице:
|
|
||||||
</Typography>
|
|
||||||
<Select
|
|
||||||
value={limit}
|
|
||||||
onChange={onLimitChange}
|
|
||||||
size="small"
|
|
||||||
sx={{ minWidth: 70 }}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{limitOptions.map((option) => (
|
|
||||||
<MenuItem key={option} value={option}>
|
|
||||||
{option}
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</Box>
|
|
||||||
<Typography variant="body2" color="textSecondary">
|
|
||||||
Всего: {totalCount}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Pagination
|
|
||||||
count={Math.ceil(totalCount / limit)}
|
|
||||||
page={page}
|
|
||||||
onChange={onPageChange}
|
|
||||||
color="primary"
|
|
||||||
size="large"
|
|
||||||
showFirstButton
|
|
||||||
showLastButton
|
|
||||||
disabled={loading}
|
|
||||||
renderItem={(item) => (
|
|
||||||
<PaginationItem
|
|
||||||
{...item}
|
|
||||||
sx={{
|
|
||||||
'&.Mui-selected': {
|
|
||||||
backgroundColor: 'primary.main',
|
|
||||||
color: 'white',
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: 'primary.dark',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Box sx={{ width: 120 }} />
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useState, useRef } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { EDIT_ACCESS_RUSSIAN, ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
import { EDIT_ACCESS_RUSSIAN, ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||||
import {
|
import {
|
||||||
SelectWrapper,
|
SelectWrapper,
|
||||||
SelectContainer,
|
SelectContainer,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useAuth } from '../../../app/context/AuthProvider';
|
import { useAuth } from '../../../app/context/AuthProvider';
|
||||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../constants';
|
||||||
import { Switch, Buttons, StyledNavLink } from './SwitchFormTask.style';
|
import { Switch, Buttons, StyledNavLink } from './SwitchFormTask.style';
|
||||||
|
|
||||||
export function SwitchFormTask() {
|
export function SwitchFormTask() {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DIRECTION_TRANSLATE, FORM_TYPE_OPTIONS } from '../../../constants/constants';
|
import { DIRECTION_TRANSLATE, FORM_TYPE_OPTIONS } from '../../../constants';
|
||||||
import {
|
import {
|
||||||
Section,
|
Section,
|
||||||
SectionStartPart,
|
SectionStartPart,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React from 'react';
|
||||||
import { Box, Stack, Typography } from '@mui/material';
|
import { Box, Stack, Typography } from '@mui/material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import TableCard from '../TableCard/TableCard';
|
import TableCard from '../TableCard/TableCard';
|
||||||
@ -6,10 +6,10 @@ import { toast } from 'react-toastify';
|
|||||||
|
|
||||||
const TablesList = ({
|
const TablesList = ({
|
||||||
filteredTables,
|
filteredTables,
|
||||||
|
projects,
|
||||||
query,
|
query,
|
||||||
basePath = '/table',
|
basePath = '/table',
|
||||||
formInfo,
|
formInfo
|
||||||
isProject = false,
|
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ const TablesList = ({
|
|||||||
if (!formInfo){
|
if (!formInfo){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const path = `${basePath}/form/${formInfo.id}/form-type/${isProject ? 'PROJECT' : formInfo.form_type_code}/${table.sheet}/${table?.direction}/${table?.year}`;
|
const path = `${basePath}/form/${formInfo.id}/form-type/${formInfo.form_type_code}/${table.sheet}/${table?.direction}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableCard
|
<TableCard
|
||||||
|
|||||||
@ -26,7 +26,7 @@ import { formatDate } from '../../utils/formatDate';
|
|||||||
import { IconWithContent } from './IconWithContent';
|
import { IconWithContent } from './IconWithContent';
|
||||||
import { Chip } from '../styles/StyledChip';
|
import { Chip } from '../styles/StyledChip';
|
||||||
import { ExportButton } from '../common/Buttons/ButtonsActions';
|
import { ExportButton } from '../common/Buttons/ButtonsActions';
|
||||||
import { FORM_TYPE_TRANSLATE } from '../../constants/constants';
|
import { FORM_TYPE_TRANSLATE } from '../../constants';
|
||||||
|
|
||||||
const TaskCard = styled.div`
|
const TaskCard = styled.div`
|
||||||
width: 26.8rem;
|
width: 26.8rem;
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
export const DEFAULT_PROJECT_CONFIG = {
|
|
||||||
'ССП/РФ': {
|
|
||||||
type: 'multiselect',
|
|
||||||
options: [],
|
|
||||||
placeholder: 'Выберите ССП/РФ',
|
|
||||||
defaultValue: null,
|
|
||||||
required_field: true,
|
|
||||||
},
|
|
||||||
'Год': {
|
|
||||||
type: 'number',
|
|
||||||
placeholder: 'Введите год',
|
|
||||||
defaultValue: null,
|
|
||||||
min: 0,
|
|
||||||
step: 1,
|
|
||||||
required_field: false,
|
|
||||||
},
|
|
||||||
'Тип проекта развития': {
|
|
||||||
type: 'multiselect',
|
|
||||||
options: [
|
|
||||||
'Открытие ВСП',
|
|
||||||
'Закрытие ВСП',
|
|
||||||
'Переезд ВСП',
|
|
||||||
'Реновация РФ',
|
|
||||||
'Открытие УРМ',
|
|
||||||
],
|
|
||||||
placeholder: 'Выберите тип проекта',
|
|
||||||
defaultValue: null,
|
|
||||||
required_field: false,
|
|
||||||
|
|
||||||
},
|
|
||||||
'Название проекта': {
|
|
||||||
type: 'text',
|
|
||||||
placeholder: 'Введите название проекта',
|
|
||||||
defaultValue: null,
|
|
||||||
required_field: true,
|
|
||||||
},
|
|
||||||
'Формат ВСП': {
|
|
||||||
type: 'multiselect',
|
|
||||||
options: [
|
|
||||||
'Фланговый',
|
|
||||||
'Типовой',
|
|
||||||
'Розничный',
|
|
||||||
'МСБ',
|
|
||||||
'Лёгкий',
|
|
||||||
'Мини',
|
|
||||||
'Розничный-киоск',
|
|
||||||
'МБО',
|
|
||||||
'Офис самообслуживания',
|
|
||||||
'Другое',
|
|
||||||
],
|
|
||||||
placeholder: 'Выберите формат ВСП',
|
|
||||||
defaultValue: null,
|
|
||||||
required_field: false,
|
|
||||||
|
|
||||||
},
|
|
||||||
'Адрес объекта': {
|
|
||||||
type: 'text',
|
|
||||||
placeholder: 'Введите адрес объекта',
|
|
||||||
defaultValue: '',
|
|
||||||
required_field: false,
|
|
||||||
|
|
||||||
},
|
|
||||||
'Целевая численность, чел.': {
|
|
||||||
type: 'number',
|
|
||||||
placeholder: 'Введите число',
|
|
||||||
defaultValue: 0,
|
|
||||||
min: 0,
|
|
||||||
step: 1,
|
|
||||||
required_field: false,
|
|
||||||
|
|
||||||
},
|
|
||||||
'Тип размещения': {
|
|
||||||
type: 'multiselect',
|
|
||||||
options: ['Собственность', 'Аренда', 'Субаренда'],
|
|
||||||
placeholder: 'Выберите тип размещения',
|
|
||||||
defaultValue: null,
|
|
||||||
required_field: false,
|
|
||||||
|
|
||||||
},
|
|
||||||
'Площадь размещения, м2': {
|
|
||||||
type: 'number',
|
|
||||||
placeholder: 'Введите число',
|
|
||||||
defaultValue: 0,
|
|
||||||
min: 0,
|
|
||||||
step: 1,
|
|
||||||
required_field: false,
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
};
|
|
||||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react';
|
|||||||
import { PageContainer } from '../../StyledForPage';
|
import { PageContainer } from '../../StyledForPage';
|
||||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch.jsx';
|
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch.jsx';
|
||||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../constants';
|
||||||
import { useAuth } from '../../../app/context/AuthProvider';
|
import { useAuth } from '../../../app/context/AuthProvider';
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { Box, Typography, Link, Chip } from '@mui/material';
|
import { Box, Typography, Link, Chip } from '@mui/material';
|
||||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../../constants/constants';
|
import { ROLES_ID_RUSSIAN_NAME } from '../../../../constants';
|
||||||
import {
|
import {
|
||||||
ENTITY_RUSSIAN_NAMES,
|
ENTITY_RUSSIAN_NAMES,
|
||||||
getRussianNamesForType,
|
getRussianNamesForType,
|
||||||
|
|||||||
@ -12,8 +12,8 @@ export const EVENT_CONFIGS = {
|
|||||||
format: 'Утвержденный формат',
|
format: 'Утвержденный формат',
|
||||||
opened_at: 'Дата открытия ВСП',
|
opened_at: 'Дата открытия ВСП',
|
||||||
notes: 'Примечания',
|
notes: 'Примечания',
|
||||||
placement_type: 'Форма расположения ВСП',
|
location_form: 'Форма расположения ВСП',
|
||||||
staff_count: 'Штатная численность ВСП',
|
numbers: 'Штатная численность ВСП',
|
||||||
total_area: 'Площадь помещений ВСП',
|
total_area: 'Площадь помещений ВСП',
|
||||||
rent_contract_num: 'Номер договора аренды',
|
rent_contract_num: 'Номер договора аренды',
|
||||||
rent_end_date: 'Срок окончания договора аренды',
|
rent_end_date: 'Срок окончания договора аренды',
|
||||||
@ -21,6 +21,8 @@ export const EVENT_CONFIGS = {
|
|||||||
branch_name: 'Региональный филиал ',
|
branch_name: 'Региональный филиал ',
|
||||||
closed_at: 'Дата закрытия ВСП',
|
closed_at: 'Дата закрытия ВСП',
|
||||||
user_email: 'Почта пользователя',
|
user_email: 'Почта пользователя',
|
||||||
|
staff_count: 'Кол-во работников',
|
||||||
|
placement_type: 'Тип размещение',
|
||||||
is_active: 'Активный',
|
is_active: 'Активный',
|
||||||
},
|
},
|
||||||
VSP_UPDATE: {
|
VSP_UPDATE: {
|
||||||
@ -30,8 +32,8 @@ export const EVENT_CONFIGS = {
|
|||||||
format: 'Утвержденный формат',
|
format: 'Утвержденный формат',
|
||||||
opened_at: 'Дата открытия ВСП',
|
opened_at: 'Дата открытия ВСП',
|
||||||
notes: 'Примечания',
|
notes: 'Примечания',
|
||||||
placement_type: 'Форма расположения ВСП',
|
location_form: 'Форма расположения ВСП',
|
||||||
staff_count: 'Штатная численность ВСП',
|
numbers: 'Штатная численность ВСП',
|
||||||
total_area: 'Площадь помещений ВСП',
|
total_area: 'Площадь помещений ВСП',
|
||||||
rent_contract_num: 'Номер договора аренды',
|
rent_contract_num: 'Номер договора аренды',
|
||||||
rent_end_date: 'Срок окончания договора аренды',
|
rent_end_date: 'Срок окончания договора аренды',
|
||||||
@ -39,6 +41,8 @@ export const EVENT_CONFIGS = {
|
|||||||
'changes.branch_name': 'Региональный филиал ',
|
'changes.branch_name': 'Региональный филиал ',
|
||||||
closed_at: 'Дата закрытия ВСП',
|
closed_at: 'Дата закрытия ВСП',
|
||||||
user_email: 'Почта пользователя',
|
user_email: 'Почта пользователя',
|
||||||
|
staff_count: 'Кол-во работников',
|
||||||
|
placement_type: 'Тип размещение',
|
||||||
is_active: 'Активный',
|
is_active: 'Активный',
|
||||||
},
|
},
|
||||||
USER_ACCESS_GRANTED: {
|
USER_ACCESS_GRANTED: {
|
||||||
@ -78,15 +82,15 @@ export const EVENT_CONFIGS = {
|
|||||||
},
|
},
|
||||||
ACCESS_EXTEND: {
|
ACCESS_EXTEND: {
|
||||||
form_id: 'Информация о задаче',
|
form_id: 'Информация о задаче',
|
||||||
closes_at_after: 'Дата закрытия (новая)',
|
closes_at_after: 'Дата закрытия',
|
||||||
closes_at_before: 'Дата закрытия (старая)',
|
closes_at_before: 'Дата открытия',
|
||||||
phase_name: 'Названия этапа',
|
phase_name: 'Названия этапа',
|
||||||
sheet: "Лист",
|
sheet: "Лист",
|
||||||
},
|
},
|
||||||
ACCESS_WINDOW_CHANGE: {
|
ACCESS_WINDOW_CHANGE: {
|
||||||
form_id: 'Информация о задаче',
|
form_id: 'Информация о задаче',
|
||||||
closes_at: 'Дата закрытия',
|
closes_at: 'Дата закрытия',
|
||||||
opens_at: 'Дата открытия',
|
open_at: 'Дата открытия',
|
||||||
phase_name: 'Названия этапа',
|
phase_name: 'Названия этапа',
|
||||||
role: 'Для роли',
|
role: 'Для роли',
|
||||||
column_keys: 'Колонки в этапе',
|
column_keys: 'Колонки в этапе',
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { FormsApi } from '../../../../api/forms';
|
|||||||
import { SspApi } from '../../../../api/ssp';
|
import { SspApi } from '../../../../api/ssp';
|
||||||
import { TasksApi } from '../../../../api/tasks';
|
import { TasksApi } from '../../../../api/tasks';
|
||||||
import { UsersApi } from '../../../../api/users';
|
import { UsersApi } from '../../../../api/users';
|
||||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../../constants/constants';
|
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, ROLES_ID_RUSSIAN_NAME, SHEET_NAME, STAGE_ROLE_RUSSIAN_NAME } from '../../../../constants';
|
||||||
|
|
||||||
export const VALUE_TRANSFORMERS = {
|
export const VALUE_TRANSFORMERS = {
|
||||||
Роль: transformRole,
|
Роль: transformRole,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import { PageContainer } from '../../StyledForPage';
|
import { PageContainer } from '../../StyledForPage';
|
||||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
||||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../constants';
|
||||||
import { useAuth } from '../../../app/context/AuthProvider';
|
import { useAuth } from '../../../app/context/AuthProvider';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
|||||||
import Modal from '../../../components/common/Modal/Modal';
|
import Modal from '../../../components/common/Modal/Modal';
|
||||||
import { Box, Button, Typography } from '@mui/material';
|
import { Box, Button, Typography } from '@mui/material';
|
||||||
import SelectWithOptions from '../components/SelectWithOptions';
|
import SelectWithOptions from '../components/SelectWithOptions';
|
||||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../constants';
|
||||||
import { AutocompleteForChangeOptions } from '../components/AutocompleteForChangeOptions';
|
import { AutocompleteForChangeOptions } from '../components/AutocompleteForChangeOptions';
|
||||||
|
|
||||||
export const ModalEditUserSsp = ({
|
export const ModalEditUserSsp = ({
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
|
|||||||
import {
|
import {
|
||||||
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
||||||
ROLES_ID_RUSSIAN_NAME,
|
ROLES_ID_RUSSIAN_NAME,
|
||||||
} from '../../../../constants/constants';
|
} from '../../../../constants';
|
||||||
import SelectWithOptions from '../../components/SelectWithOptions';
|
import SelectWithOptions from '../../components/SelectWithOptions';
|
||||||
import useAbbreviation from './hooks/useAbbreviation';
|
import useAbbreviation from './hooks/useAbbreviation';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -5,12 +5,12 @@ import {
|
|||||||
CheckedIcon,
|
CheckedIcon,
|
||||||
IndeterminateIcon,
|
IndeterminateIcon,
|
||||||
} from '../../IconsForTable/icons';
|
} from '../../IconsForTable/icons';
|
||||||
import { ROLES_NAME_ID } from '../../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../../constants';
|
||||||
import { createFilterData } from '../../utils/filters';
|
import { createFilterData } from '../../utils/filters';
|
||||||
import {
|
import {
|
||||||
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
ROLES_ID_RUSSIAN_NAME_ARRAY as ROLES,
|
||||||
ROLES_ID_NAME,
|
ROLES_ID_NAME,
|
||||||
} from '../../../../constants/constants';
|
} from '../../../../constants';
|
||||||
import SelectWithOptions from '../../components/SelectWithOptions';
|
import SelectWithOptions from '../../components/SelectWithOptions';
|
||||||
import RowActionsMenu from './RowActionMenu';
|
import RowActionsMenu from './RowActionMenu';
|
||||||
import DepartmentCell from './DepartmentCell';
|
import DepartmentCell from './DepartmentCell';
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { CheckIcon, CheckedIcon } from '../IconsForTable/icons';
|
import { CheckIcon, CheckedIcon } from '../IconsForTable/icons';
|
||||||
import { TrashSvg } from '../../../components/common/icons/icons';
|
import { TrashSvg } from '../../../components/common/icons/icons';
|
||||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../constants';
|
||||||
|
|
||||||
const StyleForChecked = {
|
const StyleForChecked = {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { Button, Stack } from '@mui/material';
|
|||||||
import Select from '@mui/material/Select';
|
import Select from '@mui/material/Select';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||||
import { UserIcon, TrashSvg } from '../../../components/common/icons/icons';
|
import { UserIcon, TrashSvg } from '../../../components/common/icons/icons';
|
||||||
import {
|
import {
|
||||||
DangerOutlinedButton,
|
DangerOutlinedButton,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
|||||||
import { PageContainer } from '../../StyledForPage';
|
import { PageContainer } from '../../StyledForPage';
|
||||||
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
import { HeaderSwitch } from '../../../components/HeaderMenu/HeaderSwitch';
|
||||||
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
import { SwitchUserSsp } from '../../../components/AdminPanel/SwitchUserSsp';
|
||||||
import { ROLES_ID_RUSSIAN_NAME_ARRAY, ROLES_NAME_ID } from '../../../constants/constants';
|
import { ROLES_ID_RUSSIAN_NAME_ARRAY, ROLES_NAME_ID } from '../../../constants';
|
||||||
import { useAuth } from '../../../app/context/AuthProvider';
|
import { useAuth } from '../../../app/context/AuthProvider';
|
||||||
import { Box, TextField, Autocomplete, FormLabel } from '@mui/material';
|
import { Box, TextField, Autocomplete, FormLabel } from '@mui/material';
|
||||||
import SearchComponent from '../../../components/common/SearchComponent';
|
import SearchComponent from '../../../components/common/SearchComponent';
|
||||||
@ -10,7 +10,7 @@ import { toast } from 'react-toastify';
|
|||||||
import { SspApi } from '../../../api/ssp';
|
import { SspApi } from '../../../api/ssp';
|
||||||
import { UsersApi } from '../../../api/users';
|
import { UsersApi } from '../../../api/users';
|
||||||
import UserTable from './UserTable/UserTable';
|
import UserTable from './UserTable/UserTable';
|
||||||
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants/constants';
|
import { ROLES_ID_RUSSIAN_NAME } from '../../../constants';
|
||||||
import { ModalEditUserSsp } from './ModalEditUserSsp';
|
import { ModalEditUserSsp } from './ModalEditUserSsp';
|
||||||
import { ModalAddUsers } from './ModalAddUsers';
|
import { ModalAddUsers } from './ModalAddUsers';
|
||||||
import UsersActionBar from './UsersActionBar';
|
import UsersActionBar from './UsersActionBar';
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import TableDictVsp from './components/TableDictVsp';
|
|||||||
import { PageContainer } from '../StyledForPage';
|
import { PageContainer } from '../StyledForPage';
|
||||||
import { SspApi } from '../../api/ssp';
|
import { SspApi } from '../../api/ssp';
|
||||||
import { useAuth } from '../../app/context/AuthProvider';
|
import { useAuth } from '../../app/context/AuthProvider';
|
||||||
import { ROLES_NAME_ID } from '../../constants/constants';
|
import { ROLES_NAME_ID } from '../../constants';
|
||||||
import DictVspFilters from './components/DictVspFilters';
|
import DictVspFilters from './components/DictVspFilters';
|
||||||
|
|
||||||
const DictVspInfo = () => {
|
const DictVspInfo = () => {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
PrimaryButton,
|
PrimaryButton,
|
||||||
} from '../../../../components/common/Buttons/Buttons';
|
} from '../../../../components/common/Buttons/Buttons';
|
||||||
import { useAuth } from '../../../../app/context/AuthProvider';
|
import { useAuth } from '../../../../app/context/AuthProvider';
|
||||||
import { ROLES_NAME_ID } from '../../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../../constants';
|
||||||
import {
|
import {
|
||||||
ModalContainer,
|
ModalContainer,
|
||||||
FieldContainer,
|
FieldContainer,
|
||||||
@ -43,8 +43,8 @@ const ModalEditAddVsp = ({
|
|||||||
open_date: null,
|
open_date: null,
|
||||||
close_date: null,
|
close_date: null,
|
||||||
notes: '',
|
notes: '',
|
||||||
placement_type: '',
|
location_form: '',
|
||||||
staff_count: '',
|
numbers: '',
|
||||||
area: null,
|
area: null,
|
||||||
rent_contract_num: '',
|
rent_contract_num: '',
|
||||||
rent_end_date: null,
|
rent_end_date: null,
|
||||||
@ -94,8 +94,8 @@ const ModalEditAddVsp = ({
|
|||||||
? new Date(initialData.close_date)
|
? new Date(initialData.close_date)
|
||||||
: null,
|
: null,
|
||||||
notes: initialData.notes || '',
|
notes: initialData.notes || '',
|
||||||
placement_type: initialData.placement_type || '',
|
location_form: initialData.location_form || '',
|
||||||
staff_count: initialData.staff_count?.toString() || '',
|
numbers: initialData.numbers?.toString() || '',
|
||||||
area: initialData.area || null,
|
area: initialData.area || null,
|
||||||
rent_contract_num: initialData.rent_contract_num || '',
|
rent_contract_num: initialData.rent_contract_num || '',
|
||||||
rent_end_date: initialData.rent_end_date
|
rent_end_date: initialData.rent_end_date
|
||||||
@ -114,8 +114,8 @@ const ModalEditAddVsp = ({
|
|||||||
open_date: null,
|
open_date: null,
|
||||||
close_date: null,
|
close_date: null,
|
||||||
notes: '',
|
notes: '',
|
||||||
placement_type: '',
|
location_form: '',
|
||||||
staff_count: '',
|
numbers: '',
|
||||||
area: null,
|
area: null,
|
||||||
rent_contract_num: '',
|
rent_contract_num: '',
|
||||||
rent_end_date: null,
|
rent_end_date: null,
|
||||||
@ -215,7 +215,7 @@ const ModalEditAddVsp = ({
|
|||||||
|
|
||||||
const submitData = {
|
const submitData = {
|
||||||
...formData,
|
...formData,
|
||||||
staff_count: formData.staff_count ? parseInt(formData.staff_count, 10) : null,
|
numbers: formData.numbers ? parseInt(formData.numbers, 10) : null,
|
||||||
area: formData.area ? parseFloat(formData.area) : null,
|
area: formData.area ? parseFloat(formData.area) : null,
|
||||||
open_date: formData.open_date
|
open_date: formData.open_date
|
||||||
? formData.open_date.toLocaleDateString('en-CA')
|
? formData.open_date.toLocaleDateString('en-CA')
|
||||||
@ -255,8 +255,8 @@ const ModalEditAddVsp = ({
|
|||||||
open_date: null,
|
open_date: null,
|
||||||
close_date: null,
|
close_date: null,
|
||||||
notes: '',
|
notes: '',
|
||||||
placement_type: '',
|
location_form: '',
|
||||||
staff_count: '',
|
numbers: '',
|
||||||
area: '',
|
area: '',
|
||||||
rent_contract_num: '',
|
rent_contract_num: '',
|
||||||
rent_end_date: null,
|
rent_end_date: null,
|
||||||
@ -461,11 +461,11 @@ const ModalEditAddVsp = ({
|
|||||||
<FieldLabel>Форма расположения ВСП</FieldLabel>
|
<FieldLabel>Форма расположения ВСП</FieldLabel>
|
||||||
<StyledTextField
|
<StyledTextField
|
||||||
select
|
select
|
||||||
value={formData.placement_type}
|
value={formData.location_form}
|
||||||
onChange={handleChange('placement_type')}
|
onChange={handleChange('location_form')}
|
||||||
placeholder="Выберите форму расположения"
|
placeholder="Выберите форму расположения"
|
||||||
error={!!errors.placement_type}
|
error={!!errors.location_form}
|
||||||
helperText={errors.placement_type}
|
helperText={errors.location_form}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
select: {
|
select: {
|
||||||
@ -495,11 +495,11 @@ const ModalEditAddVsp = ({
|
|||||||
<FieldLabel>Штатная численность ВСП</FieldLabel>
|
<FieldLabel>Штатная численность ВСП</FieldLabel>
|
||||||
<StyledTextField
|
<StyledTextField
|
||||||
type="text"
|
type="text"
|
||||||
value={formData.staff_count}
|
value={formData.numbers}
|
||||||
onChange={handleNumberChange('staff_count')}
|
onChange={handleNumberChange('numbers')}
|
||||||
placeholder="Введите численность"
|
placeholder="Введите численность"
|
||||||
error={!!errors.staff_count}
|
error={!!errors.numbers}
|
||||||
helperText={errors.staff_count}
|
helperText={errors.numbers}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
htmlInput: { min: 1, step: 1 },
|
htmlInput: { min: 1, step: 1 },
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import {
|
|||||||
PrimaryOutlinedButton,
|
PrimaryOutlinedButton,
|
||||||
} from '../../../components/common/Buttons/Buttons';
|
} from '../../../components/common/Buttons/Buttons';
|
||||||
import { useAuth } from '../../../app/context/AuthProvider';
|
import { useAuth } from '../../../app/context/AuthProvider';
|
||||||
import { ROLES_NAME_ID } from '../../../constants/constants';
|
import { ROLES_NAME_ID } from '../../../constants';
|
||||||
import ModalExport from './Modals/ModalExport';
|
import ModalExport from './Modals/ModalExport';
|
||||||
|
|
||||||
const TableDictVsp = ({
|
const TableDictVsp = ({
|
||||||
@ -192,7 +192,7 @@ const TableDictVsp = ({
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'placement_type',
|
accessorKey: 'location_form',
|
||||||
header: 'Форма расположения ВСП',
|
header: 'Форма расположения ВСП',
|
||||||
size: 170,
|
size: 170,
|
||||||
minSize: 140,
|
minSize: 140,
|
||||||
@ -204,7 +204,7 @@ const TableDictVsp = ({
|
|||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: 'staff_count',
|
accessorKey: 'numbers',
|
||||||
header: 'Штатная численность ВСП',
|
header: 'Штатная численность ВСП',
|
||||||
size: 170,
|
size: 170,
|
||||||
minSize: 140,
|
minSize: 140,
|
||||||
|
|||||||
@ -8,13 +8,12 @@ import { useParams } from 'react-router-dom';
|
|||||||
import { RealtimeProvider } from '../components/RealtimeTable/contexts/RealtimeContext';
|
import { RealtimeProvider } from '../components/RealtimeTable/contexts/RealtimeContext';
|
||||||
import { useAuth } from '../app/context/AuthProvider';
|
import { useAuth } from '../app/context/AuthProvider';
|
||||||
import { BackButton } from '../components/common/Buttons/BackButton';
|
import { BackButton } from '../components/common/Buttons/BackButton';
|
||||||
import { SHEET_NAME } from '../constants/constants';
|
import { SHEET_NAME } from '../constants';
|
||||||
|
|
||||||
const TableInfo = ({ formId, sheetName, isProject = false }) => {
|
const TableInfo = ({ formId, sheetName }) => {
|
||||||
const path = (isProject ? `/project/` : '/task/') + formId;
|
|
||||||
const portalContent = (
|
const portalContent = (
|
||||||
<TaskInfoContainer>
|
<TaskInfoContainer>
|
||||||
<BackButton to={path} />
|
<BackButton to={`/task/${formId}`} />
|
||||||
{sheetName && (
|
{sheetName && (
|
||||||
<NameTask>{SHEET_NAME[sheetName] || sheetName}</NameTask>
|
<NameTask>{SHEET_NAME[sheetName] || sheetName}</NameTask>
|
||||||
)}
|
)}
|
||||||
@ -29,29 +28,20 @@ const TableInfo = ({ formId, sheetName, isProject = false }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function NewTablePage() {
|
export default function NewTablePage() {
|
||||||
const { formId, formType, sheetName, direction, year } = useParams();
|
const { formId, formType, sheetName, direction } = useParams();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const isProject = formType == 'PROJECT';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{formId && <TableInfo formId={formId} sheetName={sheetName} isProject={isProject} />}
|
{formId && <TableInfo formId={formId} sheetName={sheetName} />}
|
||||||
{user && (
|
{user && (
|
||||||
<RealtimeProvider
|
<RealtimeProvider
|
||||||
formId={formId}
|
formId={formId}
|
||||||
sheetName={sheetName}
|
sheetName={sheetName}
|
||||||
userId={user.id}
|
userId={user.id}
|
||||||
direction={direction === 'null' ? null : direction}
|
direction={direction === 'null' ? null : direction}
|
||||||
isProject={isProject}
|
|
||||||
year={year}
|
|
||||||
>
|
>
|
||||||
<RealtimeTable
|
<RealtimeTable formType={formType} formId={formId} sheetName={sheetName} direction={direction === 'null' ? null : direction} />
|
||||||
formType={formType}
|
|
||||||
formId={formId}
|
|
||||||
sheetName={sheetName}
|
|
||||||
direction={direction === 'null' ? null : direction}
|
|
||||||
year={year}
|
|
||||||
/>
|
|
||||||
</RealtimeProvider>
|
</RealtimeProvider>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@ -1,349 +0,0 @@
|
|||||||
// src/components/common/ProjectCardComponent/ProjectCardComponent.jsx
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
TextField,
|
|
||||||
FormControl,
|
|
||||||
FormLabel,
|
|
||||||
Box,
|
|
||||||
Stack,
|
|
||||||
Chip,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { ProjectsApi } from '../../api/projects';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import {
|
|
||||||
CheckBoxCheckSvg,
|
|
||||||
CheckBoxUncheckSvg,
|
|
||||||
DateIcon,
|
|
||||||
Edit,
|
|
||||||
ExportSvg,
|
|
||||||
UserIcon,
|
|
||||||
BuildingIcon,
|
|
||||||
LocationIcon,
|
|
||||||
} from '../../components/common/icons/icons';
|
|
||||||
import { IconWithContent } from '../../components/common/IconWithContent';
|
|
||||||
import { ExportButton } from '../../components/common/Buttons/ButtonsActions';
|
|
||||||
import { ModalCreateProject } from './ModalCreateProject';
|
|
||||||
|
|
||||||
const ProjectCard = styled.div`
|
|
||||||
width: 26.8rem;
|
|
||||||
height: 15rem;
|
|
||||||
display: flex;
|
|
||||||
padding: 1.25rem;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 0.06rem solid var(--Stroke, rgba(0, 0, 0, 0.1));
|
|
||||||
border-radius: 1rem;
|
|
||||||
box-shadow:
|
|
||||||
0rem 2rem 4rem -2rem rgba(0, 0, 0, 0.02),
|
|
||||||
0rem 4rem 6rem -1rem rgba(0, 0, 0, 0.02);
|
|
||||||
background: var(--White, rgba(255, 255, 255, 1));
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CardHeader = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ProjectId = styled.div`
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1rem;
|
|
||||||
letter-spacing: 0rem;
|
|
||||||
text-align: left;
|
|
||||||
color: rgba(0, 0, 0, 0.6);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const CardBody = styled.div`
|
|
||||||
width: 100%;
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.8125rem;
|
|
||||||
justify-content: flex-end;
|
|
||||||
height: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ProjectTitle = styled.div`
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: var(--Text-Primary, #333);
|
|
||||||
line-height: 1.63;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StatusChip = styled(Chip)`
|
|
||||||
font-size: 0.7rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
background-color: ${({ status }) => {
|
|
||||||
switch (status) {
|
|
||||||
case 'active': return '#4caf50';
|
|
||||||
case 'completed': return '#2196f3';
|
|
||||||
case 'paused': return '#ff9800';
|
|
||||||
case 'archived': return '#9e9e9e';
|
|
||||||
default: return '#9e9e9e';
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
color: white;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const LevelChip = styled(Chip)`
|
|
||||||
font-size: 0.6rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
background-color: rgba(0, 0, 0, 0.08);
|
|
||||||
color: rgba(0, 0, 0, 0.7);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const ProjectInfoGrid = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InfoChip = styled(Chip)`
|
|
||||||
font-size: 0.6rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
|
||||||
color: rgba(0, 0, 0, 0.7);
|
|
||||||
`;
|
|
||||||
|
|
||||||
const getStatusLabel = (status) => {
|
|
||||||
const statusMap = {
|
|
||||||
active: 'Активный',
|
|
||||||
completed: 'Завершен',
|
|
||||||
paused: 'Приостановлен',
|
|
||||||
archived: 'Архивирован',
|
|
||||||
};
|
|
||||||
return statusMap[status] || status || 'Не указан';
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ProjectCardComponent = ({
|
|
||||||
project,
|
|
||||||
projectDataInit,
|
|
||||||
exportMode = false,
|
|
||||||
isLoadingFile = false,
|
|
||||||
onAddForExport,
|
|
||||||
onExportClick,
|
|
||||||
}) => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
org_unit_name,
|
|
||||||
years,
|
|
||||||
status,
|
|
||||||
level,
|
|
||||||
project_type,
|
|
||||||
vsp_format,
|
|
||||||
placement_type,
|
|
||||||
object_address,
|
|
||||||
staff_count,
|
|
||||||
total_area,
|
|
||||||
report_count,
|
|
||||||
parent_id,
|
|
||||||
} = project;
|
|
||||||
|
|
||||||
const [isEditModalOpen, setEditModalOpen] = useState(false);
|
|
||||||
const [currentName, setCurrentName] = useState(name || '');
|
|
||||||
const [isChecked, setIsChecked] = useState(false);
|
|
||||||
const [editFormData, setEditFormData] = useState({});
|
|
||||||
const [projectData, setProjectData] = useState(projectDataInit);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsChecked(false);
|
|
||||||
}, [exportMode]);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Функция для подготовки данных проекта к редактированию
|
|
||||||
const prepareProjectDataForEdit = () => {
|
|
||||||
return {
|
|
||||||
'ССП/РФ': project.org_unit_id || null,
|
|
||||||
'Год': years || null,
|
|
||||||
'Тип проекта развития': project_type || null,
|
|
||||||
'Название проекта': currentName,
|
|
||||||
'Формат ВСП': vsp_format || null,
|
|
||||||
'Адрес объекта': object_address || '',
|
|
||||||
'Целевая численность, чел.': staff_count || 0,
|
|
||||||
'Тип размещения': placement_type || null,
|
|
||||||
'Площадь размещения, м2': total_area || 0,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleOpenEdit = (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
setEditFormData(prepareProjectDataForEdit());
|
|
||||||
setEditModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCloseEdit = () => {
|
|
||||||
setEditModalOpen(false);
|
|
||||||
setEditFormData({});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckboxClick = (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
if (!isLoadingFile) {
|
|
||||||
setIsChecked(!isChecked);
|
|
||||||
onAddForExport(!isChecked);
|
|
||||||
} else {
|
|
||||||
toast.warning('Файл формируется!');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExportProject = (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
onExportClick?.(id, name);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleNavigate = () => {
|
|
||||||
if (!exportMode) {
|
|
||||||
navigate(`/project/${id}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Обновленная функция обновления проекта
|
|
||||||
const updateProject = async (formData) => {
|
|
||||||
try {
|
|
||||||
const projectData = {
|
|
||||||
name: formData['Название проекта'] || currentName,
|
|
||||||
project_type: formData['Тип проекта развития'] || null,
|
|
||||||
vsp_format: formData['Формат ВСП'] || null,
|
|
||||||
object_address: formData['Адрес объекта'] || '',
|
|
||||||
staff_count: Number(formData['Целевая численность, чел.']) || 0,
|
|
||||||
placement_type: formData['Тип размещения'] || null,
|
|
||||||
total_area: Number(formData['Площадь размещения, м2']) || 0,
|
|
||||||
year: formData['Год'] ? Number(formData['Год']) : null,
|
|
||||||
branch_id: formData['ССП/РФ'] || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
await ProjectsApi.update(id, projectData);
|
|
||||||
|
|
||||||
// Обновляем локальные данные
|
|
||||||
setCurrentName(projectData.name);
|
|
||||||
setEditModalOpen(false);
|
|
||||||
toast.success('Данные проекта успешно обновлены');
|
|
||||||
|
|
||||||
// Можно добавить рефреш данных или перезагрузку страницы
|
|
||||||
window.location.reload(); // или используйте callback для обновления списка
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(
|
|
||||||
error?.response?.data?.detail || 'Не удалось обновить данные проекта',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveProject = async (formData) => {
|
|
||||||
await updateProject(formData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getInitials = (name) => {
|
|
||||||
if (!name) return '?';
|
|
||||||
const words = name.split(' ');
|
|
||||||
if (words.length >= 2) {
|
|
||||||
return `${words[0][0]}${words[1][0]}`.toUpperCase();
|
|
||||||
}
|
|
||||||
return name.substring(0, 2).toUpperCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<ProjectCard onClick={handleNavigate} key={id}>
|
|
||||||
<Box sx={{ width: '100%' }}>
|
|
||||||
<CardHeader>
|
|
||||||
<ProjectId>ID: {id}</ProjectId>
|
|
||||||
<Stack direction={'row'} spacing={'.5rem'} sx={{
|
|
||||||
|
|
||||||
}} >
|
|
||||||
{!exportMode && (
|
|
||||||
<>
|
|
||||||
{status && (
|
|
||||||
<StatusChip
|
|
||||||
status={status}
|
|
||||||
label={getStatusLabel(status)}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{/* {Пока нет редактирование + экспорт} */}
|
|
||||||
{/* <ExportButton onClick={handleExportProject} />
|
|
||||||
<Edit onClick={handleOpenEdit} /> */}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<ProjectTitle>{currentName}</ProjectTitle>
|
|
||||||
|
|
||||||
<ProjectInfoGrid>
|
|
||||||
{project_type && (
|
|
||||||
<InfoChip label={`Тип: ${project_type}`} size="small" />
|
|
||||||
)}
|
|
||||||
{report_count !== undefined && report_count !== null && (
|
|
||||||
<InfoChip label={`Отчетов: ${report_count}`} size="small" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
</ProjectInfoGrid>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Stack
|
|
||||||
sx={{
|
|
||||||
direction: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: '100%',
|
|
||||||
alignItems: 'end',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CardBody>
|
|
||||||
<IconWithContent icon={<DateIcon />}>
|
|
||||||
{years.join(', ') || 'Год не указан'}
|
|
||||||
</IconWithContent>
|
|
||||||
<IconWithContent icon={<UserIcon />}>
|
|
||||||
{org_unit_name || 'Организация не указана'}
|
|
||||||
</IconWithContent>
|
|
||||||
</CardBody>
|
|
||||||
|
|
||||||
{exportMode && (
|
|
||||||
<div>
|
|
||||||
{isChecked ? (
|
|
||||||
<CheckBoxCheckSvg size="1.625rem" />
|
|
||||||
) : (
|
|
||||||
<CheckBoxUncheckSvg size="1.625rem" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Stack>
|
|
||||||
</ProjectCard>
|
|
||||||
|
|
||||||
<ModalCreateProject
|
|
||||||
open={isEditModalOpen}
|
|
||||||
onClose={handleCloseEdit}
|
|
||||||
onCreate={handleSaveProject}
|
|
||||||
config={projectData}
|
|
||||||
initialData={editFormData}
|
|
||||||
isEdit={true}
|
|
||||||
projectId={id}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,298 +0,0 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { ProjectsApi } from '../../api/projects';
|
|
||||||
import { CardsSkeleton } from '../../components/common/Skeleton';
|
|
||||||
import SearchComponent from '../../components/common/SearchComponent';
|
|
||||||
import { ProjectCardComponent } from './ProjectCard';
|
|
||||||
import { HeaderSwitch } from '../../components/HeaderMenu/HeaderSwitch';
|
|
||||||
import { PageContainer, TasksContainer } from '../StyledForPage';
|
|
||||||
import {
|
|
||||||
Box,
|
|
||||||
FormControl,
|
|
||||||
Button,
|
|
||||||
Stack,
|
|
||||||
Pagination,
|
|
||||||
PaginationItem,
|
|
||||||
Select,
|
|
||||||
MenuItem,
|
|
||||||
Typography,
|
|
||||||
IconButton,
|
|
||||||
Tooltip,
|
|
||||||
TextField,
|
|
||||||
Autocomplete,
|
|
||||||
} from '@mui/material';
|
|
||||||
import { toast } from 'react-toastify';
|
|
||||||
import { ModalCreateProject } from './ModalCreateProject';
|
|
||||||
import {
|
|
||||||
ROLES_NAME_ID,
|
|
||||||
} from '../../constants/constants';
|
|
||||||
import { useAuth } from '../../app/context/AuthProvider';
|
|
||||||
import { PrimaryButton, PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
|
|
||||||
import {
|
|
||||||
ExportExitButton,
|
|
||||||
ExportWithTextButton,
|
|
||||||
} from '../../components/common/Buttons/ButtonsActions';
|
|
||||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportFile';
|
|
||||||
import { WhitePlus } from '../../components/common/icons/icons';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { PaginatedList } from '../../components/common/PaginatedList';
|
|
||||||
import { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
|
|
||||||
import { SspApi } from '../../api/ssp';
|
|
||||||
import NavigationToggle from '../../components/common/NavigationToggle';
|
|
||||||
|
|
||||||
export { DEFAULT_PROJECT_CONFIG } from '../../constants/projectConfig';
|
|
||||||
|
|
||||||
export default function ProjectsPage() {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const [items, setItems] = useState([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
|
||||||
|
|
||||||
// Новые фильтры
|
|
||||||
const [yearFilter, setYearFilter] = useState('');
|
|
||||||
const [branchFilter, setBranchFilter] = useState('');
|
|
||||||
|
|
||||||
// Pagination state
|
|
||||||
const [page, setPage] = useState(1);
|
|
||||||
const [limit, setLimit] = useState(20);
|
|
||||||
const [totalCount, setTotalCount] = useState(0);
|
|
||||||
|
|
||||||
const { user } = useAuth();
|
|
||||||
|
|
||||||
const [exportMode, setExportMode] = useState(false);
|
|
||||||
const [exportList, setExportList] = useState([]);
|
|
||||||
|
|
||||||
const [projectData, setProjectData] = useState(DEFAULT_PROJECT_CONFIG);
|
|
||||||
|
|
||||||
// Получаем список годов для фильтра (можно динамически или задать диапазон)
|
|
||||||
const yearOptions = useMemo(() => {
|
|
||||||
const currentYear = new Date().getFullYear();
|
|
||||||
const years = [];
|
|
||||||
for (let year = currentYear - 5; year <= currentYear + 2; year++) {
|
|
||||||
years.push(year);
|
|
||||||
}
|
|
||||||
return years;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!user) return;
|
|
||||||
if (user.role_id == ROLES_NAME_ID.admin) {
|
|
||||||
SspApi.getAll().then(data => {
|
|
||||||
if (data.success) {
|
|
||||||
setProjectData(prev => ({
|
|
||||||
...prev, 'ССП/РФ': {
|
|
||||||
...prev['ССП/РФ'],
|
|
||||||
options: data.result
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
toast.error('Ошибка при получении ССП/РФ');
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setProjectData(prev => ({
|
|
||||||
...prev, 'ССП/РФ': {
|
|
||||||
...prev['ССП/РФ'],
|
|
||||||
options: user.org_units,
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}, [user])
|
|
||||||
|
|
||||||
const openModal = () => {
|
|
||||||
setModalOpen(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapFormDataToApi = (formData) => {
|
|
||||||
const apiData = {
|
|
||||||
name: formData['Название проекта'] || '',
|
|
||||||
project_type: formData['Тип проекта развития'] || null,
|
|
||||||
vsp_format: formData['Формат ВСП'] || null,
|
|
||||||
object_address: formData['Адрес объекта'] || '',
|
|
||||||
staff_count: formData['Целевая численность, чел.'] || 0,
|
|
||||||
placement_type: formData['Тип размещения'] || null,
|
|
||||||
total_area: formData['Площадь размещения, м2'] || 0,
|
|
||||||
year: formData['Год'] || null,
|
|
||||||
branch_id: formData['ССП/РФ'] || null,
|
|
||||||
|
|
||||||
};
|
|
||||||
return apiData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCreateProject = async (formData) => {
|
|
||||||
try {
|
|
||||||
const apiData = mapFormDataToApi(formData);
|
|
||||||
|
|
||||||
await ProjectsApi.create(apiData);
|
|
||||||
|
|
||||||
setPage(1);
|
|
||||||
loadProjects(1, limit);
|
|
||||||
|
|
||||||
setModalOpen(false);
|
|
||||||
toast.success('Проект успешно создан');
|
|
||||||
} catch (error) {
|
|
||||||
toast.error(
|
|
||||||
error.response?.data?.detail ||
|
|
||||||
error.response?.data?.message ||
|
|
||||||
'Ошибка при создании проекта',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const loadProjects = async (currentPage = page, currentLimit = limit) => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const skip = (currentPage - 1) * currentLimit;
|
|
||||||
const params = {
|
|
||||||
offset: skip,
|
|
||||||
limit: currentLimit,
|
|
||||||
// Добавляем фильтры в параметры запроса
|
|
||||||
...(yearFilter && { year: parseInt(yearFilter) }),
|
|
||||||
...(branchFilter && { branch_id: parseInt(branchFilter.id) }),
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await ProjectsApi.list(params);
|
|
||||||
const data = res.result;
|
|
||||||
|
|
||||||
setItems(data);
|
|
||||||
setTotalCount(res.count);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
toast.error('Ошибка загрузки проектов');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Сбрасываем страницу при изменении любых фильтров
|
|
||||||
useEffect(() => {
|
|
||||||
setPage(1);
|
|
||||||
loadProjects(1, limit);
|
|
||||||
}, [yearFilter, branchFilter]);
|
|
||||||
|
|
||||||
const handlePageChange = (event, value) => {
|
|
||||||
setPage(value);
|
|
||||||
loadProjects(value, limit);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLimitChange = (event) => {
|
|
||||||
const newLimit = event.target.value;
|
|
||||||
setLimit(newLimit);
|
|
||||||
setPage(1);
|
|
||||||
loadProjects(1, newLimit);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Сброс всех фильтров
|
|
||||||
const handleClearFilters = () => {
|
|
||||||
setYearFilter('');
|
|
||||||
setBranchFilter('');
|
|
||||||
setPage(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
loadProjects(page, limit);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setExportList([]);
|
|
||||||
}, [exportMode]);
|
|
||||||
|
|
||||||
const handleChangeToExportList = (projectId, checked) => {
|
|
||||||
if (checked) {
|
|
||||||
setExportList((prev) => [...prev, projectId]);
|
|
||||||
} else {
|
|
||||||
const newExportList = exportList.filter((t) => t != projectId);
|
|
||||||
setExportList(newExportList);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBulkExport = async () => {
|
|
||||||
await exportBulkForms(exportList);
|
|
||||||
setExportMode(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSingleExport = (projectId, projectTitle) => {
|
|
||||||
exportSingleForm(projectId, projectTitle);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleRefresh = () => {
|
|
||||||
loadProjects(page, limit);
|
|
||||||
toast.info('Список проектов обновлен');
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!user) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<PageContainer>
|
|
||||||
<HeaderSwitch />
|
|
||||||
<NavigationToggle currentView={'projects'} onViewChange={(path) => navigate(path)} />
|
|
||||||
|
|
||||||
<PaginatedList
|
|
||||||
items={items}
|
|
||||||
loading={loading}
|
|
||||||
totalCount={totalCount}
|
|
||||||
page={page}
|
|
||||||
limit={limit}
|
|
||||||
onPageChange={handlePageChange}
|
|
||||||
onLimitChange={handleLimitChange}
|
|
||||||
entityName="проектов"
|
|
||||||
emptyMessage="Проекты не найдены"
|
|
||||||
renderItem={(project, exportProps) => (
|
|
||||||
<ProjectCardComponent
|
|
||||||
key={project.id}
|
|
||||||
project={project}
|
|
||||||
projectDataInit={projectData}
|
|
||||||
{...exportProps}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
getItemId={(project) => project.id}
|
|
||||||
getItemTitle={(project) => project.name || 'Проект'}
|
|
||||||
extraActions={
|
|
||||||
user.role_id === ROLES_NAME_ID.admin && (
|
|
||||||
<PrimaryButton onClick={openModal} startIcon={<WhitePlus />} height='2.5rem' >
|
|
||||||
Создать проект
|
|
||||||
</PrimaryButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
filterActions={
|
|
||||||
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap', alignItems: 'center', height: '2.5rem' }}>
|
|
||||||
<Select
|
|
||||||
size="small"
|
|
||||||
displayEmpty
|
|
||||||
value={yearFilter}
|
|
||||||
onChange={(e) => setYearFilter(e.target.value)}
|
|
||||||
sx={{ width: 130 }}
|
|
||||||
>
|
|
||||||
<MenuItem value="">Все годы</MenuItem>
|
|
||||||
{yearOptions.map((year) => (
|
|
||||||
<MenuItem key={year} value={year}>{year}</MenuItem>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Autocomplete
|
|
||||||
size="small"
|
|
||||||
options={projectData['ССП/РФ']?.options ?? []}
|
|
||||||
noOptionsText="Не найдено"
|
|
||||||
getOptionLabel={(option) => option.title || option.name || '-'}
|
|
||||||
value={branchFilter}
|
|
||||||
onChange={(event, newValue) => setBranchFilter(newValue)}
|
|
||||||
sx={{ width: 200 }}
|
|
||||||
renderInput={(params) => (
|
|
||||||
<TextField {...params} placeholder="Все" size="small" />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<PrimaryOutlinedButton onClick={handleClearFilters} height="100%"> Сбросить</PrimaryOutlinedButton>
|
|
||||||
</Box>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</PageContainer>
|
|
||||||
|
|
||||||
<ModalCreateProject
|
|
||||||
open={modalOpen}
|
|
||||||
onClose={() => setModalOpen(false)}
|
|
||||||
onCreate={handleCreateProject}
|
|
||||||
config={projectData}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,8 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
const PageContainer = styled.div`
|
const PageContainer = styled.div`
|
||||||
padding: 2rem;
|
padding: 2.5rem;
|
||||||
padding-top: 1rem;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export default function TablePage() {
|
|||||||
|
|
||||||
const connection = connectToTable(tableId, user?.id, {
|
const connection = connectToTable(tableId, user?.id, {
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error('Ошибка подключения к серверу');
|
toast.error('Ошибка подключения к серверу реального времени');
|
||||||
},
|
},
|
||||||
onClose: (event) => {
|
onClose: (event) => {
|
||||||
if (isUnmountingRef.current) {
|
if (isUnmountingRef.current) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState } from 'react';
|
||||||
import { Box, Button, Divider, TextField, Typography } from '@mui/material';
|
import { Box, Button, Divider, TextField, Typography } from '@mui/material';
|
||||||
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
|
import SelectWithOptions from '../AdminPanel/components/SelectWithOptions';
|
||||||
import Modal from '../../components/common/Modal/Modal';
|
import Modal from '../../components/common/Modal/Modal';
|
||||||
@ -9,98 +9,25 @@ export const ModalCreateProject = ({
|
|||||||
onClose,
|
onClose,
|
||||||
onCreate,
|
onCreate,
|
||||||
config = {},
|
config = {},
|
||||||
initialData = null,
|
|
||||||
title = 'Создание проекта',
|
|
||||||
buttonText = 'Создать',
|
|
||||||
}) => {
|
}) => {
|
||||||
const [projectData, setProjectData] = useState(() => {
|
const [projectData, setProjectData] = useState(() => {
|
||||||
if (initialData) {
|
const initialData = {};
|
||||||
return { ...initialData };
|
|
||||||
}
|
|
||||||
// Иначе создаем из конфига
|
|
||||||
const data = {};
|
|
||||||
Object.keys(config).forEach((key) => {
|
Object.keys(config).forEach((key) => {
|
||||||
data[key] = config[key].defaultValue;
|
initialData[key] = config[key].defaultValue;
|
||||||
});
|
});
|
||||||
return data;
|
return initialData;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [errors, setErrors] = useState({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (initialData) {
|
|
||||||
setProjectData({ ...initialData });
|
|
||||||
} else {
|
|
||||||
const data = {};
|
|
||||||
Object.keys(config).forEach((key) => {
|
|
||||||
data[key] = config[key].defaultValue;
|
|
||||||
});
|
|
||||||
setProjectData(data);
|
|
||||||
}
|
|
||||||
setErrors({});
|
|
||||||
}, [initialData, config, open]);
|
|
||||||
|
|
||||||
const handleChange = (field, value) => {
|
const handleChange = (field, value) => {
|
||||||
setProjectData((prev) => ({
|
setProjectData((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[field]: value,
|
[field]: value,
|
||||||
}));
|
}));
|
||||||
if (errors[field]) {
|
|
||||||
setErrors((prev) => ({
|
|
||||||
...prev,
|
|
||||||
[field]: undefined,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateField = (fieldName, config) => {
|
|
||||||
if (config.required_field) {
|
|
||||||
const value = projectData[fieldName];
|
|
||||||
|
|
||||||
if (config.type === 'multiselect') {
|
|
||||||
if (!value || (Array.isArray(value) && value.length === 0) || value === '') {
|
|
||||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
|
||||||
}
|
|
||||||
} else if (config.type === 'text') {
|
|
||||||
if (!value || value.trim() === '') {
|
|
||||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
|
||||||
}
|
|
||||||
} else if (config.type === 'number') {
|
|
||||||
if (value === null || value === undefined || value === '') {
|
|
||||||
return `Поле "${fieldName}" обязательно для заполнения`;
|
|
||||||
}
|
|
||||||
if (config.min !== undefined && Number(value) < config.min) {
|
|
||||||
return `Значение должно быть не меньше ${config.min}`;
|
|
||||||
}
|
|
||||||
if (config.max !== undefined && Number(value) > config.max) {
|
|
||||||
return `Значение должно быть не больше ${config.max}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateAllFields = () => {
|
|
||||||
const newErrors = {};
|
|
||||||
let hasErrors = false;
|
|
||||||
|
|
||||||
Object.entries(config).forEach(([fieldName, fieldConfig]) => {
|
|
||||||
const error = validateField(fieldName, fieldConfig);
|
|
||||||
if (error) {
|
|
||||||
newErrors[fieldName] = error;
|
|
||||||
hasErrors = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setErrors(newErrors);
|
|
||||||
return !hasErrors;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickCreate = () => {
|
const handleClickCreate = () => {
|
||||||
if (validateAllFields()) {
|
onCreate(projectData);
|
||||||
onCreate(projectData);
|
onClose();
|
||||||
onClose();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@ -109,14 +36,10 @@ export const ModalCreateProject = ({
|
|||||||
resetData[key] = config[key].defaultValue;
|
resetData[key] = config[key].defaultValue;
|
||||||
});
|
});
|
||||||
setProjectData(resetData);
|
setProjectData(resetData);
|
||||||
setErrors({});
|
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderField = (fieldName, config) => {
|
const renderField = (fieldName, config) => {
|
||||||
const hasError = !!errors[fieldName];
|
|
||||||
const errorText = errors[fieldName];
|
|
||||||
|
|
||||||
switch (config.type) {
|
switch (config.type) {
|
||||||
case 'multiselect':
|
case 'multiselect':
|
||||||
return (
|
return (
|
||||||
@ -127,13 +50,9 @@ export const ModalCreateProject = ({
|
|||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
paddingBottom: '.25rem',
|
paddingBottom: '.25rem',
|
||||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fieldName}
|
{fieldName}
|
||||||
{config.required_field && (
|
|
||||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
|
||||||
)}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<SelectWithOptions
|
<SelectWithOptions
|
||||||
className="sameSize"
|
className="sameSize"
|
||||||
@ -142,22 +61,7 @@ export const ModalCreateProject = ({
|
|||||||
placeholder={config.placeholder}
|
placeholder={config.placeholder}
|
||||||
onChange={(value) => handleChange(fieldName, value)}
|
onChange={(value) => handleChange(fieldName, value)}
|
||||||
width="28.25rem"
|
width="28.25rem"
|
||||||
style={{
|
|
||||||
borderColor: hasError ? 'var(--error-color, #d32f2f)' : undefined,
|
|
||||||
borderWidth: hasError ? '2px' : undefined,
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{hasError && (
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: 'var(--error-color, #d32f2f)',
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
marginTop: '4px',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{errorText}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -169,14 +73,11 @@ export const ModalCreateProject = ({
|
|||||||
sx={{
|
sx={{
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
color: 'var(--grey-dark)',
|
||||||
paddingBottom: '.25rem',
|
paddingBottom: '.25rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fieldName}
|
{fieldName}
|
||||||
{config.required_field && (
|
|
||||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
|
||||||
)}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
className="sameSize"
|
className="sameSize"
|
||||||
@ -184,14 +85,6 @@ export const ModalCreateProject = ({
|
|||||||
onChange={(e) => handleChange(fieldName, e.target.value)}
|
onChange={(e) => handleChange(fieldName, e.target.value)}
|
||||||
placeholder={config.placeholder}
|
placeholder={config.placeholder}
|
||||||
size="small"
|
size="small"
|
||||||
error={hasError}
|
|
||||||
helperText={hasError ? errorText : ''}
|
|
||||||
FormHelperTextProps={{
|
|
||||||
sx: {
|
|
||||||
marginLeft: 0,
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -204,14 +97,11 @@ export const ModalCreateProject = ({
|
|||||||
sx={{
|
sx={{
|
||||||
fontWeight: '400',
|
fontWeight: '400',
|
||||||
fontSize: '0.75rem',
|
fontSize: '0.75rem',
|
||||||
color: hasError ? 'var(--error-color, #d32f2f)' : 'var(--grey-dark)',
|
color: 'var(--grey-dark)',
|
||||||
paddingBottom: '.25rem',
|
paddingBottom: '.25rem',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{fieldName}
|
{fieldName}
|
||||||
{config.required_field && (
|
|
||||||
<span style={{ color: 'var(--error-color, #d32f2f)', marginLeft: '4px' }}>*</span>
|
|
||||||
)}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
className="sameSize"
|
className="sameSize"
|
||||||
@ -219,6 +109,7 @@ export const ModalCreateProject = ({
|
|||||||
value={projectData[fieldName] || ''}
|
value={projectData[fieldName] || ''}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const inputValue = e.target.value;
|
const inputValue = e.target.value;
|
||||||
|
//только числа
|
||||||
const isValidInput =
|
const isValidInput =
|
||||||
/^\d*\.?\d*$/.test(inputValue) || inputValue === '';
|
/^\d*\.?\d*$/.test(inputValue) || inputValue === '';
|
||||||
if (isValidInput) {
|
if (isValidInput) {
|
||||||
@ -227,8 +118,6 @@ export const ModalCreateProject = ({
|
|||||||
}}
|
}}
|
||||||
placeholder={config.placeholder}
|
placeholder={config.placeholder}
|
||||||
size="small"
|
size="small"
|
||||||
error={hasError}
|
|
||||||
helperText={hasError ? errorText : ''}
|
|
||||||
slotProps={{
|
slotProps={{
|
||||||
htmlInput: {
|
htmlInput: {
|
||||||
min: config.min,
|
min: config.min,
|
||||||
@ -236,12 +125,6 @@ export const ModalCreateProject = ({
|
|||||||
step: config.step || 1,
|
step: config.step || 1,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
FormHelperTextProps={{
|
|
||||||
sx: {
|
|
||||||
marginLeft: 0,
|
|
||||||
fontSize: '0.75rem',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@ -253,7 +136,7 @@ export const ModalCreateProject = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={title}
|
title={'Создание проекта'}
|
||||||
open={open}
|
open={open}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
customSize={'43.75'}
|
customSize={'43.75'}
|
||||||
@ -271,7 +154,7 @@ export const ModalCreateProject = ({
|
|||||||
onClick={handleClickCreate}
|
onClick={handleClickCreate}
|
||||||
style={{ height: '2.5rem' }}
|
style={{ height: '2.5rem' }}
|
||||||
>
|
>
|
||||||
{buttonText}
|
Создать
|
||||||
</PrimaryButton>
|
</PrimaryButton>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
@ -297,4 +180,4 @@ export const ModalCreateProject = ({
|
|||||||
<Divider />
|
<Divider />
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -3,7 +3,6 @@ import { createPortal } from 'react-dom';
|
|||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { TasksApi } from '../../api/tasks';
|
import { TasksApi } from '../../api/tasks';
|
||||||
import { TablesApi } from '../../api/tables';
|
import { TablesApi } from '../../api/tables';
|
||||||
import { ProjectsApi } from '../../api/projects'; // Добавьте импорт API для проектов
|
|
||||||
import {
|
import {
|
||||||
TaskInfoContainer,
|
TaskInfoContainer,
|
||||||
NameTask,
|
NameTask,
|
||||||
@ -19,16 +18,17 @@ import { toast } from 'react-toastify';
|
|||||||
import { SettingModal as SettingStageModal } from '../../components/Stages/SettingModal';
|
import { SettingModal as SettingStageModal } from '../../components/Stages/SettingModal';
|
||||||
import { PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
|
import { PrimaryOutlinedButton } from '../../components/common/Buttons/Buttons';
|
||||||
import { FormsApi } from '../../api/forms';
|
import { FormsApi } from '../../api/forms';
|
||||||
|
import { ModalCreateProject } from './ModalCreateProject';
|
||||||
import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions';
|
import { ExportWithTextButton } from '../../components/common/Buttons/ButtonsActions';
|
||||||
import { exportSingleForm } from '../../utils/exportFile';
|
import { exportSingleForm } from '../../utils/exportForm';
|
||||||
import TablesList from '../../components/common/TableList/TableList';
|
import TablesList from '../../components/common/TableList/TableList';
|
||||||
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants/constants';
|
import { DIRECTION_TRANSLATE, FORM_TYPE_TRANSLATE, SHEET_NAME } from '../../constants';
|
||||||
|
|
||||||
const TaskInfo = ({ task, isProject }) => {
|
const TaskInfo = ({ task }) => {
|
||||||
const portalContent = (
|
const portalContent = (
|
||||||
<TaskInfoContainer>
|
<TaskInfoContainer>
|
||||||
<BackButton to={isProject ? "/projects" : "/tasks"} />
|
<BackButton to="/tasks" />
|
||||||
<NameTask>{isProject ? 'Проект' : FORM_TYPE_TRANSLATE[task.form_type_code] || ''}</NameTask>
|
<NameTask>{FORM_TYPE_TRANSLATE[task.form_type_code] || ''}</NameTask>
|
||||||
</TaskInfoContainer>
|
</TaskInfoContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -40,13 +40,9 @@ const TaskInfo = ({ task, isProject }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function TaskPage() {
|
export default function TaskPage() {
|
||||||
const { taskId, projectId } = useParams();
|
const { taskId } = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const id = Number(taskId);
|
||||||
// Определяем, что у нас за ID и какой тип
|
|
||||||
const isProject = !!projectId;
|
|
||||||
const id = isProject ? Number(projectId) : Number(taskId);
|
|
||||||
|
|
||||||
const [task, setTask] = useState(null);
|
const [task, setTask] = useState(null);
|
||||||
const [tables, setTables] = useState([]);
|
const [tables, setTables] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -55,42 +51,23 @@ export default function TaskPage() {
|
|||||||
const [modalProjectOpen, setModalProjectOpen] = useState(false);
|
const [modalProjectOpen, setModalProjectOpen] = useState(false);
|
||||||
const [tempProjects, setTempProjects] = useState([]);
|
const [tempProjects, setTempProjects] = useState([]);
|
||||||
const [projects, setProjects] = useState([]);
|
const [projects, setProjects] = useState([]);
|
||||||
const [isProjectData, setIsProjectData] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getData = async () => {
|
const getSheets = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
if (isProject) {
|
const data = await TasksApi.getSheets(id);
|
||||||
// Получаем информацию о проекте
|
// Преобразуем данные с учетом direction
|
||||||
const projectData = await ProjectsApi.get(id);
|
const mappedTables = data.result.all_sheets.map(sheet => ({
|
||||||
setTask(projectData.result);
|
name: SHEET_NAME[sheet.sheet_name] || sheet.sheet_name,
|
||||||
setIsProjectData(true);
|
sheet: sheet.sheet_name,
|
||||||
|
direction: sheet.direction,
|
||||||
const mappedTables = projectData.result.reports.map(sheet => ({
|
}));
|
||||||
name: SHEET_NAME[sheet.report_type] || sheet.report_type,
|
//пока скрыт
|
||||||
sheet: sheet.report_type,
|
setTables(mappedTables.filter(t => t.sheet !== "OTCH9F",));
|
||||||
year: sheet.year,
|
|
||||||
}));
|
|
||||||
setTables(mappedTables);
|
|
||||||
} else {
|
|
||||||
// Получаем информацию о задаче
|
|
||||||
const taskData = await TasksApi.getById(id);
|
|
||||||
setTask(taskData.result);
|
|
||||||
setIsProjectData(false);
|
|
||||||
|
|
||||||
// Получаем таблицы задачи
|
|
||||||
const tablesData = await TasksApi.getSheets(id);
|
|
||||||
const mappedTables = tablesData.result.all_sheets.map(sheet => ({
|
|
||||||
name: SHEET_NAME[sheet.sheet_name] || sheet.sheet_name,
|
|
||||||
sheet: sheet.sheet_name,
|
|
||||||
direction: sheet.direction,
|
|
||||||
}));
|
|
||||||
setTables(mappedTables.filter(t => t.sheet !== "OTCH9F"));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(
|
toast.error(
|
||||||
error?.response?.data?.detail || `Ошибка получения данных ${isProject ? 'проекта' : 'задачи'}`,
|
error?.response?.data?.detail || 'Ошибка получения данных задачи',
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
@ -98,11 +75,27 @@ export default function TaskPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (Number.isFinite(id)) {
|
if (Number.isFinite(id)) {
|
||||||
getData();
|
getSheets();
|
||||||
}
|
}
|
||||||
}, [id, isProject]);
|
}, [id]);
|
||||||
|
|
||||||
// Объединили два useEffect в один
|
useEffect(() => {
|
||||||
|
const getFormInfo = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
const data = await TasksApi.getById(id);
|
||||||
|
setTask(data.result);
|
||||||
|
} catch (error) {
|
||||||
|
// обработка ошибки
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Number.isFinite(id)) {
|
||||||
|
getFormInfo();
|
||||||
|
}
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
const handleCreateProject = (projectData) => {
|
const handleCreateProject = (projectData) => {
|
||||||
// логика создания проекта
|
// логика создания проекта
|
||||||
@ -114,14 +107,6 @@ export default function TaskPage() {
|
|||||||
return searchString.includes(query.toLowerCase());
|
return searchString.includes(query.toLowerCase());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Функция для экспорта
|
|
||||||
const handleExport = () => {
|
|
||||||
const fileName = isProject
|
|
||||||
? `project_${task?.title || id}`
|
|
||||||
: `form_${task?.title || taskId}`;
|
|
||||||
exportSingleForm(id, fileName);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center pt-12">
|
<div className="flex justify-center pt-12">
|
||||||
@ -132,7 +117,7 @@ export default function TaskPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{task && <TaskInfo task={task} isProject={isProject} />}
|
{task && <TaskInfo task={task} />}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -158,7 +143,9 @@ export default function TaskPage() {
|
|||||||
onChange={setQuery}
|
onChange={setQuery}
|
||||||
/>
|
/>
|
||||||
<Stack sx={{ flexDirection: 'row', spacing: '.5rem', justifyContent: 'end', gap: '.5rem' }}>
|
<Stack sx={{ flexDirection: 'row', spacing: '.5rem', justifyContent: 'end', gap: '.5rem' }}>
|
||||||
<ExportWithTextButton onClick={handleExport} />
|
<ExportWithTextButton
|
||||||
|
onClick={() => exportSingleForm(taskId, task?.title ?? 'form')}
|
||||||
|
/>
|
||||||
<PrimaryOutlinedButton
|
<PrimaryOutlinedButton
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => setModalStageOpen(true)}
|
onClick={() => setModalStageOpen(true)}
|
||||||
@ -169,10 +156,9 @@ export default function TaskPage() {
|
|||||||
<SettingStageModal
|
<SettingStageModal
|
||||||
isOpen={modalStageOpen}
|
isOpen={modalStageOpen}
|
||||||
onClose={() => setModalStageOpen(false)}
|
onClose={() => setModalStageOpen(false)}
|
||||||
taskId={isProject ? undefined : taskId}
|
taskId={taskId}
|
||||||
projectId={isProject ? projectId : undefined}
|
mode="task"
|
||||||
mode={isProject ? "project" : "task"}
|
formTypeCode={task?.form_type_code || ''}
|
||||||
formTypeCode={ task?.form_type_code || ''}
|
|
||||||
sheets={tables}
|
sheets={tables}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -183,9 +169,8 @@ export default function TaskPage() {
|
|||||||
query={query}
|
query={query}
|
||||||
projects={projects}
|
projects={projects}
|
||||||
filteredTables={filteredTables}
|
filteredTables={filteredTables}
|
||||||
basePath={"/table"}
|
basePath="/table"
|
||||||
formInfo={task}
|
formInfo={task}
|
||||||
isProject={isProject}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
import { Box, Paper, Typography } from '@mui/material';
|
import { Box, Paper, Typography } from '@mui/material';
|
||||||
import { CustomCheckbox } from '../StyledCheckbox';
|
import { CustomCheckbox } from '../../components/common/StyledCheckbox';
|
||||||
|
|
||||||
export const OrgUnitsAutocompletePaper = forwardRef(
|
export const OrgUnitsAutocompletePaper = forwardRef(
|
||||||
function OrgUnitsAutocompletePaper(
|
function OrgUnitsAutocompletePaper(
|
||||||
@ -27,7 +27,7 @@ import {
|
|||||||
ORG_UNIT_TYPE_OPTIONS,
|
ORG_UNIT_TYPE_OPTIONS,
|
||||||
ROLES_NAME_ID,
|
ROLES_NAME_ID,
|
||||||
FORM_TYPE_TRANSLATE
|
FORM_TYPE_TRANSLATE
|
||||||
} from '../../constants/constants';
|
} from '../../constants';
|
||||||
import { useAuth } from '../../app/context/AuthProvider';
|
import { useAuth } from '../../app/context/AuthProvider';
|
||||||
import { SspApi } from '../../api/ssp';
|
import { SspApi } from '../../api/ssp';
|
||||||
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
import { PrimaryButton } from '../../components/common/Buttons/Buttons';
|
||||||
@ -35,13 +35,10 @@ import {
|
|||||||
ExportExitButton,
|
ExportExitButton,
|
||||||
ExportWithTextButton,
|
ExportWithTextButton,
|
||||||
} from '../../components/common/Buttons/ButtonsActions';
|
} from '../../components/common/Buttons/ButtonsActions';
|
||||||
import { exportBulkForms, exportSingleForm } from '../../utils/exportFile';
|
import { exportBulkForms, exportSingleForm } from '../../utils/exportForm';
|
||||||
import { WhitePlus } from '../../components/common/icons/icons';
|
import { WhitePlus } from '../../components/common/icons/icons';
|
||||||
import { OrgUnitsAutocompletePaper } from '../../components/common/OrgUnitsAutocompletePaper/OrgUnitsAutocompletePaper';
|
import { OrgUnitsAutocompletePaper } from './OrgUnitsAutocompletePaper';
|
||||||
import { renderOrgUnitValue } from '../../components/common/OrgUnitsAutocompletePaper/renderOrgUnitValue';
|
import { renderOrgUnitValue } from './renderOrgUnitValue';
|
||||||
import { PaginatedList } from '../../components/common/PaginatedList';
|
|
||||||
import NavigationToggle from '../../components/common/NavigationToggle';
|
|
||||||
import { useNavigate } from 'react-router';
|
|
||||||
|
|
||||||
const modalSelectMenuProps = {
|
const modalSelectMenuProps = {
|
||||||
sx: { zIndex: 1301 },
|
sx: { zIndex: 1301 },
|
||||||
@ -55,8 +52,6 @@ const modalSelectSx = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function TasksPage() {
|
export default function TasksPage() {
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const [items, setItems] = useState([]);
|
const [items, setItems] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [loadingUpdate, setLoadingUpdate] = useState(false);
|
const [loadingUpdate, setLoadingUpdate] = useState(false);
|
||||||
@ -260,34 +255,128 @@ export default function TasksPage() {
|
|||||||
<>
|
<>
|
||||||
<PageContainer>
|
<PageContainer>
|
||||||
<HeaderSwitch />
|
<HeaderSwitch />
|
||||||
<NavigationToggle currentView={'tasks'} onViewChange={(path) => navigate(path)} />
|
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||||||
<PaginatedList
|
{exportMode && <span>Выделено задач: {exportList.length}</span>}
|
||||||
items={items}
|
</Box>
|
||||||
loading={loading}
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||||
totalCount={totalCount}
|
<div style={{ width: '20rem' }}>
|
||||||
page={page}
|
{/* <SearchComponent
|
||||||
limit={limit}
|
placeholder="Поиск по названию задачи"
|
||||||
onPageChange={handlePageChange}
|
value={query}
|
||||||
onLimitChange={handleLimitChange}
|
onChange={setQuery}
|
||||||
entityName="задач"
|
/> */}
|
||||||
emptyMessage="Задачи не найдены"
|
</div>
|
||||||
renderItem={(task, exportProps) => (
|
<Stack direction={'row'} spacing={'.5rem'}>
|
||||||
<TaskCardComponent
|
{!exportMode ? (
|
||||||
key={task.id}
|
<>
|
||||||
taskForm={task}
|
<ExportWithTextButton
|
||||||
{...exportProps}
|
text={'Пакетный экспорт'}
|
||||||
/>
|
onClick={() => {
|
||||||
|
setExportMode(true);
|
||||||
|
}}
|
||||||
|
></ExportWithTextButton>
|
||||||
|
{user.role_id == ROLES_NAME_ID.admin && (
|
||||||
|
<PrimaryButton onClick={openModal} startIcon={<WhitePlus />}>
|
||||||
|
Создать задачу
|
||||||
|
</PrimaryButton>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ExportWithTextButton
|
||||||
|
text={'Экспортировать'}
|
||||||
|
onClick={handleBulkExport}
|
||||||
|
disabled={exportList.length === 0}
|
||||||
|
/>
|
||||||
|
<ExportExitButton onClick={() => setExportMode(false)} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* TODO: вернуть, когда будет реализовано на бэке */}
|
||||||
|
{/* <FilterComponent statusFilter={statusFilter} setStatusFilter={setStatusFilter} /> */}
|
||||||
|
<TasksContainer>
|
||||||
|
{loading ? (
|
||||||
|
<CardsSkeleton count={limit} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{items.map((t) => (
|
||||||
|
<TaskCardComponent
|
||||||
|
exportMode={exportMode}
|
||||||
|
taskForm={t}
|
||||||
|
key={t.id + '-task-id'}
|
||||||
|
onAddForExport={(checked) => {
|
||||||
|
handleChangeToExportList(t.id, checked);
|
||||||
|
}}
|
||||||
|
onExportClick={handleSingleExport}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
getItemId={(task) => task.id}
|
</TasksContainer>
|
||||||
getItemTitle={(task) => task.title || 'Задача'}
|
|
||||||
extraActions={
|
{/* Pagination Footer */}
|
||||||
user.role_id === ROLES_NAME_ID.admin && (
|
{!loading && items.length > 0 && (
|
||||||
<PrimaryButton onClick={openModal} startIcon={<WhitePlus />} height={'2.5rem'}>
|
<Box
|
||||||
Создать задачу
|
sx={{
|
||||||
</PrimaryButton>
|
display: 'flex',
|
||||||
)
|
justifyContent: 'space-between',
|
||||||
}
|
alignItems: 'center',
|
||||||
/>
|
pt: 2,
|
||||||
|
pb: 2,
|
||||||
|
borderTop: '1px solid',
|
||||||
|
borderColor: 'divider',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
|
<Typography variant="body2" color="textSecondary">
|
||||||
|
Показать на странице:
|
||||||
|
</Typography>
|
||||||
|
<Select
|
||||||
|
value={limit}
|
||||||
|
onChange={handleLimitChange}
|
||||||
|
size="small"
|
||||||
|
sx={{ minWidth: 70 }}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<MenuItem value={10}>10</MenuItem>
|
||||||
|
<MenuItem value={20}>20</MenuItem>
|
||||||
|
<MenuItem value={50}>50</MenuItem>
|
||||||
|
<MenuItem value={100}>100</MenuItem>
|
||||||
|
<MenuItem value={200}>200</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Pagination
|
||||||
|
count={Math.ceil(totalCount / limit)}
|
||||||
|
page={page}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
color="primary"
|
||||||
|
size="large"
|
||||||
|
showFirstButton
|
||||||
|
showLastButton
|
||||||
|
disabled={loading}
|
||||||
|
renderItem={(item) => (
|
||||||
|
<PaginationItem
|
||||||
|
{...item}
|
||||||
|
sx={{
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: 'primary.main',
|
||||||
|
color: 'white',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'primary.dark',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Box sx={{ width: 120 }} /> {/* Spacer for symmetry */}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
<Modal
|
<Modal
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
|
|||||||
@ -1,64 +0,0 @@
|
|||||||
import { FormsSheetApi } from "../api/form_sheet";
|
|
||||||
import { ProjectsApi } from "../api/projects";
|
|
||||||
import { TasksApi } from "../api/tasks";
|
|
||||||
import { downloadFile } from "./downloadFile";
|
|
||||||
import { getApiErrorMessage } from "./getApiErrorMessage";
|
|
||||||
import { toast } from "react-toastify";
|
|
||||||
|
|
||||||
export const exportSingleForm = async (taskId, taskTitle) => {
|
|
||||||
try {
|
|
||||||
const response = await TasksApi.exportTask(taskId);
|
|
||||||
await downloadFile(response, taskTitle, "xlsx");
|
|
||||||
} catch (error) {
|
|
||||||
const message = await getApiErrorMessage(
|
|
||||||
error,
|
|
||||||
"Ошибка при скачивании файла",
|
|
||||||
);
|
|
||||||
toast.error(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const exportSheet = async (formId, sheetName, direction) => {
|
|
||||||
try {
|
|
||||||
const normalizedDirection =
|
|
||||||
direction && direction !== "null" ? direction : undefined;
|
|
||||||
const params = normalizedDirection
|
|
||||||
? { direction: normalizedDirection }
|
|
||||||
: {};
|
|
||||||
const response = await FormsSheetApi.export(formId, sheetName, params);
|
|
||||||
await downloadFile(response, sheetName, "xlsx");
|
|
||||||
} catch (error) {
|
|
||||||
const message = await getApiErrorMessage(
|
|
||||||
error,
|
|
||||||
"Ошибка при скачивании файла",
|
|
||||||
);
|
|
||||||
toast.error(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const exportSheetProject = async (formId, sheetName, year) => {
|
|
||||||
try {
|
|
||||||
const response = await ProjectsApi.export(formId, year, sheetName);
|
|
||||||
await downloadFile(response, sheetName, "xlsx");
|
|
||||||
} catch (error) {
|
|
||||||
const message = await getApiErrorMessage(
|
|
||||||
error,
|
|
||||||
"Ошибка при скачивании файла",
|
|
||||||
);
|
|
||||||
toast.error(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const exportBulkForms = async (formIds, fileName = "Задачи") => {
|
|
||||||
try {
|
|
||||||
const response = await TasksApi.exportTasks({ form_ids: formIds });
|
|
||||||
await downloadFile(response, fileName, "xlsx");
|
|
||||||
} catch (error) {
|
|
||||||
const message = await getApiErrorMessage(
|
|
||||||
error,
|
|
||||||
"Ошибка при скачивании файла",
|
|
||||||
);
|
|
||||||
toast.error(message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
48
web/src/utils/exportForm.js
Normal file
48
web/src/utils/exportForm.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { FormsSheetApi } from '../api/form_sheet';
|
||||||
|
import { TasksApi } from '../api/tasks';
|
||||||
|
import { downloadFile } from './downloadFile';
|
||||||
|
import { getApiErrorMessage } from './getApiErrorMessage';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
export const exportSingleForm = async (taskId, taskTitle) => {
|
||||||
|
try {
|
||||||
|
const response = await TasksApi.exportTask(taskId);
|
||||||
|
await downloadFile(response, taskTitle, 'xlsx');
|
||||||
|
} catch (error) {
|
||||||
|
const message = await getApiErrorMessage(
|
||||||
|
error,
|
||||||
|
'Ошибка при скачивании файла',
|
||||||
|
);
|
||||||
|
toast.error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportSheet = async (formId, sheetName, direction) => {
|
||||||
|
try {
|
||||||
|
const normalizedDirection =
|
||||||
|
direction && direction !== 'null' ? direction : undefined;
|
||||||
|
const params = normalizedDirection ? { direction: normalizedDirection } : {};
|
||||||
|
const response = await FormsSheetApi.export(formId, sheetName, params);
|
||||||
|
await downloadFile(response, sheetName, 'xlsx');
|
||||||
|
} catch (error) {
|
||||||
|
const message = await getApiErrorMessage(
|
||||||
|
error,
|
||||||
|
'Ошибка при скачивании файла',
|
||||||
|
);
|
||||||
|
toast.error(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const exportBulkForms = async (formIds, fileName = 'Задачи') => {
|
||||||
|
try {
|
||||||
|
const response = await TasksApi.exportTasks({ form_ids: formIds });
|
||||||
|
await downloadFile(response, fileName, 'xlsx');
|
||||||
|
} catch (error) {
|
||||||
|
const message = await getApiErrorMessage(
|
||||||
|
error,
|
||||||
|
'Ошибка при скачивании файла',
|
||||||
|
);
|
||||||
|
toast.error(message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user